Zastąp metodę na poziomie instancji

87

Czy w Pythonie jest sposób na przesłonięcie metody klasy na poziomie instancji? Na przykład:

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

boby = Dog()
boby.bark() # WOOF
# METHOD OVERRIDE
boby.bark() # WoOoOoF!!
pistacchio
źródło

Odpowiedzi:

10

Nie rób tego tak, jak pokazano. Twój kod staje się nieczytelny, gdy monkeyp patrzysz na instancję, która różni się od klasy.

Nie można debugować kodu z monkeypatchami.

Kiedy znajdziesz błąd w programie bobyi print type(boby), zobaczysz, że (a) jest to pies, ale (b) z jakiegoś niejasnego powodu nie szczeka poprawnie. To jest koszmar. Nie rób tego.

Zrób to zamiast tego.

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!!
S.Lott
źródło
9
@arivero: Pomyślałem, że „Proszę, nie rób tego tak, jak pokazano”, jasno to wyjaśnia. Jakie inne lub inne słowa chciałbyś zobaczyć, aby wyjaśnić, że to nie jest odpowiedź na zadane pytanie, ale zawiera porady, dlaczego jest to zły pomysł?
S.Lott
46
Nie zgadzam się z radą, ani z PO jak się wydaje. Ale zakładam, że ludzie mają powody, by pytać. A nawet jeśli OP tego nie zrobił, inni przyszli goście mogliby. Więc IMHO, odpowiedź plus nagana jest lepsza niż zwykła nagana.
arivero
13
@arivero: To nie jest odpowiedzią na moje pytanie.
S.Lott,
9
@ S.Lott Myślę, że powinieneś połączyć "pokazane" z rzeczywistą odpowiedzią, tak naprawdę nie mam problemu z tym, że jest to zaakceptowana odpowiedź, ale są powody, dla których musisz małpa łata w niektórych okolicznościach i z mojego czytania przeglądowego Uznałem, że „jak pokazano” oznacza to, co pokazałeś, a nie inną odpowiedź.
Daniel Chatfield,
4
Podklasy są znacznie silniejszym kontraktem niż metoda łatania małpy. Jeśli to możliwe, silne kontrakty zapobiegają nieoczekiwanym zachowaniom. Ale w niektórych przypadkach pożądany jest luźniejszy kontrakt. W takich sytuacjach wolałbym użyć wywołania zwrotnego - zamiast małpiego łatania - ponieważ zezwalając na dostosowywanie niektórych zachowań, kontrakt klasy pozostaje nienaruszony. Twoja odpowiedź, choć praktyczna rada, jest dość słaba na to pytanie, a Twój styl kodowania jest niespójny.
Aaron3468,
169

Tak, to możliwe:

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
źródło
1
Mały komentarz, kiedy wykonujesz funcType (new_bark, foo, Dog) dodajesz nazwę == bark i metodę instancji Dog.new_bark w foo .__ dict__, prawda? Tak więc, gdy wywołasz go ponownie, najpierw wyszukaj w słowniku instancji i wezwij go,
James,
11
Proszę wyjaśnić, co to robi, a zwłaszcza co funcTypei dlaczego jest konieczne.
Aleksandr Dubinsky
3
Myślę, że można to nieco uprościć i wyjaśnić, używając funcType = types.MethodType(po zaimportowaniu types) zamiast funcType = type(Dog.bark).
Elias Zamaria,
13
Myślę, że to nie działa w Pythonie 3. Otrzymuję błąd „TypeError: function () argument 1 must be code, not function”. Jakieś sugestie dotyczące Pythona 3?
Sait
2
Możesz również wywołać __get__funkcję, aby powiązać ją z instancją.
Mad Physicist
41

Musisz użyć MethodType z typesmodułu. Celem MethodTypejest nadpisanie metod na poziomie instancji (aby selfbyły dostępne w nadpisywanych metodach).

Zobacz poniższy przykład.

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!!
Harshal Dhumal
źródło
32

Aby wyjaśnić doskonałą odpowiedź @ codelogic, proponuję bardziej wyraźne podejście. Jest to ta sama technika, którą .operator robi dokładnie, aby powiązać metodę klasy, gdy uzyskujesz do niej dostęp jako atrybut instancji, z tym wyjątkiem, że Twoja metoda będzie w rzeczywistości funkcją zdefiniowaną poza klasą.

Pracując z kodem @ codelogic, jedyną różnicą jest sposób powiązania metody. Używam faktu, że funkcje i metody nie są deskryptorami danych w Pythonie i wywołuję tę __get__metodę. Zwróć szczególną uwagę, że zarówno oryginał, jak i zamiennik mają identyczne podpisy, co oznacza, że ​​możesz napisać zamiennik jako metodę pełnej klasy, uzyskując dostęp do wszystkich atrybutów instancji za pośrednictwem self.

klasa Pies:
    def bark (self):
        drukuj "Hau"

def new_bark (self):
    drukuj "Woof Woof"

foo = Pies ()

# "Hau"
foo.bark ()

# zamień kory na new_bark tylko dla tego obiektu
foo.bark = new_bark .__ get __ (foo, Dog)

foo.bark ()
# "Woof Woof"

Przypisując powiązaną metodę do atrybutu instancji, utworzyłeś niemal pełną symulację zastępowania metody. Jedną z przydatnych funkcji, której brakuje, jest dostęp do wersji bez argumentów super, ponieważ nie jesteś w definicji klasy. Inną rzeczą jest to, że __name__atrybut twojej metody związanej nie przyjmie nazwy funkcji, którą przesłania, jak w definicji klasy, ale nadal możesz ustawić ją ręcznie. Trzecia różnica polega na tym, że metoda powiązana ręcznie jest zwykłym odwołaniem do atrybutu, który po prostu jest funkcją. .Operator nic nie robi, ale pobrać to odwołanie. Z drugiej strony podczas wywoływania zwykłej metody z wystąpienia proces wiązania tworzy za każdym razem nową powiązaną metodę.

Nawiasem mówiąc, jedynym powodem, dla którego to działa, jest to, że atrybuty instancji zastępują deskryptory niebędące danymi . Deskryptory danych mają __set__metody, których (na szczęście dla Ciebie) nie. Deskryptory danych w klasie mają w rzeczywistości pierwszeństwo przed atrybutami instancji. Dlatego możesz przypisać do właściwości: ich __set__metoda jest wywoływana podczas próby przypisania. Osobiście lubię pójść o krok dalej i ukryć rzeczywistą wartość podstawowego atrybutu w instancji __dict__, gdzie jest ona niedostępna w normalny sposób, dokładnie dlatego, że właściwość ją przesłania.

Należy również pamiętać, że nie ma to sensu w przypadku metod magicznych (podwójnego podkreślenia) . Metody magiczne można oczywiście w ten sposób nadpisać, ale operacje, które ich używają, patrzą tylko na typ. Na przykład możesz ustawić __contains__coś specjalnego w swojej instancji, ale wywołanie x in instancezignoruje to i użyje type(instance).__contains__(instance, x)zamiast tego. Dotyczy to wszystkich magicznych metod określonych w modelu danych Pythona .

Szalony Fizyk
źródło
1
To powinna być akceptowana odpowiedź: jest czysty i działa w Pythonie 3.
BlenderBender
@BlenderBender. Doceniam twoje wsparcie
Mad Physicist
Jaka jest różnica między tą odpowiedzią a tą nad twoją z @Harshal Dhumai?
1313e
1
@ 1313. Funkcjonalnie nie powinno być dużej różnicy. Podejrzewam, że powyższa odpowiedź może akceptować szerszy zakres wywołań jako dane wejściowe, ale bez czytania dokumentów i zabawy, nie jestem pewien.
Mad Physicist
Dokumentacja Pythona 2 do tego: docs.python.org/2/howto/descriptor.html#functions-and-methods . Więc teoretycznie jest to to samo, co odpowiedź @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!!

bobyJeśli potrzebujesz, możesz użyć zmiennej wewnątrz funkcji. Ponieważ nadpisujesz metodę tylko dla tego jednego obiektu instancji, ten sposób jest prostszy i daje dokładnie taki sam efekt jak użycie self.

nosklo
źródło
1
IMHO używanie oryginalnego podpisu zwiększa czytelność, zwłaszcza jeśli funkcja jest zdefiniowana w innym miejscu kodu, a nie w pobliżu instancji. Wyjątkiem byłby przypadek, w którym metoda nadpisywania jest również używana niezależnie jako funkcja. Oczywiście w tym prostym przykładzie nie ma to znaczenia.
codelogic
1
Nie rozumiem, dlaczego nie jest to akceptowana odpowiedź. Nazywa się patchingi to jest właściwy sposób, aby to zrobić (np. boby = Dog()I boby.bark = new_bark). Jest to niezwykle przydatne w testach jednostkowych dla kontroli. Więcej wyjaśnień znajdziesz na tryolabs.com/blog/2013/07/05/run-time-method-patching-python (przykłady) - nie Nie jestem powiązany z linkowaną witryną lub autorem.
Geoff
5
metoda new_bark nie ma dostępu do self (instancji), więc użytkownik nie może uzyskać dostępu do właściwości instancji w new_bark. Zamiast tego należy użyć MethodType z modułu types (patrz moja odpowiedź poniżej).
Harshal Dhumal
3

Ponieważ nikt functools.partialtu nie wspomina :

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
źródło
1

Ponieważ funkcje są obiektami pierwszej klasy w Pythonie, możesz je przekazać podczas inicjalizacji obiektu klasy lub nadpisać je w dowolnym momencie dla danej instancji klasy:

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()

a wyniki są

calling original bark
woof
calling the new bark
wooOOOoof
calling another new
nowoof
JV.
źródło
-4

Chociaż podobał mi się pomysł dziedziczenia od S. Lott i zgadzam się z rzeczą `` typ (a) '', ale ponieważ funkcje również mają dostępne atrybuty, myślę, że można nimi zarządzać w ten sposób:

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__

a wynik to:

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
JV.
źródło
2
Jeśli trzeba nim „zarządzać”, to - dla mnie - coś jest nie tak. Zwłaszcza, gdy istnieje pierwszorzędna funkcja językowa, która już działa.
S.Lott,
-4

Drogi, to nie jest przesłonięcie, po prostu wywołujesz tę samą funkcję dwa razy z obiektem. Zasadniczo nadpisywanie jest związane z więcej niż jedną klasą. jeśli ta sama metoda podpisu istnieje w różnych klasach, wtedy to, która funkcja wywołuje tę funkcję, zdecyduje, który obiekt ją wywoła. Zastępowanie jest możliwe w Pythonie, gdy tworzysz więcej niż jedną klasę, zapisuje te same funkcje i jeszcze jedną rzecz do udostępnienia, że ​​przeciążanie jest niedozwolone w Pythonie

nagimsit
źródło
-4

Uważam, że jest to najdokładniejsza odpowiedź na pierwotne pytanie

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()
Ruvalcaba
źródło