Ghi đè một phương thức ở cấp phiên bản


87

Có cách nào trong Python để ghi đè một phương thức lớp ở cấp cá thể không? Ví dụ:

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

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

Câu trả lời:


11

Vui lòng không làm điều này như được hiển thị. Mã của bạn trở nên không thể đọc được khi bạn bắt cặp một thể hiện khác với lớp.

Bạn không thể gỡ lỗi mã được khớp với khỉ.

Khi bạn tìm thấy một lỗi trong bobyprint type(boby), bạn sẽ thấy rằng (a) đó là một con chó, nhưng (b) đối với một số lý do che khuất nó không sủa một cách chính xác. Đây là một cơn ác mộng. Đừng làm việc đó.

Hãy làm điều này thay thế.

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: Tôi nghĩ rằng "Vui lòng không làm điều này như được hiển thị" đã làm cho điều đó hoàn toàn rõ ràng. Bạn muốn xem những từ nào khác hoặc khác để làm rõ hơn rằng đây không phải là câu trả lời cho câu hỏi đã được đặt ra, mà là đưa ra lời khuyên về lý do tại sao đó là một ý tưởng tồi?
S.Lott

45
Tôi không đồng ý trong lời khuyên, cũng không phải OP như nó có vẻ. Nhưng tôi cho rằng mọi người có lý do để hỏi. Hoặc ngay cả khi OP không có, những khách truy cập khác trong tương lai có thể. Vì vậy, IMHO, một câu trả lời cộng với một lời khiển trách thì tốt hơn chỉ là một lời khiển trách.
arivero

13
@arivero: Điều đó không trả lời câu hỏi của tôi.
S.Lott

9
@ S.Lott Tôi nghĩ bạn nên liên kết "hiển thị" với câu trả lời thực tế, tôi thực sự không có vấn đề gì với việc đây là câu trả lời được chấp nhận nhưng có những lý do tại sao bạn cần phải vá lỗi trong một số trường hợp và từ việc đọc lướt của tôi Tôi lấy "như được hiển thị" có nghĩa là những gì bạn đang hiển thị, thay vì một câu trả lời khác.
Daniel Chatfield

4
Phân lớp là một hợp đồng mạnh hơn nhiều so với phương pháp vá lỗi của khỉ. Khi có thể, hợp đồng mạnh mẽ ngăn chặn hành vi bất ngờ. Nhưng trong một số trường hợp, một hợp đồng lỏng lẻo hơn là mong muốn. Trong những tình huống đó, tôi thích sử dụng gọi lại - thay vì vá lỗi - bởi vì, bằng cách cho phép tùy chỉnh một số hành vi, hợp đồng lớp vẫn không bị vi phạm. Câu trả lời của bạn, mặc dù là lời khuyên thực tế, nhưng câu hỏi này khá nghèo nàn và phong cách viết mã của bạn không nhất quán.
Aaron3468,

166

Vâng nó có thể:

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"

1
Một nhận xét nhỏ, khi bạn làm funcType (new_bark, foo, Dog) nó sẽ thêm tên == sủa và phương thức thể hiện Dog.new_bark trong foo .__ dict__ phải không? Vì vậy, khi bạn gọi một lần nữa nó đầu tiên tra cứu vào từ điển dụ và lời kêu gọi đó,
James

11
Vui lòng giải thích điều này có tác dụng gì, đặc biệt là điều gì funcTypevà tại sao nó lại cần thiết.
Aleksandr Dubinsky

3
Tôi nghĩ rằng điều này có thể được thực hiện đơn giản và rõ ràng hơn một chút bằng cách sử dụng funcType = types.MethodType(sau khi nhập types) thay vì funcType = type(Dog.bark).
Elias Zamaria

13
Tôi đoán điều này không hoạt động với Python 3. Tôi nhận được lỗi "TypeError: function () đối số 1 phải là mã, không phải hàm". Bất kỳ đề xuất nào cho Python 3?
Sait

2
Bạn cũng có thể gọi __get__hàm để liên kết nó với phiên bản.
Mad Physicist

37

Bạn cần sử dụng MethodType từ typesmô-đun. Mục đích của MethodTypelà ghi đè các phương thức mức cá thể (để selfcó thể có sẵn trong các phương thức bị ghi đè).

Xem ví dụ dưới đây.

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!!

31

Để giải thích câu trả lời tuyệt vời của @ codelogic, tôi đề xuất một cách tiếp cận rõ ràng hơn. Đây là kỹ thuật tương tự mà .toán tử dùng để ràng buộc một phương thức lớp khi bạn truy cập nó như một thuộc tính cá thể, ngoại trừ phương thức của bạn thực sự sẽ là một hàm được định nghĩa bên ngoài một lớp.

Làm việc với mã của @ codelogic, sự khác biệt duy nhất là cách phương thức được liên kết. Tôi đang sử dụng thực tế là các hàm và phương thức không phải là bộ mô tả dữ liệu trong Python và gọi __get__phương thức. Đặc biệt lưu ý rằng cả bản gốc và bản thay thế đều có chữ ký giống hệt nhau, có nghĩa là bạn có thể viết phương thức thay thế dưới dạng một phương thức lớp đầy đủ, truy cập tất cả các thuộc tính cá thể thông qua self.

lớp chó:
    def vỏ (tự):
        in "Gâu"

def new_bark (self):
    in "Woof Woof"

foo = Con chó ()

# "Gâu"
foo.bark ()

# thay thế vỏ cây bằng vỏ cây mới chỉ cho đối tượng này
foo.bark = new_bark .__ get __ (foo, Dog)

foo.bark ()
# "Gâu gâu"

Bằng cách gán phương thức liên kết cho một thuộc tính cá thể, bạn đã tạo ra một mô phỏng gần như hoàn chỉnh về việc ghi đè một phương thức. Một tính năng tiện dụng bị thiếu là quyền truy cập vào phiên bản no-arg của super, vì bạn không có trong định nghĩa lớp. Một điều nữa là __name__thuộc tính của phương thức ràng buộc của bạn sẽ không lấy tên của hàm mà nó đang ghi đè, như trong định nghĩa lớp, nhưng bạn vẫn có thể đặt nó theo cách thủ công. Sự khác biệt thứ ba là phương thức được ràng buộc thủ công của bạn là một tham chiếu thuộc tính thuần túy chỉ là một hàm. Các .nhà điều hành không làm gì nhưng lấy tài liệu tham khảo đó. Mặt khác, khi gọi một phương thức thông thường từ một thể hiện, quá trình ràng buộc sẽ tạo ra một phương thức liên kết mới mỗi lần.

Lý do duy nhất mà điều này hoạt động, nhân tiện, là các thuộc tính cá thể ghi đè các bộ mô tả không phải dữ liệu . Bộ mô tả dữ liệu có __set__các phương thức, phương thức nào (may mắn cho bạn) thì không. Các bộ mô tả dữ liệu trong lớp thực sự được ưu tiên hơn bất kỳ thuộc tính cá thể nào. Đó là lý do tại sao bạn có thể gán cho một thuộc tính: __set__phương thức của chúng được gọi khi bạn cố gắng thực hiện một phép gán. Cá nhân tôi muốn thực hiện điều này một bước xa hơn và ẩn giá trị thực tế của thuộc tính cơ bản trong cá thể __dict__, nơi mà nó không thể truy cập được bằng cách thông thường chính xác vì thuộc tính che khuất nó.

Bạn cũng nên nhớ rằng điều này là vô nghĩa đối với các phương thức magic (dấu gạch dưới kép) . Tất nhiên, các phương pháp ma thuật có thể được ghi đè theo cách này, nhưng các thao tác sử dụng chúng chỉ nhìn vào kiểu. Ví dụ: bạn có thể đặt __contains__thành một cái gì đó đặc biệt trong trường hợp của mình, nhưng việc gọi x in instancesẽ bỏ qua điều đó và sử dụng type(instance).__contains__(instance, x)thay thế. Điều này áp dụng cho tất cả các phương thức ma thuật được chỉ định trong mô hình dữ liệu Python .


1
Điều này sẽ là câu trả lời được chấp nhận: đó là sạch và các công trình bằng Python 3.
BlenderBender

@BlenderBender. Tôi đánh giá cao sự hỗ trợ của bạn
Mad Physicist

Sự khác biệt giữa câu trả lời này và câu trả lời ở trên của bạn từ @Harshal Dhumai là gì?
1313e

1
@ 1313. Về mặt chức năng, không có nhiều sự khác biệt. Tôi nghi ngờ câu trả lời ở trên có thể chấp nhận một loạt các lệnh gọi làm đầu vào, nhưng nếu không đọc tài liệu và chơi xung quanh, tôi không chắc.
Mad Physicist

Tài liệu Python 2 cho việc này: docs.python.org/2/howto/descriptor.html#functions-and-methods . Vì vậy, về mặt lý thuyết, nó giống với câu trả lời của @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!!

Bạn có thể sử dụng bobybiến bên trong hàm nếu cần. Vì bạn chỉ ghi đè phương thức cho một đối tượng cá thể này, nên cách này đơn giản hơn và có tác dụng giống hệt như cách sử dụng self.


1
IMHO sử dụng chữ ký gốc giúp tăng khả năng đọc, đặc biệt nếu hàm được định nghĩa ở nơi khác trong mã, không phải gần phiên bản. Ngoại lệ sẽ là trường hợp phương thức ghi đè cũng được sử dụng độc lập như một hàm. Tất nhiên, trong ví dụ đơn giản này, nó không quan trọng.
codelogic

1
Tôi không hiểu tại sao đây không phải là câu trả lời được chấp nhận. Nó được gọi patchingvà đây là cách chính xác để làm điều đó (ví dụ boby = Dog()boby.bark = new_bark). Nó cực kỳ hữu ích trong việc kiểm tra đơn vị để kiểm soát. Để biết thêm giải thích, hãy xem tryolabs.com/blog/2013/07/05/run-time-method-patching-python (ví dụ) - không, tôi không liên kết với trang web hoặc tác giả được liên kết.
Geoff

5
new_bark method không có quyền truy cập vào self (instance) vì vậy không có cách nào người dùng có thể truy cập các thuộc tính instance trong new_bark. Thay vào đó, người ta cần sử dụng mô-đun MethodType từ các loại (xem câu trả lời của tôi bên dưới).
Harshal Dhumal

3

Vì không có ai đang đề cập functools.partialở đây:

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

Vì các hàm là các đối tượng lớp đầu tiên trong Python nên bạn có thể chuyển chúng trong khi khởi tạo đối tượng lớp của mình hoặc ghi đè nó bất cứ lúc nào cho một cá thể lớp nhất định:

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

và kết quả là

calling original bark
woof
calling the new bark
wooOOOoof
calling another new
nowoof

-4

Mặc dù tôi thích ý tưởng kế thừa từ S. Lott và đồng ý với điều 'type (a)', nhưng vì các hàm cũng có các thuộc tính có thể truy cập được, tôi nghĩ nó có thể được quản lý theo cách này:

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__

và đầu ra là:

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
Nếu nó cần được "quản lý", thì - với tôi - có gì đó không ổn. Đặc biệt là khi có một tính năng ngôn ngữ hạng nhất đã thực hiện công việc.
S.Lott

-4

Thưa, đây không phải là ghi đè bạn chỉ đang gọi cùng một hàm hai lần với đối tượng. Về cơ bản ghi đè có liên quan đến nhiều hơn một lớp. khi cùng một phương thức chữ ký tồn tại trong các lớp khác nhau thì hàm nào bạn đang gọi nó sẽ quyết định đối tượng gọi hàm này. Có thể ghi đè trong python khi bạn tạo nhiều hơn một lớp đang viết các hàm giống nhau và một điều nữa cần chia sẻ là không được phép ghi đè trong python


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.