Есть ли в Python способ переопределить метод класса на уровне экземпляра? Например:
class Dog:
def bark(self):
print "WOOF"
boby = Dog()
boby.bark() # WOOF
# METHOD OVERRIDE
boby.bark() # WoOoOoF!!






Да, это возможно:
class Dog:
def bark(self):
print "Woof"
def new_bark(self):
print "Woof Woof"
foo = Dog()
funcType = type(Dog.bark)
# "Woof"
foo.bark()
# replace bark with new_bark for this object only
foo.bark = funcType(new_bark, foo, Dog)
foo.bark()
# "Woof Woof"
Пожалуйста, объясните, что это делает, особенно что делает funcType и почему это необходимо.
Я думаю, что это можно сделать немного проще и яснее, используя funcType = types.MethodType (после импорта types) вместо funcType = type(Dog.bark).
Я предполагаю, что это не работает с Python 3. Я получаю ошибку «TypeError: функция () аргумент 1 должен быть кодом, а не функцией». Есть предложения по Python 3?
Вы также можете вызвать __get__ для функции, чтобы привязать ее к экземпляру.
@Sait Решение Mad Physicist с использованием __get__ также работает в Python 3.
Это почти то, что мне действительно нужно в моей ситуации, но я также хотел бы ничего не трогать и не менять в экземпляре, который я отменяю. Возможно ли это каким-либо образом?
@ 1313e да, можно сделать переопределение местный: tmp=foo.bark; foo.bark=funcType(new_bark, foo, Dog); foo.bark(); foo.bark=tmp
class Dog:
def bark(self):
print "WOOF"
boby = Dog()
boby.bark() # WOOF
# METHOD OVERRIDE
def new_bark():
print "WoOoOoF!!"
boby.bark = new_bark
boby.bark() # WoOoOoF!!
При необходимости вы можете использовать переменную boby внутри функции. Поскольку вы переопределяете метод только для этого объекта-экземпляра, этот способ проще и имеет тот же эффект, что и использование self.
IMHO использование исходной подписи добавляет удобочитаемости, особенно если функция определена в другом месте кода, а не рядом с экземпляром. Исключением может быть случай, когда метод переопределения также используется независимо как функция. Конечно, в этом простом примере это не имеет значения.
Я не понимаю, почему это не принятый ответ. Он называется patching, и это правильный способ сделать это (например, boby = Dog() и boby.bark = new_bark). Это невероятно полезно в модуле тестирование для элементов управления. Для получения дополнительных объяснений см. tryolabs.com/blog/2013/07/05/run-time-method-patching-python (примеры) - нет, я не связан с сайтом, на который указывает ссылка, или с автором.
Метод new_bark не имеет доступа к self (instance), поэтому пользователь не может получить доступ к свойствам экземпляра в new_bark. Вместо этого нужно использовать MethodType из модуля типов (см. Мой ответ ниже).
Поскольку функции являются объектами первого класса в Python, вы можете передать их при инициализации объекта класса или переопределить его в любое время для данного экземпляра класса:
class Dog:
def __init__(self, barkmethod=None):
self.bark=self.barkp
if barkmethod:
self.bark=barkmethod
def barkp(self):
print "woof"
d=Dog()
print "calling original bark"
d.bark()
def barknew():
print "wooOOOoof"
d1=Dog(barknew)
print "calling the new bark"
d1.bark()
def barknew1():
print "nowoof"
d1.bark=barknew1
print "calling another new"
d1.bark()
и результаты
calling original bark
woof
calling the new bark
wooOOOoof
calling another new
nowoof
Пожалуйста, не делайте этого, как показано. Ваш код становится нечитаемым, когда вы обезьяны исправляете экземпляр, чтобы он отличался от класса.
Вы не можете отлаживать код, исправленный обезьяной.
Когда вы найдете ошибку в boby и print type(boby), вы увидите, что (а) это собака, но (б) по какой-то непонятной причине она не лает правильно. Это кошмар. Не делай этого.
Пожалуйста, сделайте это вместо этого.
class Dog:
def bark(self):
print "WOOF"
class BobyDog( Dog ):
def bark( self ):
print "WoOoOoF!!"
otherDog= Dog()
otherDog.bark() # WOOF
boby = BobyDog()
boby.bark() # WoOoOoF!!
@arivero: Я думал, что "Пожалуйста, не делайте этого, как показано" ясно это проясняет. Какие еще или другие слова вы хотели бы увидеть, чтобы прояснить, что это не ответ на заданный вопрос, а совет о том, почему это плохая идея?
Я не возражаю ни в совете, ни в ОП, как кажется. Но я предполагаю, что у людей есть причины спрашивать. Или, даже если OP не сделал этого, другие будущие посетители могли бы. Так что, ИМХО, лучше ответ плюс выговор, чем просто выговор.
@arivero: Это не ответ на мой вопрос.
@ S.Lott Я думаю, вы должны связать «показанный» с фактическим ответом, у меня действительно нет проблем с тем, что это принятый ответ, но есть причины, по которым вам нужно патч обезьяны в некоторых обстоятельствах, и из моего беглого чтения Я решил, что «как показано» означает то, что вы показываете, а не другой ответ.
Хорошо, ради аргумента: допустим, у вас есть двадцать тысяч экземпляров какого-то класса, на создание которых ушла вся ночь. Теперь вы отредактировали один из методов базового класса. Вы хотите использовать новый метод, но не хотите ждать, чтобы снова создать все эти объекты. Я не уверен, что в данном случае потребуется создание подклассов.
Создание подклассов - гораздо более сильный контракт, чем метод обезьяньего исправления. По возможности сильные контракты предотвращают неожиданное поведение. Но в некоторых случаях желателен более слабый контракт. В таких ситуациях я бы предпочел использовать обратный вызов - вместо обезьяньего исправления - потому что, позволяя настраивать какое-то поведение, контракт класса остается не нарушенным. Ваш ответ, хотя и практический совет, на этот вопрос довольно плох, и ваш стиль кодирования непоследователен.
Модуль MethodType from types решает эту проблему (см. Мой ответ ниже).
Переопределение на уровне экземпляра очень полезно, когда вы хотите, чтобы конкретный экземпляр вел себя по-другому. Нет необходимости писать подклассы и т. д. Это неприемлемое решение. Многие другие решения ниже.
Никогда не говори никогда. Вероятно, всегда есть веская причина сделать то, о чем спрашивает спрашивающий.
Вы сказали: «Вы не можете отлаживать код, исправленный обезьянами». Eclipse и PyDev прекрасно умеют его отлаживать.
Это вообще не отвечает на вопрос. Это показывает, как переопределить унаследованный метод на уровне КЛАССА. OP запрашивает переопределение метода на уровне INSTANCE (когда класс уже инициализирован). В настоящее время я нахожусь в той же ситуации, когда хочу сделать именно это, поскольку переопределение унаследованных методов на уровне класса не является вариантом.
Хотя мне понравилась идея наследования от С. Лотта, и я согласен с «типом (а)», но поскольку функции тоже имеют доступные атрибуты, я думаю, что этим можно управлять следующим образом:
class Dog:
def __init__(self, barkmethod=None):
self.bark=self.barkp
if barkmethod:
self.bark=barkmethod
def barkp(self):
"""original bark"""
print "woof"
d=Dog()
print "calling original bark"
d.bark()
print "that was %s\n" % d.bark.__doc__
def barknew():
"""a new type of bark"""
print "wooOOOoof"
d1=Dog(barknew)
print "calling the new bark"
d1.bark()
print "that was %s\n" % d1.bark.__doc__
def barknew1():
"""another type of new bark"""
print "nowoof"
d1.bark=barknew1
print "another new"
d1.bark()
print "that was %s\n" % d1.bark.__doc__
и вывод:
calling original bark
woof
that was original bark
calling the new bark
wooOOOoof
that was a new type of bark
another new
nowoof
that was another type of new bark
Если это нужно «управлять», то - по мне - что-то не так. Особенно, когда есть первоклассная языковая функция, которая уже выполняет свою работу.
Уважаемый, это не отменяет, вы просто дважды вызываете одну и ту же функцию с объектом. В основном переопределение относится к более чем одному классу. когда один и тот же метод подписи существует в разных классах, то какая функция вы вызываете this, решает объект, который это вызывает. Переопределение возможно в python, когда вы создаете несколько классов, которые записывают одни и те же функции и еще кое-что, чтобы поделиться тем, что перегрузка не разрешена в python
Вам нужно использовать MethodType из модуля types. Целью MethodType является перезапись методов уровня экземпляра (чтобы self мог быть доступен в перезаписываемых методах).
См. Пример ниже.
import types
class Dog:
def bark(self):
print "WOOF"
boby = Dog()
boby.bark() # WOOF
def _bark(self):
print "WoOoOoF!!"
boby.bark = types.MethodType(_bark, boby)
boby.bark() # WoOoOoF!!
при использовании Python-ввода mypy вызывает ошибку Cannot assign to a methodсм. связанную проблему. на данный момент можно использовать setattr(boby, "bar", types.MethodType(_bark, boby).
Я нашел, что это наиболее точный ответ на исходный вопрос.
https://stackoverflow.com/a/10829381/7640677
import a
def _new_print_message(message):
print "NEW:", message
a.print_message = _new_print_message
import b
b.execute()
Чтобы объяснить отличный ответ @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 = new_bark.__get__(foo, Dog)
foo.bark()
# "Woof Woof"
Назначив связанный метод атрибуту экземпляра, вы создали почти полную симуляцию переопределения метода. Одна удобная функция, которой не хватает, - это доступ к версии super без аргументов, поскольку вы не находитесь в определении класса. Другое дело, что атрибут __name__ вашего связанного метода не будет принимать имя функции, которую он переопределяет, как это было бы в определении класса, но вы все равно можете установить его вручную. Третье отличие состоит в том, что привязанный вручную метод представляет собой простую ссылку на атрибут, которая оказывается функцией. Оператор . ничего не делает, кроме получения этой ссылки. С другой стороны, при вызове обычного метода из экземпляра процесс привязки каждый раз создает новый связанный метод.
Кстати, единственная причина, по которой это работает, заключается в том, что атрибуты экземпляра переопределяют дескрипторы не данные. В дескрипторах данных есть методы __set__, а в методах (к счастью для вас) их нет. Дескрипторы данных в классе фактически имеют приоритет над любыми атрибутами экземпляра. Вот почему вы можете назначить свойство: их метод __set__ вызывается, когда вы пытаетесь сделать назначение. Мне лично нравится идти дальше и скрывать фактическое значение базового атрибута в __dict__ экземпляра, где оно недоступно обычными средствами именно потому, что свойство затеняет его.
Также следует помнить, что для магические (двойное подчеркивание) методы это бессмысленно. Конечно, таким образом можно переопределить магические методы, но операции, которые их используют, смотрят только на тип. Например, вы можете установить для __contains__ что-то особенное в своем экземпляре, но при вызове x in instance это проигнорирует и вместо этого будет использоваться type(instance).__contains__(instance, x). Это относится ко всем магическим методам, указанным в Python модель данных.
Это должен быть принятый ответ: он чистый и работает на Python 3.
@BlenderBender. Я ценю вашу поддержку
В чем разница между этим ответом и ответом @Harshal Dhumai над вашим?
@ 1313. Функционально особой разницы быть не должно. Я подозреваю, что приведенный выше ответ может принимать в качестве входных данных более широкий диапазон вызываемых, но я не уверен, не читая документы и не играясь.
Документы Python 2 для этого: docs.python.org/2/howto/descriptor.html#functions-and-method s. Итак, теоретически это то же самое, что и ответ @Harshal Dhumai.
Будьте осторожны, когда вам нужно вызвать старый метод внутри нового метода:
import types
class Dog:
def bark(self):
print("WOOF")
boby = Dog()
boby.bark() # WOOF
def _bark(self):
self.bark()
print("WoOoOoF!!")
boby.bark = types.MethodType(_bark, boby)
boby.bark() # Process finished with exit code -1073741571 (0xC00000FD) [stack overflow]
# This also happens with the '__get__' solution
В этих ситуациях вы мог используете следующее:
def _bark(self):
Dog.bark(self)
print( "WoOoOoF!!") # Calls without error
Но что, если кто-то еще в библиотеке уже переопределил метод foobark? Тогда Dog.bark(foo) - это не то же самое, что foo.bark! По моему опыту, самое простое решение, которое работает в обоих случаях, - это
# Save the previous definition before overriding
old_bark = foo.bark
def _bark(self):
old_bark()
print("WoOoOoF!!")
foo.bark = _bark
# Works for instance-overridden methods, too
В большинстве случаев создание подклассов и использование super - правильный способ справиться с этой ситуацией. Однако бывают случаи, когда такое исправление обезьяны необходимо, и оно завершится ошибкой переполнения стека, если вы не проявите немного большей осторожности.
Небольшой комментарий, когда вы выполняете funcType (new_bark, foo, Dog), он добавляет имя == bark и метод экземпляра Dog.new_bark в foo .__ dict__, верно? Итак, когда вы снова вызываете, он сначала ищет в словаре экземпляра и вызывает это,