Почему ismethod возвращает False для метода, когда он accessed через класс?

Вопрос или проблема

Определите простой метод:

class Foo:
    def bar(self):
        print('bar!')

Теперь используйте inspect.ismethod:

>>> from inspect import ismethod
>>> ismethod(Foo.bar)
False

Почему это так? Разве bar не является методом?

Смотрите документацию:

inspect.ismethod(object)

Возвращает True, если объект является связанным методом, написанным на Python.

Таким образом, ismethod на самом деле только проверяет связанные методы. Это означает, что если вы создадите экземпляр и получите доступ к методу через этот экземпляр, ismethod вернет True:

>>> obj = Foo()
>>> ismethod(obj.bar)
True

“Связанный” метод означает метод, который уже связан с объектом, поэтому этот объект передается как параметр self. Вот как вы можете вызывать obj.bar() без аргументов, даже несмотря на то, что bar был объявлен с одним параметром.

Мы также можем увидеть разницу, посмотрев на типы:

>>> Foo.bar
<function Foo.bar at 0x7853771f1ab0>
>>> obj.bar
<bound method Foo.bar of <__main__.Foo object at 0x7853771e4850>>

Только последний является связанным методом. Foo.bar — это функция, она не считается методом в системе типов Python.


Так почему inspect.ismethod ведет себя таким образом?

С точки зрения Python, bar — это обычная функция, на которую ссылается атрибут класса Foo. Чтобы это прояснить, предположим, что вы определяете функцию вне класса и затем присваиваете ее атрибуту класса:

def baz(self):
    print('baz!')

Foo.baz = baz

Если вы вызовете ismethod(baz), вы должны ожидать, что она вернет False, потому что baz очевидно является просто функцией, объявленной во внешней области видимости. Вы получите тот же результат, если вызовете ismethod(Foo.baz), потому что Foo.baz — это всего лишь ссылка на ту же функцию. На самом деле, выражение Foo.baz оценивается, чтобы получить ссылку на функцию до того, как эта ссылка передается в качестве аргумента в ismethod; конечно, ismethod не может давать разные ответы для одного и того же аргумента в зависимости от того, откуда этот аргумент поступает.

bar объявлена внутри класса Foo. Способ работы классов в Python в основном эквивалентен объявлению функции вне класса и последующему присвоению ее атрибуту класса с таким же именем, как и функции. Так что bar и baz работают точно так же:

>>> obj.baz
<bound method baz of <__main__.Foo object at 0x7853771e4850>>
>>> obj.baz()
baz!

Короче говоря, Foo.bar “не является методом”, потому что в Python “методы” — это просто обычные функции до тех пор, пока они не связаны с экземплярами.

Ответ или решение

ismethod возвращает False для метода, когда он доступен через класс, из-за того, что этот метод является "связанным" (binded) только тогда, когда он вызывается из экземпляра класса. Чтобы прояснить эту концепцию, давайте рассмотрим некоторые основные моменты.

Определение класса и метода

Рассмотрим ваш пример:

class Foo:
    def bar(self):
        print('bar!')

Здесь bar является методом класса Foo, но когда мы обращаемся к этому методу через сам класс, как в следующем коде:

from inspect import ismethod

print(ismethod(Foo.bar))  # Это возвращает False

возвращается False, потому что Foo.bar представляет собой обычную функцию, а не связанный метод.

Понятие связанного метода

В Python "связанный метод" (bound method) – это метод, который уже привязан к объекту (экземпляру класса). Это означает, что объект предоставляется как первый аргумент (обычно называемый self). Например, если мы создадим экземпляр класса Foo и затем обратимся к методу через экземпляр:

obj = Foo()
print(ismethod(obj.bar))  # Это возвращает True

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

Объяснение поведения ismethod

Документация Python чётко указывает, что inspect.ismethod проверяет, является ли объект связанным методом. Когда вы вызываете Foo.bar, это просто ссылка на функцию bar, которая не привязана ни к какому экземпляру. Таким образом, она не считается методом в контексте, в котором ismethod работает.

Для большего понимания, если вы определите функцию за пределами класса и затем присвоите ее как атрибут класса, ismethod также вернет False:

def baz(self):
    print('baz!')

Foo.baz = baz
print(ismethod(Foo.baz))  # Это вернет False

Итог

Таким образом, Foo.bar и Foo.baz являются обычными функциями (функциями, определенными в области видимости класса), и они становятся методами только тогда, когда вы получаете к ним доступ через экземпляр класса, что приводит к созданию связанного метода.

В заключение, в Python методы являются обычными функциями, пока они не будут связаны с экземплярами, и inspect.ismethod дает True только для таких связанных методов.

Оцените материал
Добавить комментарий

Капча загружается...