2016-05-24 28 views
6

Trong này nổi tiếng answer giải thích metaclass trong Python. Nó đề cập rằng thuộc tính __metaclass__ sẽ không được kế thừa.Thừa kế của metaclass

Nhưng như một vấn đề của thực tế, tôi đã cố gắng bằng Python:

class Meta1(type): 
    def __new__(cls, clsname, bases, dct): 
     print "Using Meta1" 
     return type.__new__(cls, clsname, bases, dct) 

# "Using Meta1" printed 
class Foo1: 
    __metaclass__ = Meta1 

# "Using Meta1" printed 
class Bar1(Foo1): 
    pass 

Đúng như dự đoán, cả hai FooBar sử dụng Meta1 như metaclass và chuỗi in như mong đợi.

Nhưng trong ví dụ sau đây, khi type(...) được trả lại thay vì type.__new__(...), các metaclass không còn được thừa hưởng:

class Meta2(type): 
    def __new__(cls, clsname, bases, dct): 
     print "Using Meta2" 
     return type(clsname, bases, dct) 

# "Using Meta2" printed 
class Foo2: 
    __metaclass__ = Meta2 

# Nothing printed 
class Bar2(Foo2): 
    pass 

Kiểm tra __metaclass____class__ thuộc tính, tôi có thể thấy:

print Foo1.__metaclass__ # <class '__main__.Meta1'> 
print Bar1.__metaclass__ # <class '__main__.Meta1'> 
print Foo2.__metaclass__ # <class '__main__.Meta2'> 
print Bar2.__metaclass__ # <class '__main__.Meta2'> 

print Foo1.__class__ # <class '__main__.Meta1'> 
print Bar1.__class__ # <class '__main__.Meta1'> 
print Foo2.__class__ # <type 'type'> 
print Bar2.__class__ # <type 'type'> 

Tóm lại:

  1. Cả hai __metaclass____class__sẽ được kế thừa từ lớp cơ sở.

  2. Hành vi tạo được xác định bởi Meta2 sẽ được sử dụng cho Foo2, mặc dù Foo2.__class__ thực sự là type.

  3. Thuộc tính __metaclass__ trong Bar2Meta2, nhưng hành vi tạo của Bar2 không bị ảnh hưởng. Trong một từ khác, Bar2 sử dụng type làm metaclass "thực" thay vì Meta2.

Những quan sát này làm cho cơ chế thừa kế của __metaclass__ trở nên mơ hồ đối với tôi.

tôi đoán là:

  1. Khi trực tiếp gán một lớp (ví dụ Meta1) đến __metaclass__ thuộc tính của một lớp 'foo1', Đó là __metaclass__ thuộc tính có hiệu lực.

  2. Khi lớp con không đặt thuộc tính __metaclass__ một cách rõ ràng khi xác định. Thuộc tính __class__ thay vì thuộc tính __metaclass__ của lớp cơ sở sẽ quyết định metaclass "thực" của lớp con.

Đoán của tôi có đúng không? Làm thế nào để Python đối phó với thừa kế của metaclass?

+0

Tôi nghĩ rằng nó đã làm với loại .__ new__ và loại http://stackoverflow.com/questions/2608708/what-is-the-difference-between-type-and -loại-mới-trong-python –

Trả lời

3

Bạn đang suy đoán rất nhiều, trong khi Python tối giản và "Các trường hợp đặc biệt không đủ đặc biệt để phá vỡ các quy tắc". chỉ thị, làm cho nó dễ hiểu hơn thế.

Trong Python2, thuộc tính __metaclass__ trong nội dung lớp được sử dụng trong thời gian tạo lớp để gọi "lớp" mà lớp đó sẽ là. Thông thường nó là lớp có tên là type.Để làm rõ, thời điểm đó là sau khi trình phân tích cú pháp đã phân tích cú pháp thân lớp, sau khi trình biên dịch biên dịch nó thành đối tượng mã, và sau khi nó thực sự chạy ở thời gian chạy chương trình, và chỉ khi __metaclass__ được cung cấp rõ ràng trong thân lớp đó.

Vì vậy, cách kiểm tra của let mà đi trong một trường hợp như thế:

class A(object): 
    __metaclass__ = MetaA 

class B(A): 
    pass 

A__metaclass__ trong cơ thể của nó - MetaA được gọi thay vì type để làm cho nó vào "đối tượng lớp". B không có __metaclass__ trong cơ thể. Sau khi nó được tạo ra, nếu bạn chỉ cố gắng truy cập thuộc tính __metaclass__, nó là một thuộc tính như anyother, sẽ được hiển thị vì Python sẽ lấy nó từ siêu lớp A. Nếu bạn kiểm tra A.__dict__ bạn sẽ thấy __metaclass__ và nếu bạn kiểm tra B.__dict__ thì không.

Thuộc tính A.__metaclass__ này là không được sử dụng khi nào B được tạo. Nếu bạn thay đổi nó trong A trước khi khai báo B sẽ vẫn sử dụng cùng một metaclass là A - bởi vì Python không sử dụng loại của lớp cha mẹ như metaclass trong sự vắng mặt của việc khai báo rõ ràng __metaclass__.

Để minh họa:

In [1]: class M(type): pass 

In [2]: class A(object): __metaclass__ = M 

In [3]: print "class: {}, metaclass_attr: {}, metaclass_in_dict: {}, type: {}".format(A.__class__, A.__metaclass__, A.__dict__.get("__metaclass__"), type(A)) 
class: <class '__main__.M'>, metaclass_attr: <class '__main__.M'>, metaclass_in_dict: <class '__main__.M'>, type: <class '__main__.M'> 

In [4]: class B(A): pass 

In [5]: print "class: {}, metaclass_attr: {}, metaclass_in_dict: {}, type: {}".format(B.__class__, B.__metaclass__, B.__dict__.get("__metaclass__"), type(B)) 
class: <class '__main__.M'>, metaclass_attr: <class '__main__.M'>, metaclass_in_dict: None, type: <class '__main__.M'> 

In [6]: A.__metaclass__ = type 

In [8]: class C(A): pass 

In [9]: print "class: {}, metaclass_attr: {}, metaclass_in_dict: {}, type: {}".format(C.__class__, C.__metaclass__, C.__dict__.get("__metaclass__"), type(C)) 
class: <class '__main__.M'>, metaclass_attr: <type 'type'>, metaclass_in_dict: None, type: <class '__main__.M'> 

Hơn nữa, nếu bạn cố gắng để chỉ cần tạo một lớp thông qua một cuộc gọi đến type thay vì sử dụng một cơ thể với một tuyên bố class, __metaclass__ cũng chỉ là một thuộc tính bình thường:

In [11]: D = type("D", (object,), {"__metaclass__": M}) 

In [12]: type(D) 
type 

Tổng hợp cho đến nay: Thuộc tính __metaclass__ trong Python 2 chỉ đặc biệt nếu được đặt trong cơ thể lớp một cách rõ ràng khai báo, như là một phần của việc thực hiện tuyên bố khối class. Nó là một thuộc tính bình thường không có đặc tính đặc biệt sau đó.

Cả Python3 đều đã loại bỏ thuộc tính lạ "__metaclass__ này hiện không tốt" và được phép tùy chỉnh thêm cho thân lớp bằng cách thay đổi cú pháp để chỉ định metaclasses. (Nó giống như tuyên bố như thể nó là một "metaclass tên tham số" trên class tuyên bố chính nó)

Bây giờ, đến phần thứ hai của những gì nêu ra những nghi ngờ của bạn: nếu trong phương pháp __new__ của metaclass bạn gọi type thay vì type.__new__, không có cách nào Python có thể "biết" type đang được gọi từ một metaclass có nguồn gốc. Khi bạn gọi type.__new__, bạn chuyển làm tham số đầu tiên của thuộc tính cls, chính số __new__ của metaclass của bạn đã được chuyển bởi thời gian chạy: đó là điều đánh dấu lớp kết quả là một thể hiện của một lớp con của type.Đó là giống như thừa kế làm việc cho bất kỳ lớp khác trong Python - vì vậy "không có hành vi đặc biệt" ở đây:

Vì vậy, nhận ra sự khác biệt:

class M1(type): 
    def __new__(metacls, name, bases, attrs): 
     cls = type.__new__(metacls, name, bases, attrs) 
     # cls now is an instance of "M1" 
     ... 
     return cls 


class M2(type): 
    def __new__(metacls, name, bases, attrs): 
     cls = type(cls, name, bases, attrs) 
     # Type does not "know" it was called from within "M2" 
     # cls is an ordinary instance of "type" 
     ... 
     return cls 

Nó có thể được nhìn thấy trong cửa sổ tương tác:

In [13]: class M2(type): 
    ....:  def __new__(metacls, name, bases, attrs): 
    ....:   return type(name, bases, attrs) 
    ....:  

In [14]: class A(M2): pass 

In [15]: type(A) 
Out[15]: type 

In [16]: class A(M2): __metaclass__ = M2 

In [17]: A.__class__, A.__metaclass__ 
Out[17]: (type, __main__.M2) 

(Lưu ý rằng metaclass __new__ phương pháp tham số đầu tiên là metaclass bản thân, do đó đặt tên đúng hơn metacls hơn cls như trong mã của bạn, và tôi rất nhiều mã "trong tự nhiên")

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