2012-01-15 25 views
6

Tôi có một trường hợp sử dụng kỳ lạ và khác thường đối với metaclasses nơi tôi muốn thay đổi __metaclass__ của một lớp cơ sở sau khi nó được định nghĩa để các lớp con của nó sẽ tự động sử dụng __metaclass__ mới. Nhưng điều đó kỳ quặc không hoạt động:Tại sao tôi không thể thay đổi thuộc tính __metaclass__ của một lớp?

class MetaBase(type): 
    def __new__(cls, name, bases, attrs): 
     attrs["y"] = attrs["x"] + 1 
     return type.__new__(cls, name, bases, attrs) 

class Foo(object): 
    __metaclass__ = MetaBase 
    x = 5 

print (Foo.x, Foo.y) # prints (5, 6) as expected 

class MetaSub(MetaBase): 
    def __new__(cls, name, bases, attrs): 
     attrs["x"] = 11 
     return MetaBase.__new__(cls, name, bases, attrs) 

Foo.__metaclass__ = MetaSub 

class Bar(Foo): 
    pass 

print(Bar.x, Bar.y) # prints (5, 6) instead of (11, 12) 

Những gì tôi đang làm rất tốt có thể không khôn ngoan/không được hỗ trợ/không xác định, nhưng tôi có thể không cho cuộc sống của tôi ra như thế nào metaclass cũ đang được gọi, và tôi ít nhất cũng hiểu được điều đó là có thể.

EDIT: Dựa trên một gợi ý do jsbueno, tôi thay thế dòng Foo.__metaclass__ = MetaSub với dòng sau, mà đã làm chính xác những gì tôi muốn:

Foo = type.__new__(MetaSub, "Foo", Foo.__bases__, dict(Foo.__dict__)) 

Trả lời

2

Thông tin siêu hạng cho một lớp được sử dụng tại thời điểm nó được tạo (được phân tích cú pháp dưới dạng khối lớp, hoặc động, với lời gọi rõ ràng đến metaclass). Nó không thể thay đổi vì metaclass thường thực hiện các thay đổi ở thời gian tạo lớp - kiểu lớp được tạo ra là metaclasse. Thuộc tính __metaclass__ của nó không liên quan khi nó được tạo.

Tuy nhiên, có thể tạo bản sao của một lớp cụ thể và có bản sao có một lớp học khác với lớp gốc.

On ví dụ của bạn, nếu thay vì thực hiện:

Foo.__metaclass__ = MetaSub

bạn làm:

Foo = Metasub("Foo", Foo.__bases__, dict(Foo.__dict__))

Bạn sẽ đạt được những gì bạn dự định. Foo mới là cho tất cả các hiệu ứng tương đương với phiên bản tiền nhiệm của nó, nhưng với một metaclass khác.

Tuy nhiên, các trường hợp hiện tại trước đây là Foo sẽ không được coi là một phiên bản mới Foo - nếu bạn cần điều đó, bạn nên tạo tên Foo sao chép bằng một tên khác thay thế.

+0

Cảm ơn bạn đã không làm chính xác những gì tôi muốn, vì tôi vẫn muốn các lớp con của 'Foo' có metaclass, nhưng tôi đã chỉnh sửa câu hỏi của mình để bao gồm giải pháp đã làm những gì tôi cần, dựa trên gợi ý. –

3

Vấn đề là các thuộc tính __metaclass__ không được sử dụng khi được thừa hưởng, trái với những gì bạn có thể mong đợi. Metaclass 'cũ' không được gọi là Bar. Các tài liệu nói như sau về cách thức metaclass được tìm thấy:

các metaclass thích hợp được xác định bởi các ưu tiên quy tắc sau:

  • Nếu dict [ '__ metaclass__'] tồn tại, nó được sử dụng.
  • Nếu không, nếu có ít nhất một lớp cơ sở, metaclass của nó được sử dụng (điều này sẽ tìm thuộc tính __class__ trước và nếu không tìm thấy, sử dụng loại của nó).
  • Nếu không, nếu biến toàn cầu có tên __metaclass__ tồn tại, nó sẽ được sử dụng.
  • Nếu không, kiểu cũ, cổ điển metaclass (types.ClassType) được sử dụng.

Vì vậy, những gì đang thực sự sử dụng như metaclass trong lớp Bar của bạn được tìm thấy trong thuộc tính của cha mẹ __class__ và không có trong thuộc tính của cha mẹ __metaclass__.

Thông tin khác có thể được tìm thấy trên this StackOverflow answer.

1

Lớp con sử dụng __metaclass__ của cha mẹ của chúng.

Giải pháp cho trường hợp sử dụng của bạn là lập chương trình __metaclass__ của cha mẹ để nó có hành vi khác nhau đối với cha mẹ so với các lớp con của nó. Có lẽ nó kiểm tra từ điển lớp cho một biến lớp và thực hiện các hành vi khác nhau tùy thuộc vào giá trị (đây là kỹ thuật loại sử dụng để kiểm soát xem có hay không các trường hợp tùy thuộc vào việc có hay không __slots__).

+0

Trong ví dụ thực tế của tôi, lớp cha là từ mô-đun bên thứ ba và tôi muốn mở rộng một lớp từ mô-đun đó, nhưng có metaclass của riêng tôi ghi đè một số hành vi của metaclass của mô-đun bên thứ ba. Nếu không tôi chắc chắn sẽ làm những gì bạn đề nghị. –

+0

@EliCourtwright Có lẽ bạn có thể cuộn lớp của riêng bạn đại biểu cho mô-đun bên thứ ba thay vì kế thừa từ nó. Điều đó sẽ cho bạn toàn quyền kiểm soát quá trình tạo lớp trong khi tối đa hóa việc sử dụng lại mã. –

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