Статический метод Python не всегда вызывается
При разборе атрибутов с помощью __dict__
мой @staticmethod
не является callable
,
Python 2.7.5 (default, Aug 29 2016, 10:12:21)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from __future__ import (absolute_import, division, print_function)
>>> class C(object):
... @staticmethod
... def foo():
... for name, val in C.__dict__.items():
... if name[:2] != '__':
... print(name, callable(val), type(val))
...
>>> C.foo()
foo False <type 'staticmethod'>
- Как это возможно?
- Как проверить, может ли статический метод вызываться?
Ниже приведу более подробный пример:
скрипт test.py
from __future__ import (absolute_import, division, print_function)
class C(object):
@staticmethod
def foo():
return 42
def bar(self):
print('Is bar() callable?', callable(C.bar))
print('Is foo() callable?', callable(C.foo))
for attribute, value in C.__dict__.items():
if attribute[:2] != '__':
print(attribute, '\t', callable(value), '\t', type(value))
c = C()
c.bar()
Результат для python2
> python2.7 test.py
Is bar() callable? True
Is foo() callable? True
bar True <type 'function'>
foo False <type 'staticmethod'>
Тот же результат для python3
> python3.4 test.py
Is bar() callable? True
Is foo() callable? True
bar True <class 'function'>
foo False <class 'staticmethod'>
2 ответа
Причиной такого поведения является протокол дескриптора. C.foo
не вернет staticmethod
но нормальная функция в то время как 'foo'
в __dict__
это staticmethod
(а также staticmethod
это дескриптор).
Короче C.foo
не то же самое, что C.__dict__['foo']
в этом случае - а точнее C.__dict__['foo'].__get__(C)
(см. также раздел документации документации модели по дескрипторам):
>>> callable(C.__dict__['foo'].__get__(C))
True
>>> type(C.__dict__['foo'].__get__(C))
function
>>> callable(C.foo)
True
>>> type(C.foo)
function
>>> C.foo is C.__dict__['foo'].__get__(C)
True
В вашем случае я бы проверил на вызываемые getattr
(который знает о дескрипторах и как получить к ним доступ) вместо того, что хранится в качестве значения в классе __dict__
:
def bar(self):
print('Is bar() callable?', callable(C.bar))
print('Is foo() callable?', callable(C.foo))
for attribute in C.__dict__.keys():
if attribute[:2] != '__':
value = getattr(C, attribute)
print(attribute, '\t', callable(value), '\t', type(value))
Какие печатные издания (на python-3.x):
Is bar() callable? True
Is foo() callable? True
bar True <class 'function'>
foo True <class 'function'>
Типы разные на Python-2.x, но результат callable
та же:
Is bar() callable? True
Is foo() callable? True
bar True <type 'instancemethod'>
foo True <type 'function'>
Вы не можете проверить, если staticmethod
объект может быть вызван или нет. Это обсуждалось на трекере в выпуске 20309. - Не все дескрипторы методов могут быть вызваны и закрыты как "не ошибка".
Короче говоря, не было никакого обоснования для реализации __call__
для статических методов объектов. Встроенный callable
не может знать, что staticmethod
Объект - это то, что по сути "держит" вызываемый объект.
Хотя вы могли бы реализовать это (для staticmethod
с и classmethod
s) это будет бремя обслуживания, которое, как упоминалось ранее, не имеет реальных мотивирующих сценариев использования.
Для вашего случая вы можете использовать getattr(C, name)
выполнить поиск объекта с именем name
; это эквивалентно выполнению C.<name>
, getattr
после обнаружения объекта staticmethod вызовет его __get__
чтобы вернуть вызываемому он управляет. Вы можете использовать callable
на что.
Хороший учебник по дескрипторам можно найти в документации, посмотрите Descriptor HOWTO.