2012-12-12 27 views
10

Update - 2012/12/13động thêm các phương pháp có hoặc không có metaclass

Chỉ cần làm rõ - Tôi không quá nhiều quan tâm đến cách làm thế nào để thêm các phương pháp đến các lớp học - như bạn có thể nhìn thấy dưới đây trong tôi câu hỏi và trong câu trả lời của mọi người, có nhiều hơn một cách để làm điều đó (lưỡi trong má và mũ nón để tự Perl của tôi). Điều tôi quan tâm là tìm hiểu sự khác biệt cơ bản của việc thêm phương pháp vào các lớp học bằng cách sử dụng các cách tiếp cận khác nhau và câu hỏi lớn thực sự là lý do tại sao tôi cần phải sử dụng metaclasses cho. Ví dụ: A Primer on Python Metaclass Programming nói rằng:

Có lẽ cách sử dụng phổ biến nhất của metaclasses [...]: thêm, xóa, đổi tên hoặc thay thế các phương thức cho những người được xác định trong lớp được tạo.

Và vì có nhiều cách để làm điều đó, tôi bối rối và tìm kiếm giải thích.

Cảm ơn!

Original - 2012/12/12

tôi cần phải tự động thêm các phương pháp để một lớp học (và các lớp học mới được tạo ra dựa trên lớp đó). Tôi đã đưa ra hai cách tiếp cận, một phương pháp liên quan đến metaclass, một cái khác đang làm mà không có một. Tôi không thể nhìn thấy bất kỳ sự khác biệt trong những gì hai cách tiếp cận cho tôi khác với thực tế sau này không liên quan đến "ma thuật đen" metaclass;)

Tiếp cận # 1với metaclass:

class Meta(type): 
     def __init__(cls, *args, **kwargs): 
       setattr(cls, "foo", lambda self: "[email protected]%s(class %s)" % (self, 
         cls.__name__)) 

class Y(object): 
     __metaclass__ = Meta 

y = Y() 
y.foo() # Gives '[email protected]<__main__.Y object at 0x10e4afd10>(class Y)' 

Tiếp cận # 2mà không metaclass:

class Z(object): 
     def __init__(self): 
       setattr(self.__class__, "foo", 
         lambda self: "[email protected]%s(class %s)" % 
         (self, self.__class__.__name__)) 

z = Z() 
z.foo() # Gives '[email protected]<__main__.Z object at 0x10c865dd0>(class Z)' 

theo như tôi có thể cho biết, cả hai cách tiếp cận đều cho tôi kết quả tương tự và "biểu hiện". Ngay cả khi tôi cố gắng tạo một lớp mới bằng cách sử dụng type("NewClassY", (Y,), {}) hoặc type("NewClassZ", (Z,), {}) Tôi nhận được kết quả mong đợi tương tự mà không khác nhau giữa hai phương pháp tiếp cận.

Vì vậy, tôi đang tự hỏi nếu có thực sự là bất kỳ "cơ bản" khác biệt trong cách tiếp cận, hoặc nếu có bất cứ điều gì mà sẽ "cắn" tôi sau này nếu tôi sử dụng một trong hai # 1 hoặc # 2 hoặc nếu nó chỉ là một cú pháp?

PS: Có, tôi đã đọc các chủ đề khác ở đây nói về metaclasses trong Python và mô hình dữ liệu pythonic.

+0

Bạn có thể gọi 'Y.foo' và 'Z.foo' sử dụng cả hai cách tiếp cận? – Blender

+2

Sự khác biệt là metaclass sẽ tạo ra các phương thức tại thời điểm lớp được định nghĩa và '__init__'way sẽ tạo ra các phương thức khi bạn khởi tạo lớp. Ngoài ra còn có nhiều thừa kế (mixins) mà bạn có thể sử dụng để thêm các phương thức. –

+0

@Blender Không, bạn không thể gọi chúng như thế vì chúng không phải là "phương pháp lớp". – TomasHeran

Trả lời

2

Nếu sự cố đang thêm phương thức động, python sẽ xử lý nó theo cách khá đơn giản. Nó đi như sau:

#!/usr/bin/python 
class Alpha(): 

    def __init__(self): 
     self.a = 10 
     self.b = 100 

alpha = Alpha() 
print alpha.a 
print alpha.b 

def foo(self): 
    print self.a * self.b * self.c 

Alpha.c = 1000 
Alpha.d = foo 

beta = Alpha() 
beta.d() 

Output:

$ python script.py 
10 
100 
1000000 

Trân trọng!

P.S.: Tôi thấy không có sự tương đồng của ma thuật đen đây (:

Edit:

Xét bình luận Martineau, tôi đang bổ sung thêm data attributes, không method function attributes tôi thực sự có thể thấy không có sự khác biệt giữa làm Alpha.d = fooAlpha.d = lambda self: foo(self), ngoại trừ việc. tôi muốn được sử dụng một hàm lambda như là một wrapper đến chức năng foo thêm

việc bổ sung của phương pháp này là như nhau, và trăn tự đặt tên cho cả hai bổ sung giống nhau:.

#!/usr/bin/python 
class Alpha(): 

    def __init__(self): 
     self.a = 10 
     self.b = 100 

alpha = Alpha() 
print alpha.a 
print alpha.b 

def foo(self): 
    print self.a * self.b * self.c 

Alpha.c = 1000 

Alpha.d = lambda self: foo(self) 
Alpha.e = foo 

print Alpha.d 
print Alpha.e 

a = Alpha() 
a.d() 
a.e() 

Output:

10 
100 
<unbound method Alpha.<lambda>> 
<unbound method Alpha.foo> 
1000000 
1000000 

Như đã trình bày, trăn tự đặt tên cho cả hai bổ sung kết quả như phương pháp - sự khác biệt duy nhất là một là một tham chiếu đến hoạt foo, và người kia là một tham chiếu đến một hàm lambda rằng sử dụng hàm foo trong phần định nghĩa của nó.

Nếu tôi nói điều gì sai, xin vui lòng, sửa tôi.

Trân trọng!

+0

Câu trả lời của bạn không thêm các phương thức vào lớp, nó chỉ thêm các thuộc tính dữ liệu, chứ không phải các thuộc tính hàm phương thức. – martineau

+0

bạn có muốn đưa ra một ví dụ về thuộc tính hàm 'phương thức' không? – Rubens

+1

'Alpha.answer = lambda self: 42' – martineau

9

Lý do rõ ràng để sử dụng metaclasses là vì chúng thực sự cung cấp siêu dữ liệu về lớp ngay khi lớp được biết, không liên quan đến sự hiện diện của các đối tượng hay không. Trivial, phải không? Vâng, chúng ta hãy thể hiện một số lệnh tôi thực hiện trên ban đầu của bạn ZY lớp để xem những gì này có nghĩa là:

In [287]: hasattr(Y,'foo') 
Out[287]: True 

In [288]: hasattr(Z,'foo') 
Out[288]: False 

In [289]: Y.__dict__ 
Out[289]: 
<dictproxy {..., 'foo': <function __main__.<lambda>>}> 

In [290]: Z.__dict__ 
Out[290]: 
<dictproxy {...}> 

In [291]: z= Z() 

In [292]: hasattr(Z,'foo') 
Out[292]: True 

In [293]: Z.__dict__ 
Out[293]: 
<dictproxy {..., 'foo': <function __main__.<lambda>>}> 

In [294]: y = Y() 

In [295]: hasattr(Y,'foo') 
Out[295]: True 

In [296]: Y.__dict__ 
Out[296]: 
<dictproxy {..., 'foo': <function __main__.<lambda>>}> 

Như bạn có thể thấy phiên bản thứ hai thực sự làm thay đổi lớp Z đáng kể sau khi nó được công bố, một cái gì đó bạn thường muốn tránh. Nó chắc chắn không phải là một hoạt động không phổ biến cho python thực hiện các thao tác trên 'các kiểu' (đối tượng lớp) và bạn có thể muốn chúng nhất quán nhất có thể, chắc chắn đối với trường hợp này (nơi mà phương thức này không thực sự động trong thời gian chạy) thời gian).

Một ứng dụng nhảy vào tâm là documenation. Nếu bạn thêm một chuỗi tài liệu vào foo bằng cách sử dụng metaclass, tài liệu có thể nhặt nó lên, thông qua phương thức __init__ điều này rất khó xảy ra.

Điều này cũng có thể dẫn đến lỗi khó phát hiện. xem xét một đoạn mã sử dụng sự biến đổi của một lớp. Nó có thể là trong 99,99% các trường hợp này được thực hiện sau khi một thể hiện của Z đã được tạo ra, nhưng 0,01% có thể dẫn đến hành vi kỳ lạ, nếu không bị treo.

Nó cũng có thể gặp khó khăn trong chuỗi phân cấp, nơi bạn phải chăm sóc tốt nơi gọi hàm tạo của cha mẹ. Một lớp học như thế này ví dụ có thể cho các vấn đề:

class Zd(Z): 
    def __init__(self): 
     self.foo() 
     Z.__init__(self) 
a = Zd() 
... 
AttributeError: 'Zd' object has no attribute 'foo' 

Trong khi điều này hoạt động tốt:

class Yd(Y): 
    def __init__(self): 
     self.foo() 
     Y.__init__(self) 
a = Yd() 

Nó có vẻ ngu ngốc để gọi một phương thức mà không có sự liên quan __init__ được sử dụng nhưng nó có thể xảy ra khi bạn' không cẩn thận, và chắc chắn trong các hệ thống phân cấp phức tạp hơn, nơi MRO không rõ ràng ngay lập tức. Và rất khó để phát hiện ra lỗi này bởi vì thường xuyên nhất Zd() sẽ thành công ngay sau khi Z.__init__ được gọi ở đâu đó trước đây.

+0

Cảm ơn bạn! Đây chính xác là những gì tôi đang tìm kiếm. – TomasHeran