Переопределить метод на уровне экземпляра

87

Есть ли способ в Python переопределить метод класса на уровне экземпляра? Например:

class Dog:
    def bark(self):
        print "WOOF"

boby = Dog()
boby.bark() # WOOF
# METHOD OVERRIDE
boby.bark() # WoOoOoF!!
фистаккио
источник

Ответы:

11

Пожалуйста, не делайте этого, как показано. Ваш код становится нечитаемым, когда вы изменяете экземпляр, чтобы он отличался от класса.

Вы не можете отлаживать код, исправленный обезьяной.

Когда вы найдете ошибку в 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!!
С.Лотт
источник
9
@arivero: Я думал, что "Пожалуйста, не делайте этого, как показано" ясно это проясняет. Какие еще или другие слова вы хотели бы увидеть, чтобы прояснить, что это не ответ на заданный вопрос, а совет, почему это плохая идея?
S.Lott
45
Я не возражаю ни в совете, ни в ОП, как кажется. Но я предполагаю, что у людей есть причины спрашивать. Или, даже если OP не сделал этого, другие будущие посетители могли бы. Так что, ИМХО, лучше ответ плюс выговор, чем просто выговор.
arivero
13
@arivero: Это не ответ на мой вопрос.
S.Lott
9
@ S.Lott Я думаю, вы должны связать «показанный» с фактическим ответом, у меня действительно нет проблем с тем, что это принятый ответ, но есть причины, по которым вам нужно патч обезьяны в некоторых обстоятельствах, и из моего беглого чтения Я решил, что «как показано» означает то, что вы показываете, а не другой ответ.
Дэниел Чатфилд,
4
Создание подклассов - намного более сильный контракт, чем метод обезьяньего исправления. По возможности сильные контракты предотвращают неожиданное поведение. Но в некоторых случаях желателен более слабый контракт. В таких ситуациях я бы предпочел использовать обратный вызов - вместо обезьяньего исправления - потому что, позволяя настраивать какое-то поведение, контракт класса остается не нарушенным. Ваш ответ, хотя и практический совет, на этот вопрос довольно плох, и ваш стиль кодирования непоследователен.
Aaron3468
167

Да, это возможно:

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"
codelogic
источник
1
Небольшой комментарий, когда вы выполняете funcType (new_bark, foo, Dog), он добавляет имя == bark и метод экземпляра Dog.new_bark в foo .__ dict__, верно? Итак, когда вы снова вызываете, он сначала ищет в словаре экземпляра и вызывает его,
Джеймс
11
Пожалуйста, объясните, что это делает, особенно что funcTypeделает и почему это необходимо.
Александр Дубинский,
3
Я думаю, что это можно сделать немного проще и яснее, используя funcType = types.MethodType(после импорта types) вместо funcType = type(Dog.bark).
Элиас Замария
13
Я предполагаю, что это не работает с Python 3. Я получаю ошибку «TypeError: функция () аргумент 1 должен быть кодом, а не функцией». Есть предложения по Python 3?
Sait
2
Вы также можете вызвать __get__функцию, чтобы привязать ее к экземпляру.
Безумный физик
38

Вам нужно использовать 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!!
Харшал Дхумал
источник
32

Чтобы объяснить отличный ответ @codelogic, я предлагаю более явный подход. Это тот же метод, с которым .оператор тщательно связывает метод класса, когда вы обращаетесь к нему как к атрибуту экземпляра, за исключением того, что ваш метод фактически будет функцией, определенной вне класса.

При работе с кодом @codelogic разница только в том, как привязан метод. Я использую тот факт, что функции и методы в Python не являются дескрипторами данных , и вызываю __get__метод. Обратите особое внимание на то, что и оригинал, и замена имеют идентичные подписи, что означает, что вы можете записать замену как метод полного класса, получив доступ ко всем атрибутам экземпляра через self.

класс Dog:
    def кора (сам):
        печать "Гав"

def new_bark (сам):
    принт "Woof Woof"

foo = Собака ()

# «Гав»
foo.bark ()

# замените bark на new_bark только для этого объекта
foo.bark = new_bark .__ получить __ (фу, собака)

foo.bark ()
# "Гав гав"

Назначив связанный метод атрибуту экземпляра, вы создали почти полную симуляцию переопределения метода. Одна удобная функция, которой не хватает, - это доступ к версии без аргументов super, поскольку вы не находитесь в определении класса. Другое дело, что __name__атрибут вашего связанного метода не будет принимать имя функции, которую он переопределяет, как это было бы в определении класса, но вы все равно можете установить его вручную. Третье отличие заключается в том, что привязанный вручную метод представляет собой простую ссылку на атрибут, которая оказывается функцией. .Оператор ничего не делает , но извлечь эту ссылку. С другой стороны, при вызове обычного метода из экземпляра процесс привязки каждый раз создает новый связанный метод.

Между прочим, единственная причина, по которой это работает, заключается в том, что атрибуты экземпляра переопределяют дескрипторы, не относящиеся к данным . У дескрипторов данных есть __set__методы, которых нет (к счастью для вас). Дескрипторы данных в классе фактически имеют приоритет над любыми атрибутами экземпляра. Вот почему вы можете назначить свойству: их __set__метод вызывается, когда вы пытаетесь выполнить назначение. Я лично предпочитаю пойти дальше и скрыть фактическое значение базового атрибута в экземпляре __dict__, где он недоступен обычными средствами именно потому, что свойство затеняет его.

Также следует иметь в виду, что для магических (двойное подчеркивание) методов это бессмысленно . Конечно, таким образом можно переопределить магические методы, но операции, которые их используют, смотрят только на тип. Например, вы можете установить __contains__что-то особенное в своем экземпляре, но при вызове x in instanceэто не учитывается и type(instance).__contains__(instance, x)вместо этого используется . Это относится ко всем магическим методам, указанным в модели данных Python .

Безумный физик
источник
1
Это должен быть принятый ответ: он чистый и работает на Python 3.
BlenderBender
@BlenderBender. Я ценю вашу поддержку
Безумный физик
В чем разница между этим ответом и ответом @Harshal Dhumai над вашим?
1313e
1
@ 1313. Функционально особой разницы быть не должно. Я подозреваю, что приведенный выше ответ может принимать в качестве входных данных более широкий диапазон вызываемых, но я не уверен, не читая документы и не играясь.
Безумный физик
Документы Python 2 для этого: docs.python.org/2/howto/descriptor.html#functions-and-methods . Итак, теоретически это то же самое, что и ответ @Harshal Dhumai.
Rockallite
26
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.

носкло
источник
1
ИМХО, использование исходной подписи добавляет удобочитаемости, особенно если функция определена в другом месте кода, а не рядом с экземпляром. Исключение составляет случай, когда метод переопределения также используется независимо как функция. Конечно, в этом простом примере это не имеет значения.
codelogic
1
Я не понимаю, почему это не принятый ответ. Он вызывается, patchingи это правильный способ сделать это (например, boby = Dog()и boby.bark = new_bark). Это невероятно полезно при модульном тестировании элементов управления. Для получения дополнительных объяснений см. Tryolabs.com/blog/2013/07/05/run-time-method-patching-python (примеры) - нет, я не связан с сайтом или автором, на который указывает ссылка .
Джефф
5
Метод new_bark не имеет доступа к self (instance), поэтому пользователь не может получить доступ к свойствам экземпляра в new_bark. Вместо этого нужно использовать MethodType из модуля типов (см. Мой ответ ниже).
Harshal Dhumal
3

Поскольку здесь никто не упоминает functools.partial:

from functools import partial

class Dog:
    name = "aaa"
    def bark(self):
        print("WOOF")

boby = Dog()
boby.bark() # WOOF

def _bark(self):
    print("WoOoOoF!!")

boby.bark = partial(_bark, boby)
boby.bark() # WoOoOoF!!
user1285245
источник
1

Поскольку функции являются объектами первого класса в 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
СП.
источник
-4

Хотя мне понравилась идея наследования от С. Лотта, и я согласен с «типом (а)», но поскольку функции тоже имеют доступные атрибуты, я думаю, что этим можно управлять следующим образом:

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
СП.
источник
2
Если это нужно «управлять», то - по мне - что-то не так. Особенно, когда есть первоклассная языковая функция, которая уже выполняет свою работу.
S.Lott
-4

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

нагимсит
источник
-4

Я нашел, что это наиболее точный ответ на исходный вопрос

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()
Рувалькаба
источник