Ganti metode di tingkat instance

87

Apakah ada cara dengan Python untuk mengganti metode kelas pada tingkat instance? Sebagai contoh:

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

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

Jawaban:

10

Tolong jangan lakukan ini seperti yang ditunjukkan. Kode Anda menjadi tidak bisa dibaca saat Anda mencocokkan instance agar berbeda dari kelas.

Anda tidak dapat men-debug kode monkeypatched.

Ketika Anda menemukan bug di bobydan print type(boby), Anda akan melihat bahwa (a) itu adalah Anjing, tetapi (b) karena alasan yang tidak jelas ia tidak menggonggong dengan benar. Ini mimpi buruk. Jangan lakukan itu.

Silakan lakukan ini sebagai gantinya.

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
sumber
9
@arivero: Saya pikir "Tolong jangan lakukan ini seperti yang ditunjukkan" membuatnya sangat jelas. Kata lain atau berbeda apa yang ingin Anda lihat untuk memperjelas bahwa ini bukan menjawab pertanyaan yang diajukan, tetapi memberikan saran mengapa itu ide yang buruk?
S. Lotot
46
Saya tidak setuju dengan saran, atau OP seperti yang terlihat. Tapi saya berasumsi bahwa orang punya alasan untuk bertanya. Atau bahkan jika OP belum, pengunjung lain di masa depan bisa. Jadi IMHO, jawaban plus teguran lebih baik dari sekedar teguran.
arivero
13
@arivero: Itu tidak menjawab pertanyaan saya.
S. Lott
9
@ S.Lott Saya pikir Anda harus menautkan "ditunjukkan" ke jawaban yang sebenarnya, saya tidak benar-benar memiliki masalah dengan ini menjadi jawaban yang diterima tetapi ada alasan mengapa Anda perlu menambal monyet dalam beberapa keadaan dan dari pembacaan skim saya Saya mengambil "seperti yang ditunjukkan" untuk mengartikan apa yang Anda tunjukkan, daripada jawaban yang berbeda.
Daniel Chatfield
4
Subclassing adalah kontrak yang jauh lebih kuat daripada metode menambal monyet. Jika memungkinkan, kontrak yang kuat mencegah perilaku yang tidak terduga. Namun dalam beberapa kasus, kontrak yang lebih longgar diinginkan. Dalam situasi tersebut, saya lebih suka menggunakan callback - daripada monkey-patching - karena, dengan mengizinkan beberapa perilaku disesuaikan, kontrak kelas tetap tidak dilanggar. Jawaban Anda, meskipun saran praktis, cukup buruk untuk pertanyaan ini dan gaya pengkodean Anda tidak konsisten.
Aaron3468
169

Iya itu mungkin:

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
sumber
1
Sebuah komentar kecil, ketika Anda melakukan funcType (new_bark, foo, Dog) yang menambahkan nama == bark dan metode instance Dog.new_bark di foo .__ dict__ kan? Jadi, ketika Anda memanggilnya lagi, cari terlebih dahulu ke dalam kamus instance dan memanggilnya,
James
11
Tolong jelaskan apa fungsinya, terutama apa funcTypedan mengapa itu perlu.
Aleksandr Dubinsky
3
Saya pikir ini bisa dibuat sedikit lebih sederhana dan lebih eksplisit dengan menggunakan funcType = types.MethodType(setelah mengimpor types) daripada funcType = type(Dog.bark).
Elias Zamaria
13
Saya kira ini tidak bekerja dengan Python 3. Saya mendapatkan kesalahan "TypeError: function () 1 harus kode, bukan fungsi". Ada saran untuk Python 3?
Tunggu
2
Anda juga dapat memanggil __get__fungsi untuk mengikatnya ke instance.
Fisikawan Gila
41

Anda perlu menggunakan MethodType dari typesmodul. Tujuannya MethodTypeadalah untuk menimpa metode tingkat instance (sehingga selfdapat tersedia dalam metode yang ditimpa).

Lihat contoh di bawah ini.

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
sumber
32

Untuk menjelaskan jawaban bagus @ codelogic, saya mengusulkan pendekatan yang lebih eksplisit. Ini adalah teknik yang sama dengan. operator untuk mengikat metode kelas saat Anda mengaksesnya sebagai atribut instance, kecuali bahwa metode Anda sebenarnya adalah fungsi yang ditentukan di luar kelas.

Bekerja dengan kode @ codelogic, satu-satunya perbedaan adalah cara mengikat metode. Saya menggunakan fakta bahwa fungsi dan metode adalah deskriptor non-data dalam Python, dan menggunakan __get__metode tersebut. Perhatikan terutama bahwa yang asli dan penggantinya memiliki tanda tangan yang identik, artinya Anda dapat menulis penggantinya sebagai metode kelas lengkap, mengakses semua atribut instance melaluiself .

kelas Anjing:
    def bark (diri):
        cetak "Guk"

def new_bark (self):
    cetak "Guk Guk"

foo = Anjing ()

# "Guk"
foo.bark ()

# ganti bark dengan new_bark untuk objek ini saja
foo.bark = new_bark .__ get __ (foo, Dog)

foo.bark ()
# "Guk guk"

Dengan menetapkan metode terikat ke atribut instance, Anda telah membuat simulasi yang hampir lengkap untuk mengganti metode. Satu fitur praktis yang hilang adalah akses ke versi no-arg super, karena Anda tidak berada dalam definisi kelas. Hal lainnya adalah bahwa __name__atribut metode terikat Anda tidak akan mengambil nama fungsi yang ditimpanya, seperti yang akan terjadi pada definisi kelas, tetapi Anda masih dapat menyetelnya secara manual. Perbedaan ketiga adalah bahwa metode Anda yang terikat secara manual adalah referensi atribut biasa yang kebetulan merupakan sebuah fungsi. The .Operator tidak apa-apa tapi mengambil referensi itu. Sebaliknya, saat memanggil metode reguler dari sebuah instance, proses binding membuat metode terikat baru setiap saat.

Ngomong-ngomong, satu-satunya alasan ini berfungsi adalah karena atribut instance menimpa deskriptor non-data . Deskriptor data memiliki __set__metode, sedangkan metode (untungnya bagi Anda) tidak. Deskriptor data di kelas sebenarnya mengambil prioritas di atas atribut instance apa pun. Itulah mengapa Anda dapat menetapkan ke properti: __set__metode mereka dipanggil saat Anda mencoba membuat tugas. Saya pribadi ingin mengambil langkah lebih jauh dan menyembunyikan nilai sebenarnya dari atribut yang mendasari di instance __dict__, di mana itu tidak dapat diakses dengan cara normal persis karena properti membayangi itu.

Anda juga harus ingat bahwa ini tidak ada gunanya untuk metode sulap (garis bawah ganda) . Metode sihir tentu saja dapat diganti dengan cara ini, tetapi operasi yang menggunakannya hanya melihat jenisnya. Misalnya, Anda dapat mengatur __contains__ke sesuatu yang istimewa dalam contoh Anda, tetapi panggilan x in instanceakan mengabaikannya dan type(instance).__contains__(instance, x)sebagai gantinya. Ini berlaku untuk semua metode ajaib yang ditentukan dalam model data Python .

Fisikawan Gila
sumber
1
Ini harus menjadi jawaban yang diterima: bersih dan berfungsi dengan Python 3.
BlenderBender
@Bayu_joo Saya menghargai dukungan Anda
Fisikawan Gila
Apa perbedaan antara jawaban ini dengan jawaban di atas milik Anda dari @Harshal Dhumai?
1313e
1
@ 1313. Secara fungsional, seharusnya tidak banyak perbedaan. Saya menduga jawaban di atas dapat menerima lebih banyak jenis callable sebagai masukan, tetapi tanpa membaca dokumen dan bermain-main, saya tidak yakin.
Fisikawan Gila
Dokumentasi Python 2 untuk ini: docs.python.org/2/howto/descriptor.html#functions-and-methods . Jadi secara teoritis sama dengan jawaban @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!!

Anda dapat menggunakan bobyvariabel di dalam fungsi jika Anda membutuhkannya. Karena Anda mengganti metode hanya untuk satu objek contoh ini, cara ini lebih sederhana dan memiliki efek yang sama persis seperti menggunakan self.

nosklo.dll
sumber
1
IMHO menggunakan tanda tangan asli menambah keterbacaan, terutama jika fungsi didefinisikan di tempat lain dalam kode, bukan di dekat instance. Pengecualiannya adalah kasus di mana metode penggantian juga digunakan secara independen sebagai fungsi. Tentu saja, dalam contoh sederhana ini, tidak masalah.
codelogic
1
Saya tidak mengerti mengapa ini bukan jawaban yang diterima. Ini disebut patchingdan ini adalah cara yang benar untuk melakukannya (misalnya boby = Dog()dan boby.bark = new_bark). Ini sangat berguna dalam pengujian unit untuk kontrol. Untuk penjelasan selengkapnya, lihat tryolabs.com/blog/2013/07/05/run-time-method-patching-python (contoh) - tidak, saya tidak berafiliasi dengan situs atau penulis yang ditautkan.
Geoff
5
Metode new_bark tidak memiliki akses ke self (instance) sehingga tidak mungkin pengguna dapat mengakses properti instance di new_bark. Sebagai gantinya seseorang perlu menggunakan MethodType dari modul tipe (lihat jawaban saya di bawah).
Harshal Dhumal
3

Karena tidak ada yang menyebutkan di functools.partialsini:

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!!
pengguna1285245
sumber
1

Karena fungsi adalah objek kelas pertama dalam Python, Anda dapat meneruskannya saat menginisialisasi objek kelas Anda atau menimpanya kapan saja untuk instance kelas tertentu:

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

dan hasilnya adalah

calling original bark
woof
calling the new bark
wooOOOoof
calling another new
nowoof
JV.
sumber
-4

Meskipun saya menyukai ide pewarisan dari S.Lott dan setuju dengan hal 'type (a)', tetapi karena fungsi juga memiliki atribut yang dapat diakses, saya pikir itu dapat dikelola dengan cara ini:

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__

dan hasilnya adalah:

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.
sumber
2
Jika perlu "dikelola", maka - bagi saya - ada yang tidak beres. Terutama ketika ada fitur bahasa kelas satu yang sudah berfungsi.
S. Lotot
-4

Sayang ini tidak menimpa Anda hanya memanggil fungsi yang sama dua kali dengan objek. Pada dasarnya menimpa terkait dengan lebih dari satu kelas. ketika metode tanda tangan yang sama ada di kelas yang berbeda maka fungsi mana yang Anda panggil ini memutuskan objek yang memanggil ini. Penimpaan dimungkinkan dalam python ketika Anda membuat lebih dari satu kelas menulis fungsi yang sama dan satu hal lagi untuk dibagikan bahwa overloading tidak diperbolehkan di python

nagimsit
sumber
-4

Saya menemukan ini sebagai jawaban paling akurat untuk pertanyaan asli

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
sumber