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

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

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

boby = Dog()
boby.bark() # WOOF
# METHOD OVERRIDE
boby.bark() # WoOoOoF!!
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
91
0
62 161
11
Перейти к ответу Данный вопрос помечен как решенный

Ответы 11

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

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 (new_bark, foo, Dog), он добавляет имя == bark и метод экземпляра Dog.new_bark в foo .__ dict__, верно? Итак, когда вы снова вызываете, он сначала ищет в словаре экземпляра и вызывает это,

James 29.11.2013 06:23

Пожалуйста, объясните, что это делает, особенно что делает funcType и почему это необходимо.

Aleksandr Dubinsky 13.07.2016 07:30

Я думаю, что это можно сделать немного проще и яснее, используя funcType = types.MethodType (после импорта types) вместо funcType = type(Dog.bark).

Elias Zamaria 06.11.2016 02:28

Я предполагаю, что это не работает с Python 3. Я получаю ошибку «TypeError: функция () аргумент 1 должен быть кодом, а не функцией». Есть предложения по Python 3?

Sait 16.12.2016 18:36

Вы также можете вызвать __get__ для функции, чтобы привязать ее к экземпляру.

Mad Physicist 15.10.2017 18:23

@Sait Решение Mad Physicist с использованием __get__ также работает в Python 3.

BlenderBender 14.05.2018 17:42

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

1313e 01.05.2019 09:05

@ 1313e да, можно сделать переопределение местный: tmp=foo.bark; foo.bark=funcType(new_bark, foo, Dog); foo.bark(); foo.bark=tmp

Alex Cohn 15.01.2020 16:47
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 использование исходной подписи добавляет удобочитаемости, особенно если функция определена в другом месте кода, а не рядом с экземпляром. Исключением может быть случай, когда метод переопределения также используется независимо как функция. Конечно, в этом простом примере это не имеет значения.

codelogic 27.12.2008 11:15

Я не понимаю, почему это не принятый ответ. Он называется patching, и это правильный способ сделать это (например, boby = Dog() и boby.bark = new_bark). Это невероятно полезно в модуле тестирование для элементов управления. Для получения дополнительных объяснений см. tryolabs.com/blog/2013/07/05/run-time-method-patching-python (примеры) - нет, я не связан с сайтом, на который указывает ссылка, или с автором.

Geoff 21.09.2016 00:45

Метод new_bark не имеет доступа к self (instance), поэтому пользователь не может получить доступ к свойствам экземпляра в new_bark. Вместо этого нужно использовать MethodType из модуля типов (см. Мой ответ ниже).

Harshal Dhumal 10.02.2017 10:50

Поскольку функции являются объектами первого класса в 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: Я думал, что "Пожалуйста, не делайте этого, как показано" ясно это проясняет. Какие еще или другие слова вы хотели бы увидеть, чтобы прояснить, что это не ответ на заданный вопрос, а совет о том, почему это плохая идея?

S.Lott 27.01.2012 23:39

Я не возражаю ни в совете, ни в ОП, как кажется. Но я предполагаю, что у людей есть причины спрашивать. Или, даже если OP не сделал этого, другие будущие посетители могли бы. Так что, ИМХО, лучше ответ плюс выговор, чем просто выговор.

arivero 29.01.2012 02:17

@arivero: Это не ответ на мой вопрос.

S.Lott 29.01.2012 04:18

@ S.Lott Я думаю, вы должны связать «показанный» с фактическим ответом, у меня действительно нет проблем с тем, что это принятый ответ, но есть причины, по которым вам нужно патч обезьяны в некоторых обстоятельствах, и из моего беглого чтения Я решил, что «как показано» означает то, что вы показываете, а не другой ответ.

Daniel Chatfield 06.09.2013 20:05

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

Darren Ringer 11.08.2016 18:31

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

Aaron3468 06.09.2016 14:51

Модуль MethodType from types решает эту проблему (см. Мой ответ ниже).

Harshal Dhumal 10.02.2017 10:56

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

giwyni 21.07.2018 21:32

Никогда не говори никогда. Вероятно, всегда есть веская причина сделать то, о чем спрашивает спрашивающий.

shrewmouse 07.09.2018 20:44

Вы сказали: «Вы не можете отлаживать код, исправленный обезьянами». Eclipse и PyDev прекрасно умеют его отлаживать.

shrewmouse 24.09.2018 21:36

Это вообще не отвечает на вопрос. Это показывает, как переопределить унаследованный метод на уровне КЛАССА. OP запрашивает переопределение метода на уровне INSTANCE (когда класс уже инициализирован). В настоящее время я нахожусь в той же ситуации, когда хочу сделать именно это, поскольку переопределение унаследованных методов на уровне класса не является вариантом.

1313e 01.05.2019 09:00

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

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

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

S.Lott 27.12.2008 17:37

Уважаемый, это не отменяет, вы просто дважды вызываете одну и ту же функцию с объектом. В основном переопределение относится к более чем одному классу. когда один и тот же метод подписи существует в разных классах, то какая функция вы вызываете 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).

jakub 21.01.2021 15:40

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

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 14.05.2018 17:43

@BlenderBender. Я ценю вашу поддержку

Mad Physicist 14.05.2018 17:49

В чем разница между этим ответом и ответом @Harshal Dhumai над вашим?

1313e 01.05.2019 09:22

@ 1313. Функционально особой разницы быть не должно. Я подозреваю, что приведенный выше ответ может принимать в качестве входных данных более широкий диапазон вызываемых, но я не уверен, не читая документы и не играясь.

Mad Physicist 01.05.2019 13:34

Документы Python 2 для этого: docs.python.org/2/howto/descriptor.html#functions-and-method‌ s. Итак, теоретически это то же самое, что и ответ @Harshal Dhumai.

Rockallite 14.05.2019 05:24

Поскольку здесь никто не упоминает 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!!

Будьте осторожны, когда вам нужно вызвать старый метод внутри нового метода:

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 - правильный способ справиться с этой ситуацией. Однако бывают случаи, когда такое исправление обезьяны необходимо, и оно завершится ошибкой переполнения стека, если вы не проявите немного большей осторожности.

Другие вопросы по теме