Переопределить метод на уровне экземпляра - PullRequest
71 голосов
/ 27 декабря 2008

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

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

boby = Dog()
boby.bark() # WOOF
# METHOD OVERRIDE
boby.bark() # WoOoOoF!!

Ответы [ 10 ]

142 голосов
/ 27 декабря 2008

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

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"
27 голосов
/ 10 февраля 2017

Вам нужно использовать MethodType из модуля типов. Назначение 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!!
25 голосов
/ 27 декабря 2008
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.

18 голосов
/ 15 октября 2017

Чтобы объяснить отличный ответ @ 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 .

14 голосов
/ 27 декабря 2008

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

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

Когда вы найдете ошибку в 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!!
1 голос
/ 13 февраля 2019

Так как никто не упоминает functool.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!!
1 голос
/ 27 декабря 2008

Поскольку функции в 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
0 голосов
/ 01 марта 2017

Я нашел, что это самый точный ответ на оригинальный вопрос

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()
0 голосов
/ 07 июня 2013

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

0 голосов
/ 27 декабря 2008

Хотя мне понравилась идея наследования от S. Lott и я согласен с понятием type (a), но поскольку функции тоже имеют доступные атрибуты, я думаю, что ими можно управлять следующим образом:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...