2012-06-18 32 views
5

Sau this answer có vẻ như một lớp metaclass có thể thay đổi sau khi lớp học đã được xác định bằng cách sử dụng như sau *:Thiết lập một lớp metaclass sử dụng một trang trí

class MyMetaClass(type): 
    # Metaclass magic... 

class A(object): 
    pass 

A = MyMetaClass(A.__name__, A.__bases__, dict(A.__dict__)) 

Định nghĩa một hàm

def metaclass_wrapper(cls): 
    return MyMetaClass(cls.__name__, cls.__bases__, dict(cls.__dict__)) 

cho phép tôi để áp dụng một trang trí cho một định nghĩa lớp như vậy,

@metaclass_wrapper 
class B(object): 
    pass 

Nó thấy ms rằng ma thuật metaclass được áp dụng cho B, tuy nhiên B không có thuộc tính __metaclass__. Là các phương pháp trên một cách hợp lý để áp dụng metaclasses để định nghĩa lớp, mặc dù tôi đang definiting và tái definiting một lớp học, hoặc tôi sẽ khấm khá hơn chỉ đơn giản là viết

class B(object): 
    __metaclass__ = MyMetaClass 
    pass 

Tôi đoán có một số khác biệt giữa hai phương pháp.


* Lưu ý, câu trả lời ban đầu trong câu hỏi liên quan, MyMetaClass(A.__name__, A.__bases__, A.__dict__), trả về một TypeError:

TypeError: type() argument 3 must be a dict, not dict_proxy

Dường như __dict__ thuộc tính của A (định nghĩa lớp) có một loại dict_proxy, trong khi loại thuộc tính __dict__ của một ví dụ của A có loại dict. Tại sao điều này? Đây có phải là sự khác biệt giữa Python 2.x và 3.x không?

Trả lời

1

Lớp học không có thuộc tính __metaclass__ ... vì bạn chưa bao giờ đặt!

Sử dụng metaclass thường được xác định bởi tên __metaclass__ được đặt trong một khối lớp học. Thuộc tính __metaclass__ không được đặt bởi metaclass. Vì vậy, nếu bạn gọi trực tiếp một metaclass thay vì thiết lập __metaclass__ và cho phép Python tìm ra nó, thì không có thuộc tính __metaclass__ nào được đặt.

Trong thực tế, các lớp học bình thường là tất cả các trường của metaclass type, vì vậy nếu các metaclass luôn thiết lập các thuộc tính __metaclass__ về trường hợp của mình thì mỗi lớp sẽ có một thuộc tính __metaclass__ (hầu hết trong số họ thiết lập để type).


Tôi sẽ không sử dụng cách tiếp cận trang trí của bạn. Nó che khuất một thực tế là một metaclass có liên quan (và cái nào), vẫn là một dòng của boilerplate, và nó chỉ lộn xộn để tạo ra một lớp từ 3 tính năng định nghĩa của (name, bases, attributes) chỉ để kéo 3 bit đó ra khỏi lớp kết quả, ném lớp đi, và tạo một lớp mới từ 3 bit đó!

Khi bạn làm điều này bằng Python 2.x:

class A(object): 
    __metaclass__ = MyMeta 
    def __init__(self): 
     pass 

Bạn muốn được xấp xỉ kết quả tương tự nếu bạn đã viết bài này:

attrs = {} 
attrs['__metaclass__'] = MyMeta 
def __init__(self): 
    pass 
attrs['__init__'] = __init__ 
A = attrs.get('__metaclass__', type)('A', (object,), attrs) 

Trong thực tế tính toán metaclass là hơn phức tạp, vì thực sự phải tìm kiếm qua tất cả các căn cứ để xác định xem có xung đột metaclass hay không và nếu một trong các căn cứ không có type như metaclass và attrs không chứa __metaclass__ thì metaclass mặc định là metaclass của tổ tiên hơn là type. Đây là một tình huống mà tôi mong đợi "giải pháp" trang trí của bạn sẽ khác với việc sử dụng trực tiếp __metaclass__. Tôi không chắc chắn chính xác những gì sẽ xảy ra nếu bạn sử dụng trang trí của bạn trong một tình huống mà sử dụng __metaclass__ sẽ cung cấp cho bạn một lỗi xung đột metaclass, nhưng tôi sẽ không mong đợi nó được dễ chịu.

Ngoài ra, nếu có bất kỳ metaclasses nào khác, phương pháp của bạn sẽ dẫn đến việc chúng chạy đầu tiên (có thể sửa đổi tên, cơ sở và thuộc tính!) Và sau đó kéo chúng ra khỏi lớp và sử dụng nó để tạo lớp mới. Điều này có khả năng có thể khá khác với những gì bạn sẽ nhận được bằng cách sử dụng __metaclass__.

Đối với __dict__ không cung cấp cho bạn một từ điển thực sự, đó chỉ là chi tiết triển khai; Tôi đoán vì lý do hiệu suất. Tôi nghi ngờ có bất kỳ thông số nào nói rằng __dict__ của một cá thể (không phải lớp) phải cùng loại với __dict__ của một lớp (cũng là một cá thể btw; chỉ là một cá thể của một metaclass). Thuộc tính __dict__ của một lớp là "dictproxy", cho phép bạn tra cứu các khóa thuộc tính như thể nó là dict nhưng vẫn không phải là dict. type là cầu kỳ về loại đối số thứ ba của nó; nó muốn một dict thực sự, không chỉ là một đối tượng "giống như dict" (xấu hổ trên nó cho spoiling duck-typing). Nó không phải là một điều 2.x vs 3.x; Python 3 hoạt động theo cùng một cách, mặc dù nó cung cấp cho bạn một biểu diễn chuỗi đẹp hơn của dictproxy. Python 2.4 (là phiên bản 2.x cũ nhất mà tôi có sẵn) cũng có các đối tượng dictproxy cho các đối tượng lớp __dict__.

1

Tóm tắt câu hỏi của bạn: "Tôi đã thử một cách mới mẻ để làm một việc, và nó không hoàn toàn hiệu quả. Tôi có nên sử dụng cách đơn giản để thay thế không?"

Có, bạn nên làm điều đó một cách đơn giản. Bạn đã không nói lý do tại sao bạn quan tâm đến việc phát minh ra một cách mới để làm điều đó.

+0

Tôi đã tự hỏi liệu có một trang trí tương đương với việc đặt thuộc tính '__metaclass__' trong định nghĩa lớp hay không. Tôi đoán một phần khác của câu hỏi của tôi là 'trang trí' của tôi đã làm * có vẻ * để làm việc, nhưng tôi nhận thấy rằng lớp trang trí không có thuộc tính '__metaclass__', vì vậy rõ ràng có sự khác biệt giữa hai phương pháp; tại sao thế này – Chris

3

Phải thừa nhận rằng, tôi hơi muộn với bữa tiệc. Tuy nhiên, tôi đã giảm giá trị này.

Điều này hoàn toàn có thể thực hiện được. Điều đó đang được nói, có rất nhiều cách khác để hoàn thành cùng một mục tiêu. Tuy nhiên, giải pháp trang trí, đặc biệt, cho phép đánh giá chậm (obj = dec(obj)), sử dụng __metaclass__ bên trong lớp học không. Trong phong cách trang trí điển hình, giải pháp của tôi là dưới đây.

Có một điều phức tạp mà bạn có thể gặp phải nếu bạn chỉ xây dựng lớp mà không thay đổi từ điển hoặc sao chép thuộc tính của nó. Bất kỳ thuộc tính nào mà lớp đã có trước đó (trước khi trang trí) sẽ bị thiếu. Vì vậy, nó là hoàn toàn cần thiết để sao chép những hơn và sau đó tinh chỉnh chúng như tôi có trong giải pháp của tôi.

Cá nhân, tôi muốn có thể theo dõi cách đối tượng được bao bọc. Vì vậy, tôi đã thêm thuộc tính __wrapped__, điều này không thực sự cần thiết. Nó cũng làm cho nó giống như functools.wraps trong Python 3 cho các lớp học. Tuy nhiên, nó có thể hữu ích với nội tâm. Ngoài ra, __metaclass__ được thêm vào để hoạt động giống như trường hợp sử dụng metaclass bình thường.

def metaclass(meta): 
    def metaclass_wrapper(cls): 
     __name = str(cls.__name__) 
     __bases = tuple(cls.__bases__) 
     __dict = dict(cls.__dict__) 

     for each_slot in __dict.get("__slots__", tuple()): 
      __dict.pop(each_slot, None) 

     __dict["__metaclass__"] = meta 

     __dict["__wrapped__"] = cls 

     return(meta(__name, __bases, __dict)) 
    return(metaclass_wrapper) 

Ví dụ nhỏ, hãy thực hiện như sau.

class MetaStaticVariablePassed(type): 
    def __new__(meta, name, bases, dct): 
     dct["passed"] = True 

     return(super(MetaStaticVariablePassed, meta).__new__(meta, name, bases, dct)) 

@metaclass(MetaStaticVariablePassed) 
class Test(object): 
    pass 

Điều này mang lại kết quả tốt đẹp ...

|1> Test.passed 
|.> True 

Sử dụng trang trí trong ít hơn bình thường, nhưng cách giống hệt nhau ...

class Test(object): 
    pass 

Test = metaclass_wrapper(Test) 

... sản lượng, như mong đợi , cùng một kết quả tốt đẹp.

|1> Test.passed 
|.> True 
Các vấn đề liên quan