Вопрос или проблема
Определите простой метод:
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
только для таких связанных методов.