Чтобы объяснить отличный ответ @ codelogic, я предлагаю более явный подход. Это та же самая техника, в которой оператор .
тщательно привязывает метод класса, когда вы обращаетесь к нему как к атрибуту экземпляра, за исключением того, что ваш метод на самом деле будет функцией, определенной вне класса.
Работая с кодом @ codelogic, единственное различие заключается в том, как метод связан. Я использую тот факт, что функции и методы не являются данными дескрипторы в Python, и вызываю метод __get__
. Обратите особое внимание, что и оригинал, и замена имеют одинаковые подписи, что означает, что вы можете написать замену как метод полного класса, получая доступ ко всем атрибутам экземпляра через self
.
class Dog:
def bark(self):
print "Woof"
def new_bark(self):
print "Woof Woof"
foo = Dog()
# "Woof"
foo.bark()
# replace bark with new_bark for this object only
foo.bark = <b>new_bark.__get__(foo, Dog)</b>
foo.bark()
# "Woof Woof"
Назначая связанный метод атрибуту экземпляра, вы создали почти полную симуляцию переопределения метода. Одной удобной функцией, которая отсутствует, является доступ к безаргитной версии super
, поскольку вы не находитесь в определении класса. Другое дело, что атрибут __name__
вашего связанного метода не будет принимать имя переопределяемой функции, как в определении класса, но вы все равно можете установить его вручную. Третье отличие состоит в том, что ваш метод, привязанный вручную, представляет собой простую ссылку на атрибут, которая просто является функцией. Оператор .
ничего не делает, кроме как извлекает эту ссылку. С другой стороны, при вызове обычного метода из экземпляра процесс привязки каждый раз создает новый связанный метод.
Кстати, единственная причина, по которой это работает, заключается в том, что атрибуты экземпляра переопределяют дескрипторы не-данные . У дескрипторов данных есть методы __set__
, которых нет (к счастью для вас). Дескрипторы данных в классе на самом деле имеют приоритет над любыми атрибутами экземпляра. Вот почему вы можете присвоить свойству: их метод __set__
вызывается, когда вы пытаетесь выполнить назначение. Я лично хотел бы пойти дальше и скрыть фактическое значение базового атрибута в экземпляре __dict__
, где он недоступен обычными средствами именно потому, что свойство затеняет его.
Также следует помнить, что это бессмысленно для магических методов (двойное подчеркивание) . Магические методы, конечно, могут быть переопределены таким образом, но операции, которые их используют, смотрят только на тип. Например, вы можете установить для __contains__
что-то особенное в вашем экземпляре, но вызов x in instance
игнорирует это и будет использовать type(instance).__contains__(instance, x)
. Это относится ко всем магическим методам, указанным в модели данных Python .