Ответ 1
Функция в Python является только вызываемым объектом. Использование def
для определения функции является одним из способов создания такого объекта. Но на самом деле ничего не мешает вам создать вызываемый тип и создать экземпляр для его получения.
Итак, следующие две вещи в основном равны:
def foo ():
print('hello world')
class FooFunction:
def __call__ (self):
print('hello world')
foo = FooFunction()
За исключением того, что последнее, очевидно, позволяет нам устанавливать специальные типы функций, например __str__
и __repr__
.
class FooFunction:
def __call__ (self):
print('hello world')
def __str__ (self):
return 'Foo function'
foo = FooFunction()
print(foo) # Foo function
Но создание типа только для этого становится немного утомительным, а также затрудняет понимание того, что делает функция: ведь синтаксис def
позволяет нам просто определить тело функции. Поэтому мы хотим сохранить это так!
К счастью, у Python есть эта замечательная функция, называемая декораторами, которую мы можем использовать здесь. Мы можем создать декоратор функции, который будет обертывать любую функцию внутри настраиваемого типа, который вызывает пользовательскую функцию для __str__
. Это может выглядеть так:
def with_str (str_func):
def wrapper (f):
class FuncType:
def __call__ (self, *args, **kwargs):
# call the original function
return f(*args, **kwargs)
def __str__ (self):
# call the custom __str__ function
return str_func()
# decorate with functool.wraps to make the resulting function appear like f
return functools.wraps(f)(FuncType())
return wrapper
Затем мы можем использовать это, чтобы добавить функцию __str__
в любую функцию, просто украсив ее. Это будет выглядеть так:
def foo_str ():
return 'This is the __str__ for the foo function'
@with_str(foo_str)
def foo ():
print('hello world')
>>> str(foo)
'This is the __str__ for the foo function'
>>> foo()
hello world
Очевидно, что это имеет некоторые ограничения и недостатки, поскольку вы не можете точно воспроизвести, что def
будет делать для новой функции внутри этого декоратора.
Например, использование inspect
модуля для поиска аргументов не будет работать должным образом: для вызываемого типа он будет включать в self
аргумент self
и при использовании универсального декоратора он сможет только сообщать подробности об wrapper
. Однако могут быть некоторые решения, например, обсуждаемые в этом вопросе, которые позволят вам восстановить некоторые функциональные возможности.
Но это обычно означает, что вы инвестируете много усилий, чтобы получить работу __str__
над объектом функции, который, вероятно, очень редко будет использоваться. Итак, вы должны подумать о том, действительно ли вам нужна реализация __str__
для ваших функций и какие операции вы будете выполнять над этими функциями.