Python - функция как атрибут класса становится связанным методом
Я заметил, что если я определяю атрибут класса, равный функции, при создании экземпляра этого класса, атрибут становится связанным методом. Может кто-нибудь объяснить мне причину такого поведения?
In [9]: def func():
...: pass
...:
In [10]: class A(object):
....: f = func
....:
In [11]: a = A()
In [12]: a.f
Out[12]: <bound method A.func of <__main__.A object at 0x104add190>>
In [13]: a.f()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-13-19134f1ad9a8> in <module>()
----> 1 a.f()
global a.f = <bound method A.func of <__main__.A object at 0x104add190>>
TypeError: func() takes no arguments (1 given)
3 ответа
Вы присвоили функцию атрибуту f
, Поскольку функция теперь находится внутри класса, она становится методом, в частности связанным методом (потому что она привязана к объекту, дальнейшее объяснение здесь).
Методы в классах получают хотя бы один параметр (self
), если специально не сказано - см. методы класса и статические методы - по этой причине ошибка говорит, что func
не принимает аргументов (как это определено как def func():
) но получил 1 (self
)
Чтобы сделать то, что вы хотите, вы должны сказать Python, что вы используете статический метод
def func():
pass
class A(object):
f = staticmethod(func)
Python не является ОО-системой, основанной на сообщениях1. Вместо этого, подобно JavaScript, свойства разрешаются в функции первого класса, а затем вызываются; Поведение немного отличается в механике таких, как обнаружено.
В Python требуется, чтобы у методов был хотя бы один параметр, обычно называемый self
, это будет автоматически предоставлено ассоциированному экземпляру, когда он вызывается как метод.
Кроме того (и, возможно, что касается вопроса), Python не делает различий между использованием def f..
или же f = some_func()
при установлении привязок членов экземпляра; возможно, это соответствует поведению вне классов.
В этом примере назначение функции экземпляру "заставляет ожидать, что она будет обрабатываться как метод экземпляра". Это одна и та же функция без параметров, вызываемая в обоих случаях; только будущее использование таких уместно.
Теперь, в отличие от JavaScript, Python обрабатывает методы и объектную ассоциацию через концепцию связанных методов - функции, разрешенные как методы, всегда "связаны".
Поведение a.f
возврат метода привязки - функция, которая автоматически предоставит связанный объект первому параметру в виде self
- выполняется независимо от источника функции. В этом случае это означает, что функция без параметров не может использоваться, когда она "связана", так как она не принимает self
параметр.
В качестве демонстрации следующее не будет выполнено таким же образом, поскольку исходный метод не соответствует минимальным требованиям для принятия экземпляра в качестве аргумента:
g = a.f
g()
В этом случае зовет g()
эквивалентно звонку func(a)
,
1 Для сравнения, Java, C#, Ruby и SmallTalk являются ОО-системами на основе сообщений - в них объекту предписывается вызывать метод по "имени" вместо разрешения метода (или функции) как значения, которое может быть вызвано,
Немного поздно для вечеринки, но другое жизнеспособное решение - сохранить функцию как словарь, который является атрибутом класса.
# Some arbitrary function
def f(x):
return x
# A class, which will wrap the function as a dictionary object, which is a class attr
class Test:
def __init__(self,f):
self.f = {'f':f}
def func(self,x):
return self.f['f'](x)
# Now let's test the Test object
t = Test(f=f)
t.func(3)
>>>
3
Этот подход более подробен, чем принятый ответ, однако он не затрагивает статический метод, декораторы или другие сложные темы. Так что это сработает в крайнем случае, если вас пугает другой, предпочтительный подход.
Изменить: если вы хотите, чтобы это было достаточно надежным для обработки аргументов ключевого слова:
class Test:
def __init__(self,f):
self.f = {'f':f}
def func(self,p):
return self.f['f'](**p)
def f(**p):
return p['x'] * p['y']
t = Test(f=f)
t.func({'x':2,'y':3})
>>>
6