2012-04-29 27 views
13

Tôi hy vọng ai đó có thể trả lời này mà có một sự hiểu biết sâu tốt của Python :)phương pháp đặc biệt Overriding trên một thể hiện

Xét đoạn mã sau:

>>> class A(object): 
...  pass 
... 
>>> def __repr__(self): 
...  return "A" 
... 
>>> from types import MethodType 
>>> a = A() 
>>> a 
<__main__.A object at 0x00AC6990> 
>>> repr(a) 
'<__main__.A object at 0x00AC6990>' 
>>> setattr(a, "__repr__", MethodType(__repr__, a, a.__class__)) 
>>> a 
<__main__.A object at 0x00AC6990> 
>>> repr(a) 
'<__main__.A object at 0x00AC6990>' 
>>> 

Chú ý cách repr (a) không mang lại kết quả mong đợi của "A"? Tôi muốn biết tại sao đây là trường hợp và nếu có một cách để đạt được điều này ...

Ngược lại, ví dụ sau đây có thể hoạt động (Có thể vì chúng tôi không cố gắng ghi đè phương pháp đặc biệt?):

>>> class A(object): 
...  def foo(self): 
...    return "foo" 
... 
>>> def bar(self): 
...  return "bar" 
... 
>>> from types import MethodType 
>>> a = A() 
>>> a.foo() 
'foo' 
>>> setattr(a, "foo", MethodType(bar, a, a.__class__)) 
>>> a.foo() 
'bar' 
>>> 
+0

Có thể bạn sẽ nhận được phản hồi sáng suốt hơn nhiều nếu bạn nói những gì bạn đang cố gắng đạt được. – mattmanser

+1

Đối với các lớp kiểu mới, các phương thức đặc biệt được tra cứu trên lớp chứ không phải là cá thể. Xem câu trả lời của tôi dưới đây để biết thêm chi tiết và để giải quyết một số vấn đề. –

+0

Tôi không thể mô tả những gì tôi muốn làm một cách chi tiết. Lấy làm tiếc. Nhưng trong ngắn hạn, tôi đang cố gắng để mô hình hóa một mô hình OO nguyên mẫu nơi tôi có thể làm các toán tử như: World = Object(). Clone(). Mixin (World); Trường hợp World là một lớp với một tập hợp các phương thức ghi đè/thay thế một phương thức trong cá thể Object.clone(). –

Trả lời

15

Python không gọi các phương thức đặc biệt, những tên có tên được bao quanh bởi __ trên cá thể, nhưng chỉ trên lớp, dường như để cải thiện hiệu suất. Vì vậy, không có cách nào để ghi đè trực tiếp __repr__() trên một phiên bản và làm cho nó hoạt động. Thay vào đó, bạn cần phải làm điều gì đó giống như vậy:

class A(object): 
    def __repr__(self): 
     return self._repr() 
    def _repr(self): 
     return object.__repr__(self) 

Bây giờ bạn có thể ghi đè __repr__() trên một thể hiện bằng cách thay _repr().

+0

"Python không gọi các phương thức đặc biệt, những cái có tên được bao quanh bởi __ trên cá thể, nhưng chỉ trên lớp, dường như để cải thiện hiệu suất" <- Bạn có tham chiếu đến điều này không? –

+1

Xem [this] (http://docs.python.org/reference/datamodel.html#special-method-lookup-for-new-style-classes). Các quy tắc hơi thoải mái trên các lớp kiểu cũ, nhưng bạn nên sử dụng các lớp kiểu mới và trong Python 3 chúng là kiểu duy nhất. – kindall

+5

Nó không phải chủ yếu để cải thiện hiệu suất. Như các tài liệu nói, "Lý do đằng sau hành vi này nằm với một số phương thức đặc biệt như' __hash __() 'và' __repr __() 'được thực hiện bởi tất cả các đối tượng, bao gồm các đối tượng kiểu. quy trình tra cứu thông thường, chúng sẽ thất bại khi được gọi trên chính đối tượng kiểu… ”(Tuy nhiên, lý do cho' __getattribute__' _also_ là hiệu suất _is_ có thể bỏ qua.) – abarnert

3

Lý do là phương pháp đặc biệt (__x__()) được định nghĩa cho lớp học, chứ không phải trường hợp.

Điều này có ý nghĩa khi bạn nghĩ về __new__() - sẽ không thể gọi điều này trên một cá thể vì cá thể không tồn tại khi được gọi.

Vì vậy, bạn có thể làm điều này trên lớp như một toàn thể nếu bạn muốn:

>>> A.__repr__ = __repr__ 
>>> a 
A 

Hoặc trên một thể hiện cá nhân, như in kindall's answer. (Lưu ý có rất nhiều điểm tương đồng ở đây, nhưng tôi nghĩ rằng các ví dụ của tôi cũng đã đủ để đăng bài này).

3

Đối với các lớp kiểu mới, Python sử dụng tra cứu phương pháp đặc biệt để bỏ qua các phiên bản. Dưới đây là một đoạn trích từ the source:

1164 /* Internal routines to do a method lookup in the type 
    1165 without looking in the instance dictionary 
    1166 (so we can't use PyObject_GetAttr) but still binding 
    1167 it to the instance. The arguments are the object, 
    1168 the method name as a C string, and the address of a 
    1169 static variable used to cache the interned Python string. 
    1170 
    1171 Two variants: 
    1172 
    1173 - lookup_maybe() returns NULL without raising an exception 
    1174  when the _PyType_Lookup() call fails; 
    1175 
    1176 - lookup_method() always raises an exception upon errors. 
    1177 
    1178 - _PyObject_LookupSpecial() exported for the benefit of other places. 
    1179 */ 

Bạn có thể thay đổi một lớp kiểu cũ (không kế thừa từ đối tượng) hoặc bạn có thể thêm các phương pháp điều phối đến lớp (phương pháp đó trở đi tra cứu trở lại ví dụ). Đối với một ví dụ về phương pháp điều phối dụ, xem công thức ở http://code.activestate.com/recipes/578091

+0

AFAIK nếu tôi đã thay đổi để sử dụng các lớp Old-Style, điều này sẽ không làm việc cho Python 3? –

7

Như đã giải thích trong Special Method Lookup:

Đối với các lớp học tùy chỉnh, lời gọi ngầm của phương pháp đặc biệt chỉ được bảo đảm để làm việc một cách chính xác nếu định nghĩa vào loại của một đối tượng, không có trong từ điển thể hiện của đối tượng… Ngoài việc bỏ qua bất kỳ thuộc tính thể hiện nào theo sở thích của tính chính xác, tìm kiếm phương pháp đặc biệt ngầm cũng thường bỏ qua phương thức __getattribute__() ngay cả trong metaclass của đối tượng

(Phần tôi đã giải thích lý do đằng sau điều này, nếu bạn quan tâm đến điều đó.)

Python không ghi lại chính xác khi triển khai nên hoặc không nên tra cứu phương pháp trên loại; tất cả các tài liệu đó là, có hiệu lực, việc triển khai đó có thể hoặc không thể xem xét cá thể cho các tra cứu phương pháp đặc biệt, vì vậy bạn không nên dựa vào một trong hai.

Như bạn có thể đoán từ kết quả thử nghiệm của mình, trong triển khai CPython, __repr__ là một trong các hàm được tra cứu trên loại.


Mọi thứ hơi khác biệt trong 2.x, chủ yếu là do sự hiện diện của các lớp học cổ điển, nhưng miễn là bạn chỉ tạo các lớp kiểu mới mà bạn có thể nghĩ chúng giống nhau.


Lý do phổ biến nhất mọi người muốn thực hiện điều này là để khỉ vá các trường hợp khác nhau của một đối tượng để làm những việc khác nhau. Bạn không thể làm điều đó với các phương pháp đặc biệt, vì vậy ... bạn có thể làm gì? Có một giải pháp sạch sẽ, và một giải pháp hacky.

Giải pháp sạch là triển khai phương pháp đặc biệt trên lớp chỉ cần gọi phương thức thông thường trên cá thể. Sau đó, bạn có thể khỉ vá rằng phương pháp thường xuyên trên mỗi trường hợp. Ví dụ:

class C(object): 
    def __repr__(self): 
     return getattr(self, '_repr')() 
    def _repr(self): 
     return 'Boring: {}'.format(object.__repr__(self)) 

c = C() 
def c_repr(self): 
    return "It's-a me, c_repr: {}".format(object.__repr__(self)) 
c._repr = c_repr.__get__(c) 

Giải pháp hack là xây dựng một phân lớp mới khi đang bay và sắp xếp lại đối tượng. Tôi nghi ngờ bất cứ ai thực sự có một tình huống mà đây là một ý tưởng tốt sẽ biết làm thế nào để thực hiện nó từ câu đó, và bất cứ ai không biết làm thế nào để làm như vậy không nên cố gắng, vì vậy tôi sẽ để nó ở đó.

+0

Tôi nghĩ rằng để làm việc trên 2.x, bạn có thể cần phải gọi '__get __ (c, type (c))', nhưng tôi không nhớ chắc chắn, và không có một phiên dịch 2.x làm việc trên máy này … Có lẽ sẽ tốt hơn nếu sử dụng 'types.MethodType' thay thế? – abarnert

Các vấn đề liên quan