Статический метод 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с и classmethods) это будет бремя обслуживания, которое, как упоминалось ранее, не имеет реальных мотивирующих сценариев использования.


Для вашего случая вы можете использовать getattr(C, name) выполнить поиск объекта с именем name; это эквивалентно выполнению C.<name>, getattrпосле обнаружения объекта staticmethod вызовет его __get__ чтобы вернуть вызываемому он управляет. Вы можете использовать callable на что.

Хороший учебник по дескрипторам можно найти в документации, посмотрите Descriptor HOWTO.

Другие вопросы по тегам