Подтвердить что ты не робот

Лучший способ добавить атрибуты к функции Python

Возьмем простой случай функции Python, которая вычисляет математическую функцию:

def func(x, a, b, c):
    """Return the value of the quadratic function, ax^2 + bx + c."""

    return a*x**2 + b*x + c

Предположим, я хочу "прикрепить" дополнительную информацию в виде атрибута функции. Например, представление LaTeX. Я знаю, что благодаря PEP232 Я могу сделать это вне определения функции:

def func(x, a, b, c):
    return a*x**2 + b*x + c
func.latex = r'$ax^2 + bx + c$'

но я хотел бы сделать это в самом определении функции. Если я напишу

def func(x, a, b, c):
    func.latex = r'$ax^2 + bx + c$'
    return a*x**2 + b*x + c

это, безусловно, работает, но только после того, как я впервые вызвал func (потому что Python "ленив" при выполнении функций (?))

Является ли мой единственный способ написать класс, подлежащий вызову?

class MyFunction:
     def __init__(self, func, latex):
         self.func = func
         self.latex = latex

     def __call__(self, *args, **kwargs):
         return self.func(*args, **kwargs)

func = MyFunction(lambda x,a,b,c: a*x**2+b*x+c, r'$ax^2 + bx + c$')

Или есть особенность языка, который я не замечаю, чтобы сделать это аккуратно?

4b9b3361

Ответ 1

Лучшим подходом к выполнению этого будет использование декораторов, для этого у вас есть два варианта:

Декоратор на основе функций:

Вы можете создать декоратор на основе функций, который принимает в качестве аргумента представление латекса и прикрепляет его к функции, которую он украшает:

def latex_repr(r):
    def wrapper(f):
        f.latex = r
        return f
    return wrapper

Затем вы можете использовать его при определении своей функции и предоставить соответствующее представление:

@latex_repr(r'$ax^2 + bx + c$')
def func(x, a, b, c):
    return a*x**2 + b*x + c

Это означает:

func = latex_repr(r'$ax^2 + bx + c$')(func)

и делает атрибут latex доступным сразу после определения функции:

print(func.latex)
'$ax^2 + bx + c$'

Я представил представление как обязательный аргумент, вы можете определить разумное значение по умолчанию, если вы не хотите, чтобы представление всегда предоставлялось.

Декоратор на основе классов:

Если классы являются вашими предпочтениями, декоратор класса может также использоваться для аналогичного эффекта на более Pythonic пути, чем ваша первоначальная попытка:

class LatexRepr:
    def __init__(self, r):
        self.latex = r

    def __call__(self, f):
        f.latex = self.latex
        return f

вы используете его таким же образом:

@LatexRepr(r'$ax^2 + bx + c$')
def func(x, a, b, c):
    return a*x**2 + b*x + c

print(func.latex)
'$ax^2 + bx + c$'

Здесь LatexRepr(r'$ax^2 + bx + c$') инициализирует класс и возвращает вызываемый экземпляр (__call__). Что это делает:

func = LatexRepr(r'$ax^2 + bx + c$')(func)
#                   __init__    
#                                  __call__

и делает то же самое wrapped.


Поскольку они оба просто добавляют аргумент функции, они просто возвращают его как-есть. Они не заменяют его другим вызываемым.

Хотя подход на основе классов делает трюк, декоратор на основе функций должен быть более быстрым и легким.


Вы дополнительно спросили:
", потому что Python" ленив "при выполнении функций": Python просто компилирует тело функции, он не выполняет никаких утверждений внутри него. Вот почему вам сначала нужно вызвать функцию для "получения" атрибута latex.

Дополнительным недостатком этого подхода является то, что вы выполняете это назначение каждый раз, когда вы вызываете функцию

Ответ 2

Поскольку вы рассматриваете свои функции как более сложные объекты, чем простые функции Python, это, безусловно, имеет смысл представлять их как вызываемые экземпляры назначенного пользовательского класса, как вы и предполагали.

Однако более простой и общий способ делать то, что вы хотите, это использовать декораторы:

def with_func_attrs(**attrs):
    def with_attrs(f):
        for k,v in attrs.items():
            setattr(f, k, v)
        return f
    return with_attrs

@with_func_attrs(latex = r'$ax^2 + bx + c$', foo = 'bar')
def func(...):
    return ...