Python: проверьте, является ли метод статическим

#python #python-3.x #static-methods

#python #статические методы

Вопрос:

предположим следующее определение класса:

 class A:
  def f(self):
    return 'this is f'

  @staticmethod
  def g():
    return 'this is g'

a = A() 
  

Итак, f — это обычный метод, а g — статический метод.

Теперь, как я могу проверить, являются ли функциональные объекты a.f и a.g статическими или нет? Существует ли функция «isstatic» в Python?

Я должен это знать, потому что у меня есть списки, содержащие много разных объектов функции (метода), и для их вызова я должен знать, ожидают ли они «self» в качестве параметра или нет.

Ответ №1:

Давайте немного поэкспериментируем:

 >>> import types
>>> class A:
...   def f(self):
...     return 'this is f'
...   @staticmethod
...   def g():
...     return 'this is g'
...
>>> a = A()
>>> a.f
<bound method A.f of <__main__.A instance at 0x800f21320>>
>>> a.g
<function g at 0x800eb28c0>
>>> isinstance(a.g, types.FunctionType)
True
>>> isinstance(a.f, types.FunctionType)
False
  

Похоже, его можно использовать types.FunctionType для различения статических методов.

Комментарии:

1. спасибо, что указали мне на модуль «типы», я почти забыл об этом.

Ответ №2:

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

(в Python 2.7):

 >>> type(A.f)
<type 'instancemethod'>
>>> type(A.g)
<type 'function'>
  

или атрибуты экземпляра в Python 3.x

 >>> a = A()
>>> type(a.f)
<type 'method'>
>>> type(a.g)
<type 'function'>
  

Комментарии:

1. Обратите внимание, что это работает только в том случае, если объект создан (по крайней мере, в python 3), поэтому, если вы хотите проверить, является ли метод статическим без создания экземпляра объекта, то это не сработает.

2. @DuckPuncher Спасибо! Вы правы, я обновил ответ.

3. @DuckPuncher В Python 3 вы можете проверить isinstance(A.__dict__['f'], types.FunctionType) и isinstance(A.__dict__['g'], staticmethod)

Ответ №3:

В дополнение к приведенным здесь ответам, в Python 3 лучший способ выглядит следующим образом:

 import inspect

class Test:
    @staticmethod
    def test(): pass

isstatic = isinstance(inspect.getattr_static(Test, "test"), staticmethod)
  

Мы используем getattr_static вместо getattr , поскольку getattr будет извлекаться связанный метод или функция, а не staticmethod объект класса. Вы можете выполнить аналогичную проверку для classmethod типов и property (например, атрибутов, определенных с помощью @property декоратора)

Обратите внимание, что даже если это staticmethod , не предполагайте, что он был определен внутри класса. Источник метода, возможно, произошел из другого класса. Чтобы получить истинный источник, вы можете посмотреть на полное имя базовой функции и модуль. Например:

 class A:
    @staticmethod:
    def test(): pass

class B: pass
B.test = inspect.getattr_static(A, "test")

print("true source: ", B.test.__qualname__)

  

Технически, любой метод может использоваться как «статические» методы, при условии, что они вызываются в самом классе, так что просто имейте это в виду. Например, это будет работать отлично:

 class Test:
    def test():
        print("works!")

Test.test()
  

Этот пример не будет работать с экземплярами Test , поскольку метод будет привязан к экземпляру и вызываться как Test.test(self) вместо этого.

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

 class Test:
    def test(self):
        print("works!")

Test.test(None)
  

Возможно, другим редким случаем является staticmethod , который также привязан к классу или экземпляру. Например:

 class Test:
    @classmethod
    def test(cls): pass

Test.static_test = staticmethod(Test.test)
  

Хотя технически это a staticmethod , на самом деле он ведет себя как a classmethod . Итак, в своем самоанализе вы можете рассмотреть возможность проверки __self__ (рекурсивно на __func__ ), чтобы увидеть, привязан ли метод к классу или экземпляру.

Комментарии:

1. Большое спасибо @Azmisov за get_attr_static то, что удалось избежать вызова протокола дескриптора! Сегодня я кое-чему научился 🙂

Ответ №4:

Так получилось, что у меня есть модуль для решения этой проблемы. И это решение, совместимое с Python2 / 3. И это позволяет тестировать с помощью метода, наследуемого от родительского класса.

Кроме того, этот модуль также может тестировать:

  1. обычный атрибут
  2. метод в стиле свойств
  3. обычный метод
  4. staticmethod
  5. classmethod

Например:

 class Base(object):
    attribute = "attribute"

    @property
    def property_method(self):
        return "property_method"

    def regular_method(self):
        return "regular_method"

    @staticmethod
    def static_method():
        return "static_method"

    @classmethod
    def class_method(cls):
        return "class_method"

class MyClass(Base):
    pass
  

Вот решение только для staticmethod. Но я рекомендую использовать модуль, размещенный здесь.

 import inspect

def is_static_method(klass, attr, value=None):
    """Test if a value of a class is static method.

    example::

        class MyClass(object):
            @staticmethod
            def method():
                ...

    :param klass: the class
    :param attr: attribute name
    :param value: attribute value
    """
    if value is None:
        value = getattr(klass, attr)
    assert getattr(klass, attr) == value

    for cls in inspect.getmro(klass):
        if inspect.isroutine(value):
            if attr in cls.__dict__:
                bound_value = cls.__dict__[attr]
                if isinstance(bound_value, staticmethod):
                    return True
    return False
  

Комментарии:

1. В этом есть ошибка. Он проверяет, является ли статичным любой метод с таким именем в MRO класса. Если в базовом классе есть статический метод, который затеняется нестатическим методом в подклассе, ваша функция будет выводить True .

2. Я исправил ошибку в этом пакете github.com/MacHu-GWU/inspect_mate-project

Ответ №5:

Зачем беспокоиться? Вы можете просто вызвать g, как вы вызываете f:

 a = A()
a.f()
a.g()
  

Комментарии:

1. вау, действительно, вы правы! Я думал, что это не сработает, потому что у меня есть методы в списке, скажем l = [a.f, a.g] , и я вызываю их с помощью l[0]() и l[1]() . Но это работает! Странно, я всегда думал, что обычным методам нужна ссылка на их содержащий объект в качестве первого параметра. Я имею в виду, что если я это сделаю A.f() , это ошибка, хотя A.g() работает, хотя A.f(a) и работает нормально.

2. Очевидно, что это хороший ответ (не проверяйте, является ли метод статическим!) на проблему, но он не отвечает на вопрос в названии (проверьте, является ли метод статическим).

3. Ты прав, Иржи. Но я думаю, что хорошо иметь несколько способов решения проблемы, вот почему я опубликовал этот ответ. И, похоже, OP нашел это полезным, так что победа / проигрыш 🙂

4. @DanielJung это работает, потому что, когда вы выполняете a.f, python создает связанный экземпляр метода (он привязан к объекту, для которого вызывается). Когда вы выполняете A.f, он возвращает несвязанный метод (вы все еще можете вызвать его с помощью A.f(a)), в то время как A.g возвращает простую функцию.

5. Зачем беспокоиться? Потому что вы внедряете метакласс, декоратор или пишете инструменты анализа кода.