Тип метода-метода Python?
Что такое тип оболочки метода в Python 3? Если я определяю такой класс:
class Foo(object):
def __init__(self, val):
self.val = val
def __eq__(self, other):
return self.val == other.val
И затем выполните:
Foo(42).__eq__
Я получаю:
<bound method Foo.__eq__ of <__main__.Foo object at 0x10121d0>>
Но если я это делаю (в Python 3):
Foo(42).__ne__
Я получаю:
<method-wrapper '__ne__' of Foo object at 0x1073e50>
Что такое тип метода-обертки?
Изменить: извините за более точное: class method-wrapper
- это тип __ne__
, как если бы я делал:
>>> type(Foo(42).__ne__)
<class 'method-wrapper'>
В то время как тип __eq__
:
>>> type(Foo(42).__eq__)
<class 'method'>
Кроме того, method-wrapper
представляется типом любого магического метода undefined для класса (поэтому __le__
, __repr__
, __str__
и т.д., если он явно не определен, также будет иметь этот тип).
Мне интересно, как класс method-wrapper
используется Python. Все ли "стандартные реализации" методов для класса просто экземпляры этого типа?
Ответы
Ответ 1
Это связано с тем, что "несвязанные методы" не существуют в Python 3.
В Python 3000 концепция несвязанных методов была удалена, а выражение "A.spam" возвращает объект простой функции. Оказалось, что ограничение, что первый аргумент должен был быть экземпляром А, редко помогал в диагностике проблем и часто являлся препятствием для продвинутых способов использования - некоторые из них называли это "утка, набравшая себя", которая кажется подходящим названием. (Источник)
В Python 2.x у нас были связанные методы и несвязанные методы. Связанный метод привязан к объекту, что означает, что когда он был вызван, он передал экземпляр объекта как первую переменную (self
, обычно). Несвязанный метод был тем, где функция была методом, но без экземпляра, к которому она принадлежала, - она выдавала бы ошибку, если в метод был передан что-то, кроме экземпляра объекта.
Теперь, в 3.x, это было изменено. Вместо связанных/несвязанных методов, когда вы запрашиваете метод объекта, он возвращает объект функции, но заверяется в функцию-обертку, которая передает переменную экземпляра. Таким образом, нет необходимости проводить различие между связанными и несвязанными методами - привязанные методы завернуты, unbound - нет.
Чтобы прояснить разницу:
2.x:
a = A()
f = A.method # f is an unbound method - you must pass an instance of `A` in to it as the first argument.
f = a.method # f is a bound method, bound to the instance `a`.
3.x:
a = A()
f = A.method # f is a function
f = a.method # f is a wrapped function with it first argument filled with `a`.
a.method
можно охарактеризовать как:
def method-wrapper():
A.method(a)
Подробнее об этом, посмотрите блог Guido - историю Python.
Edit:
Итак, причина, по которой это все применяется, заключается в том, что здесь __ne__()
не был переопределен - он находится в состоянии по умолчанию, которое является проверкой личности, реализован в C (строка 980). Обертка предназначена для предоставления метода с вышеуказанной функциональностью.
Ответ 2
Похоже, что тип <method-wrapper ..>
используется CPython для методов, реализованных в C-коде. В принципе тип не обертывает другой метод. Вместо этого он обертывает C-реализованную функцию как связанный метод. Таким образом, <method-wrapper>
точно соответствует a <bound-method>
, за исключением того, что он реализован в C.
В CPython есть два специальных типа, связанных с этим.
-
<slot wrapper>
Которая (по крайней мере) обертывает C-реализованную функцию. Ведет себя как <unbound method>
в CPython 2 (по крайней мере иногда) или <function>
в CPython 3
-
<method-wrapper>
Что переносит C-реализованную функцию как связанный метод. Экземпляры этого типа имеют атрибут __self__
, который используется в качестве первого аргумента при его вызове.
Если у вас есть <slot wrapper>
, вы привязываете его к объекту с __get__
, чтобы получить <method-wrapper>
:
# returns a <slot_wrapper> on both CPython 2 and 3
sw = object.__getattribute__
# returns a <method-wrapper>
bound_method = sw.__get__(object())
# In this case raises AttributeError since no "some_attribute" exists.
bound_method("some_attribute")
Вы можете вызвать __get__
для любого функциональноподобного объекта в Python, чтобы получить <bound method>
или <method-wrapper>
. Примечание __get__
на обоих этих типах просто вернет self.
Python 3
Тип object
в CPython 3 имеет C-реализации для __ne__
и __eq__
, а также для любого другого оператора сравнения. Таким образом, object.__ne__
возвращает a <slot wrapper>
для этого оператора. Аналогично object().__ne__
возвращает a <method-wrapper>
, который может использоваться для сравнения этого объекта.
Так как вы не определили __ne__
в своем классе, вы получите связанный метод (как <method-wrapper>
), который является C-реализованной функцией для экземпляра объекта (включая производные экземпляры). Моя ставка заключается в том, что эта функция C проверит, если вы определили какой-либо __eq__
, вызовите это, а затем не результат.
Python 2 (не ответил, но ответил)
Python 2 ведет себя значительно иначе. Поскольку мы имеем понятие несвязанных методов. которые требуют, чтобы вы вызывали их с соответствующим типом первого аргумента, оба <slot wrapper>
и <method-wrapper>
имеют __objclass__
, который является типом, первым аргументом должен быть экземпляр.
Более того: Python 2 не имеет реализаций операторов сравнения для типа object
. Таким образом, object.__ne__
не является функцией для сравнения объектов. Скорее интересно, тип type
, являющийся метаклассом object
, имеет C-реализованный оператор __ne__
. Таким образом, вы получите связанный метод из object.__ne__
, который попытается сравнить тип object
с любым другим типом (или объектом).
Таким образом, object().__ne__
будет действительно терпеть неудачу с AttributeError
, поскольку object
не определяет какой-либо такой метод. Учитывая, что object() == object()
работает (давая False), я бы предположил, что CPython 2 имеет специальные случаи в интерпретаторе для сравнения объектов.
Еще раз мы видим, что CPython 3 очистил некоторые менее удачные детали реализации Python 2.
Ответ 3
Ниже приведен тестовый код из моих процедур проверки переменных. Определен класс Tst, который имеет метод __str__
. Чтобы узнать, определен ли экземпляр класса __repr__
или __str__
, вы можете проверить hasattr(obj.__str__, '__code__')
:
Пример:
class Tst(object):
""" Test base class.
"""
cl_var = 15
def __init__(self, in_var):
self.ins_var = in_var
def __str__(self):
# Construct to build the classname ({self.__class__.__name__}).
# so it works for subclass too.
result = f"{self.__class__.__name__}"
result += f"(cl_var: {self.__class__.cl_var}, ins_var: {self.ins_var})"
return result
def show(self):
""" Test method
"""
print(self.ins_var)
t5 = Tst(299)
print('\n\n----- repr() ------')
print(f"obj.repr(): {repr(t5)}")
print(f"obj.repr.class type: {type(t5.__repr__.__class__)}")
print(f"obj.repr.class.name: {t5.__repr__.__class__.__name__}")
print(f"obj.__repr__ has code: {hasattr(t5.__repr__, '__code__')}")
print('\n\n----- str() ------')
print(f"obj.str(): {str(t5)}")
print(f"obj.str.class type: {type(t5.__str__.__class__)}")
print(f"obj.str.class.name: {t5.__str__.__class__.__name__}")
print(f"obj.__str__ has code: {hasattr(t5.__str__, '__code__')}")
Возвращает:
----- repr() ------
obj.repr(): <__main__.Tst object at 0x107716198>
obj.repr.class type: <class 'type'>
obj.repr.class.name: method-wrapper
obj.__repr__ has code: False
----- str() ------
obj.str(): Tst(cl_var: 15, ins_var: 299)
obj.str.class type: <class 'type'>
obj.str.class.name: method
obj.__str__ has code: True
Поскольку __repr__
не определен в классе, то по умолчанию используется __repr__
на базовый класс object
через method-wrapper
и дает выход по умолчанию. __str__
определен (и, следовательно, метод), поэтому тест hasattr(t5.__str__, '__code__')
дает значение True.