Тип метода-метода 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.