Một khối lớp là khoảng cú pháp đường để xây dựng một từ điển, và sau đó gọi một metaclass để xây dựng đối tượng lớp.
này:
class Foo(object):
__metaclass__ = FooMeta
FOO = 123
def a(self):
pass
Comes ra khá nhiều, nếu như bạn đã viết:
d = {}
d['__metaclass__'] = FooMeta
d['FOO'] = 123
def a(self):
pass
d['a'] = a
Foo = d.get('__metaclass__', type)('Foo', (object,), d)
Chỉ nếu không có sự ô nhiễm không gian tên (và trong thực tế cũng có một tìm kiếm thông qua tất cả các căn cứ để xác định metaclass, hoặc cho dù có một cuộc xung đột metaclass, nhưng tôi bỏ qua điều đó ở đây).
Các metaclass' __setattr__
có thể kiểm soát những gì xảy ra khi bạn cố gắng thiết lập một thuộc tính trên một trong các trường hợp của nó (đối tượng lớp), nhưng bên trong khối lớp bạn không làm điều đó, bạn đang chèn vào một đối tượng từ điển, do đó, lớp dict
kiểm soát những gì đang xảy ra, chứ không phải metaclass của bạn. Vì vậy, bạn đang ra khỏi may mắn.
Trừ khi bạn đang sử dụng Python 3.x! Trong Python 3.x bạn có thể định nghĩa một lớp học __prepare__
classmethod (hoặc staticmethod) trên metaclass, điều khiển đối tượng nào được sử dụng để tích lũy các thuộc tính được đặt trong một khối lớp trước khi chúng được truyền cho hàm tạo metaclass. Giá trị mặc định __prepare__
chỉ đơn giản là trả về một từ điển thông thường, nhưng bạn có thể xây dựng một lớp dict giống như tùy chỉnh mà không cho phép các phím được định nghĩa lại, và sử dụng để tích lũy thuộc tính của bạn:
from collections import MutableMapping
class SingleAssignDict(MutableMapping):
def __init__(self, *args, **kwargs):
self._d = dict(*args, **kwargs)
def __getitem__(self, key):
return self._d[key]
def __setitem__(self, key, value):
if key in self._d:
raise ValueError(
'Key {!r} already exists in SingleAssignDict'.format(key)
)
else:
self._d[key] = value
def __delitem__(self, key):
del self._d[key]
def __iter__(self):
return iter(self._d)
def __len__(self):
return len(self._d)
def __contains__(self, key):
return key in self._d
def __repr__(self):
return '{}({!r})'.format(type(self).__name__, self._d)
class RedefBlocker(type):
@classmethod
def __prepare__(metacls, name, bases, **kwargs):
return SingleAssignDict()
def __new__(metacls, name, bases, sad):
return super().__new__(metacls, name, bases, dict(sad))
class Okay(metaclass=RedefBlocker):
a = 1
b = 2
class Boom(metaclass=RedefBlocker):
a = 1
b = 2
a = 3
Chạy này mang lại cho tôi:
Traceback (most recent call last):
File "/tmp/redef.py", line 50, in <module>
class Boom(metaclass=RedefBlocker):
File "/tmp/redef.py", line 53, in Boom
a = 3
File "/tmp/redef.py", line 15, in __setitem__
'Key {!r} already exists in SingleAssignDict'.format(key)
ValueError: Key 'a' already exists in SingleAssignDict
một số lưu ý:
__prepare__
có phải là một classmethod
hoặc staticmethod
, bởi vì nó được gọi là trước ngày e metaclass 'instance (class của bạn) tồn tại.
type
vẫn cần tham số thứ ba của nó là một thực dict
, vì vậy bạn cần phải có một phương pháp __new__
có thể chuyển đổi các SingleAssignDict
để một bình thường một
- tôi có thể subclassed
dict
, mà có lẽ có thể tránh được (2), nhưng Tôi thực sự không thích làm điều đó bởi vì các phương pháp phi cơ bản như update
không tôn trọng phần ghi đè của bạn về các phương thức cơ bản như __setitem__
. Vì vậy, tôi thích phân lớp collections.MutableMapping
và bọc từ điển.
- Đối tượng
Okay.__dict__
thực tế là từ điển thông thường, vì nó được đặt bởi type
và type
là khó tính về loại từ điển mà nó muốn. Điều này có nghĩa là ghi đè lên các thuộc tính lớp sau khi tạo lớp không làm tăng ngoại lệ. Bạn có thể ghi đè thuộc tính __dict__
sau khi gọi superclass trong __new__
nếu bạn muốn duy trì không ghi đè do từ điển của đối tượng lớp.
Đáng buồn là kỹ thuật này không có sẵn bằng Python 2.x (tôi đã kiểm tra). Phương thức __prepare__
không được gọi, có ý nghĩa như trong Python 2.x metaclass được xác định bởi thuộc tính ma thuật __metaclass__
thay vì một từ khóa đặc biệt trong khoanh vùng; có nghĩa là đối tượng dict được sử dụng để tích lũy các thuộc tính cho khối lớp đã tồn tại vào thời điểm metaclass được biết.
Hãy so sánh Python 2:
class Foo(object):
__metaclass__ = FooMeta
FOO = 123
def a(self):
pass
Là tương đương với:
d = {}
d['__metaclass__'] = FooMeta
d['FOO'] = 123
def a(self):
pass
d['a'] = a
Foo = d.get('__metaclass__', type)('Foo', (object,), d)
Trường hợp metaclass để gọi được xác định từ vào từ điển, so với Python 3:
class Foo(metaclass=FooMeta):
FOO = 123
def a(self):
pass
Being tương đương với:
d = FooMeta.__prepare__('Foo',())
d['Foo'] = 123
def a(self):
pass
d['a'] = a
Foo = FooMeta('Foo',(), d)
Từ điển sử dụng được xác định từ metaclass.
vấn đề chính xác này là lý do tại sao cú pháp metaclass đã thay đổi trong py3! – SingleNegationElimination