Подтвердить что ты не робот

Переопределение специальных методов в экземпляре

Я надеюсь, что кто-то может ответить на это, у которого есть хорошее понимание Python:)

Рассмотрим следующий код:

>>> class A(object):
...     pass
...
>>> def __repr__(self):
...     return "A"
...
>>> from types import MethodType
>>> a = A()
>>> a
<__main__.A object at 0x00AC6990>
>>> repr(a)
'<__main__.A object at 0x00AC6990>'
>>> setattr(a, "__repr__", MethodType(__repr__, a, a.__class__))
>>> a
<__main__.A object at 0x00AC6990>
>>> repr(a)
'<__main__.A object at 0x00AC6990>'
>>>

Обратите внимание, что выражение (а) не дает ожидаемого результата "А"? Я хочу знать, почему это так, и если есть способ достичь этого...

Я контрастирую, следующий пример работает (возможно, потому что мы не пытаемся переопределить специальный метод?):

>>> class A(object):
...     def foo(self):
...             return "foo"
...
>>> def bar(self):
...     return "bar"
...
>>> from types import MethodType
>>> a = A()
>>> a.foo()
'foo'
>>> setattr(a, "foo", MethodType(bar, a, a.__class__))
>>> a.foo()
'bar'
>>>
4b9b3361

Ответ 1

Python не вызывает специальные методы с именами, окруженными __ экземпляром, но только для класса, по-видимому, для повышения производительности. Поэтому нет способа переопределить __repr__() непосредственно на экземпляр и заставить его работать. Вместо этого вам нужно сделать что-то вроде этого:

class A(object):
    def __repr__(self):
        return self._repr()
    def _repr(self):
        return object.__repr__(self)

Теперь вы можете переопределить __repr__() в экземпляре, заменив _repr().

Ответ 2

Как объясняется в Специальный поиск метода:

Для пользовательских классов гарантируется, что неявные вызовы специальных методов будут корректно работать, если они определены в типе объектов, а не в словаре экземпляров объектов... Помимо обхода любых атрибутов экземпляра в интересах корректности, неявный поиск специальных методов в целом также обходит метод __getattribute__() даже метакласса объектов

(Часть, которую я вырезал, объясняет обоснование этого, если вы заинтересованы в этом.)

Python не документирует точно, когда реализация должна или не должна искать метод в типе; на самом деле все документы, которые, по сути, реализуют, могут или не могут смотреть на экземпляр для специальных поисков методов, поэтому вы тоже не должны рассчитывать.

Как вы можете догадаться из результатов теста, в реализации CPython __repr__ является одной из функций, которые искали тип.


В 2.x ситуация немного отличается, в основном из-за наличия классических классов, но пока вы создаете только классы нового стиля, вы можете думать о них как о том же.


Наиболее распространенная причина, по которой люди хотят это сделать, - это обезьяна-патч для разных экземпляров объекта, чтобы делать разные вещи. Вы не можете сделать это с помощью специальных методов, так что... что вы можете сделать? Там есть чистое решение и хакерское решение.

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

class C(object):
    def __repr__(self):
        return getattr(self, '_repr')()
    def _repr(self):
        return 'Boring: {}'.format(object.__repr__(self))

c = C()
def c_repr(self):
    return "It's-a me, c_repr: {}".format(object.__repr__(self))
c._repr = c_repr.__get__(c)

Хакерное решение - создать новый подкласс "на лету" и переклассифицировать объект. Я подозреваю, что кто-то, у кого действительно есть ситуация, когда это хорошая идея, будет знать, как его реализовать из этого предложения, и любой, кто не знает, как это сделать, не должен пытаться, поэтому я оставлю это на этом.

Ответ 3

Причиной этого являются специальные методы (__x__()) для класса, а не для экземпляра.

Это имеет смысл, если вы думаете о __new__() - было бы невозможно вызвать это на экземпляре, поскольку экземпляр не существует, когда он вызывал.

Итак, вы можете сделать это в классе в целом, если хотите:

>>> A.__repr__ = __repr__
>>> a
A

Или в отдельном экземпляре, как в ответе kindall. (Обратите внимание, что здесь есть много сходства, но я думал, что мои примеры добавили достаточно, чтобы опубликовать это также).

Ответ 4

Для новых классов стилей Python использует специальный метод поиска, который обходит экземпляры. Здесь выдержка из источника:

  1164 /* Internal routines to do a method lookup in the type
  1165    without looking in the instance dictionary
  1166    (so we can't use PyObject_GetAttr) but still binding
  1167    it to the instance.  The arguments are the object,
  1168    the method name as a C string, and the address of a
  1169    static variable used to cache the interned Python string.
  1170 
  1171    Two variants:
  1172 
  1173    - lookup_maybe() returns NULL without raising an exception
  1174      when the _PyType_Lookup() call fails;
  1175 
  1176    - lookup_method() always raises an exception upon errors.
  1177 
  1178    - _PyObject_LookupSpecial() exported for the benefit of other places.
  1179 */

Вы можете либо перейти к классу старого стиля (не наследовать от объекта), либо добавить методы диспетчера в класс (методы, которые пересылают запросы обратно в экземпляр). Пример примера методов диспетчера см. В рецепте http://code.activestate.com/recipes/578091

Ответ 5

TLDR: невозможно определить правильные, несвязанные методы в экземплярах; это относится и к специальным методам. Поскольку связанные методы являются первоклассными объектами, при определенных обстоятельствах различие не заметно. Тем не менее, специальные методы всегда рассматриваются Python как надлежащие, несвязанные методы, когда это необходимо.

Вы всегда можете вручную обратиться к специальному методу, который использует более общий доступ к атрибутам. Доступ к атрибутам охватывает как связанные методы, хранящиеся в виде атрибутов, так и несвязанные методы, связанные по мере необходимости. Это похоже на то, как __repr__ или другие методы будут использовать атрибуты для определения своего вывода.

class A:
    def __init__(self, name):
        self.name = name

    def __repr__(self):
        # call attribute to derive __repr__
        return self.__representation__()

    def __representation__(self):
        return f'{self.__class__.__name__}({self.name})'

    def __str__(self):
        # return attribute to derive __str__
        return self.name

Несвязанные и связанные методы

У метода в Python есть два значения: несвязанные методы класса и связанные методы экземпляра этого класса.

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

>>> class Foo:
...     def bar(self): print('bar on', self)
...
>>> Foo.bar
<function __main__.Foo.bar(self)>

Несвязанный метод существует только один раз в классе - он одинаков для всех экземпляров.

Связанный метод - это несвязанный метод, который был привязан к конкретному экземпляру. Обычно это означает, что метод был найден в экземпляре, который вызывает функцию __get__ method.

>>> foo = Foo()
>>> # lookup through instance
>>> foo.bar
<bound method Foo.bar of <__main__.Foo object at 0x10c3b6390>>
>>> # explicit descriptor invokation
>>> type(foo).bar.__get__(foo, type(Foo))
<bound method Foo.bar of <__main__.Foo object at 0x10c3b6390>>

Что касается Python, то "метод" обычно означает несвязанный метод, который связан с его экземпляром по мере необходимости. Когда Python нужен специальный метод, он напрямую вызывает протокол дескриптора для несвязанного метода. В результате метод ищется в классе; атрибут в экземпляре игнорируется.


Связанные методы на объектах

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

>>> foo.bar is foo.bar  # binding happens on every lookup
False
>>> foo_bar = foo.bar   # bound methods can be stored
>>> foo_bar()           # stored bound methods can be called later
bar on <__main__.Foo object at 0x10c3b6390>
>>> foo_bar()
bar on <__main__.Foo object at 0x10c3b6390>

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

>>> foo.qux = foo.bar
>>> foo.qux
<bound method Foo.bar of <__main__.Foo object at 0x10c3b6390>>
>>> foo.qux is foo.qux  # binding is not repeated on every lookup!
True
>>> too = Foo()
>>> too.qux = foo.qux   # bound methods can be stored on other instances!
>>> too.qux             # ...but are still bound to the original instance!
<bound method Foo.bar of <__main__.Foo object at 0x10c3b6390>>
>>> import builtins
>>> builtins.qux = foo.qux  # bound methods can be stored...
>>> qux                     # ... *anywhere* that supports attributes
<bound method Foo.bar of <__main__.Foo object at 0x10c3b6390>>

Что касается Python, связанные методы - это обычные, вызываемые объекты. Так же, как он не может узнать, является ли too.qux методом too, он также не может определить, является ли too.__repr__ методом.