Gần đây tôi đã được hỏi cùng một câu hỏi và đã đưa ra một số câu trả lời. Tôi hy vọng có thể khôi phục chuỗi này, vì tôi muốn giải thích kỹ hơn về một số trường hợp sử dụng được đề cập và thêm một vài trường hợp mới.
Hầu hết các loại kính đeo mắt tôi từng thấy đều làm một trong hai điều:
Đăng ký (thêm một lớp vào cấu trúc dữ liệu):
models = {}
class ModelMetaclass(type):
def __new__(meta, name, bases, attrs):
models[name] = cls = type.__new__(meta, name, bases, attrs)
return cls
class Model(object):
__metaclass__ = ModelMetaclass
Bất cứ khi nào bạn phân lớp Model
, lớp của bạn được đăng ký trong models
từ điển:
>>> class A(Model):
... pass
...
>>> class B(A):
... pass
...
>>> models
{'A': <__main__.A class at 0x...>,
'B': <__main__.B class at 0x...>}
Điều này cũng có thể được thực hiện với trình trang trí lớp:
models = {}
def model(cls):
models[cls.__name__] = cls
return cls
@model
class A(object):
pass
Hoặc với chức năng đăng ký rõ ràng:
models = {}
def register_model(cls):
models[cls.__name__] = cls
class A(object):
pass
register_model(A)
Trên thực tế, điều này khá giống nhau: bạn đề cập đến trình trang trí lớp một cách không thuận lợi, nhưng nó thực sự không khác gì đường cú pháp cho một lệnh gọi hàm trên một lớp, vì vậy không có gì kỳ diệu về nó.
Dù sao, lợi thế của metaclasses trong trường hợp này là tính kế thừa, vì chúng hoạt động với bất kỳ lớp con nào, trong khi các giải pháp khác chỉ hoạt động đối với các lớp con được trang trí hoặc đăng ký rõ ràng.
>>> class B(A):
... pass
...
>>> models
{'A': <__main__.A class at 0x...> # No B :(
Cấu trúc lại (sửa đổi các thuộc tính của lớp hoặc thêm các thuộc tính mới):
class ModelMetaclass(type):
def __new__(meta, name, bases, attrs):
fields = {}
for key, value in attrs.items():
if isinstance(value, Field):
value.name = '%s.%s' % (name, key)
fields[key] = value
for base in bases:
if hasattr(base, '_fields'):
fields.update(base._fields)
attrs['_fields'] = fields
return type.__new__(meta, name, bases, attrs)
class Model(object):
__metaclass__ = ModelMetaclass
Bất cứ khi nào bạn phân lớp Model
và xác định một số Field
thuộc tính, chúng sẽ được đưa vào tên của chúng (ví dụ: đối với các thông báo lỗi nhiều thông tin hơn) và được nhóm vào một _fields
từ điển (để dễ dàng lặp lại mà không cần phải xem qua tất cả các thuộc tính của lớp và tất cả các lớp cơ sở của nó ' thuộc tính mọi lúc):
>>> class A(Model):
... foo = Integer()
...
>>> class B(A):
... bar = String()
...
>>> B._fields
{'foo': Integer('A.foo'), 'bar': String('B.bar')}
Một lần nữa, điều này có thể được thực hiện (không có kế thừa) với trình trang trí lớp:
def model(cls):
fields = {}
for key, value in vars(cls).items():
if isinstance(value, Field):
value.name = '%s.%s' % (cls.__name__, key)
fields[key] = value
for base in cls.__bases__:
if hasattr(base, '_fields'):
fields.update(base._fields)
cls._fields = fields
return cls
@model
class A(object):
foo = Integer()
class B(A):
bar = String()
# B.bar has no name :(
# B._fields is {'foo': Integer('A.foo')} :(
Hoặc rõ ràng:
class A(object):
foo = Integer('A.foo')
_fields = {'foo': foo} # Don't forget all the base classes' fields, too!
Mặc dù, trái ngược với sự ủng hộ của bạn đối với lập trình không phải meta có thể đọc được và có thể bảo trì được, thì điều này lại cồng kềnh, dư thừa và dễ xảy ra lỗi hơn nhiều:
class B(A):
bar = String()
# vs.
class B(A):
bar = String('bar')
_fields = {'B.bar': bar, 'A.foo': A.foo}
Đã xem xét các trường hợp sử dụng phổ biến và cụ thể nhất, các trường hợp duy nhất mà bạn hoàn toàn PHẢI sử dụng kính đo là khi bạn muốn sửa đổi tên lớp hoặc danh sách các lớp cơ sở, bởi vì sau khi được xác định, các tham số này được đưa vào lớp và không có trình trang trí hoặc chức năng có thể bỏ chúng.
class Metaclass(type):
def __new__(meta, name, bases, attrs):
return type.__new__(meta, 'foo', (int,), attrs)
class Baseclass(object):
__metaclass__ = Metaclass
class A(Baseclass):
pass
class B(A):
pass
print A.__name__ # foo
print B.__name__ # foo
print issubclass(B, A) # False
print issubclass(B, int) # True
Điều này có thể hữu ích trong các khuôn khổ để đưa ra cảnh báo bất cứ khi nào các lớp có tên tương tự hoặc cây kế thừa không đầy đủ được xác định, nhưng tôi không thể nghĩ ra lý do nào ngoài việc trolling để thực sự thay đổi các giá trị này. Có lẽ David Beazley có thể.
Dù sao, trong Python 3, metaclasses cũng có __prepare__
phương thức, cho phép bạn đánh giá phần thân của lớp thành một ánh xạ khác với a dict
, do đó hỗ trợ các thuộc tính có thứ tự, các thuộc tính quá tải và những thứ thú vị khác:
import collections
class Metaclass(type):
@classmethod
def __prepare__(meta, name, bases, **kwds):
return collections.OrderedDict()
def __new__(meta, name, bases, attrs, **kwds):
print(list(attrs))
# Do more stuff...
class A(metaclass=Metaclass):
x = 1
y = 2
# prints ['x', 'y'] rather than ['y', 'x']
class ListDict(dict):
def __setitem__(self, key, value):
self.setdefault(key, []).append(value)
class Metaclass(type):
@classmethod
def __prepare__(meta, name, bases, **kwds):
return ListDict()
def __new__(meta, name, bases, attrs, **kwds):
print(attrs['foo'])
# Do more stuff...
class A(metaclass=Metaclass):
def foo(self):
pass
def foo(self, x):
pass
# prints [<function foo at 0x...>, <function foo at 0x...>] rather than <function foo at 0x...>
Bạn có thể lập luận rằng các thuộc tính có thứ tự có thể đạt được với bộ đếm tạo và quá trình nạp chồng có thể được mô phỏng bằng các đối số mặc định:
import itertools
class Attribute(object):
_counter = itertools.count()
def __init__(self):
self._count = Attribute._counter.next()
class A(object):
x = Attribute()
y = Attribute()
A._order = sorted([(k, v) for k, v in vars(A).items() if isinstance(v, Attribute)],
key = lambda (k, v): v._count)
class A(object):
def _foo0(self):
pass
def _foo1(self, x):
pass
def foo(self, x=None):
if x is None:
return self._foo0()
else:
return self._foo1(x)
Bên cạnh việc xấu hơn nhiều, nó cũng kém linh hoạt hơn: điều gì sẽ xảy ra nếu bạn muốn các thuộc tính theo nghĩa đen có thứ tự, như số nguyên và chuỗi? Nếu None
là giá trị hợp lệ x
thì sao?
Đây là một cách sáng tạo để giải quyết vấn đề đầu tiên:
import sys
class Builder(object):
def __call__(self, cls):
cls._order = self.frame.f_code.co_names
return cls
def ordered():
builder = Builder()
def trace(frame, event, arg):
builder.frame = frame
sys.settrace(None)
sys.settrace(trace)
return builder
@ordered()
class A(object):
x = 1
y = 'foo'
print A._order # ['x', 'y']
Và đây là một cách sáng tạo để giải quyết vấn đề thứ hai:
_undefined = object()
class A(object):
def _foo0(self):
pass
def _foo1(self, x):
pass
def foo(self, x=_undefined):
if x is _undefined:
return self._foo0()
else:
return self._foo1(x)
Nhưng điều này còn hơn rất nhiều so với một chiếc siêu thủy tinh đơn giản (đặc biệt là chiếc đầu tiên, thứ thực sự làm tan chảy bộ não của bạn). Quan điểm của tôi là, bạn nhìn kính thiên văn là xa lạ và phản trực quan, nhưng bạn cũng có thể coi chúng như bước tiến hóa tiếp theo của ngôn ngữ lập trình: bạn chỉ cần điều chỉnh tư duy của mình. Rốt cuộc, bạn có thể làm mọi thứ trong C, bao gồm cả việc xác định một cấu trúc với các con trỏ hàm và chuyển nó làm đối số đầu tiên cho các hàm của nó. Một người nhìn thấy C ++ lần đầu tiên có thể nói, "điều kỳ diệu này là gì? Tại sao trình biên dịch lại ngầm chuyểnthis
cho các phương thức, nhưng không cho các hàm thông thường và hàm tĩnh? Tốt hơn hết là hãy trình bày rõ ràng và dài dòng về các lập luận của bạn ". Nhưng sau đó, lập trình hướng đối tượng sẽ mạnh hơn nhiều khi bạn nắm được nó; và điều này cũng vậy, uh ... lập trình hướng đối tượng, tôi đoán vậy. Và một khi bạn hiểu được kính thiên văn, chúng thực sự rất đơn giản, vậy tại sao không sử dụng chúng khi thuận tiện?
Và cuối cùng, kính đo là rad, và lập trình sẽ rất thú vị. Sử dụng các cấu trúc lập trình tiêu chuẩn và các mẫu thiết kế mọi lúc đều nhàm chán và kém hấp dẫn, đồng thời cản trở trí tưởng tượng của bạn. Sống ngắn ngủi! Đây là một metametaclass, dành riêng cho bạn.
class MetaMetaclass(type):
def __new__(meta, name, bases, attrs):
def __new__(meta, name, bases, attrs):
cls = type.__new__(meta, name, bases, attrs)
cls._label = 'Made in %s' % meta.__name__
return cls
attrs['__new__'] = __new__
return type.__new__(meta, name, bases, attrs)
class China(type):
__metaclass__ = MetaMetaclass
class Taiwan(type):
__metaclass__ = MetaMetaclass
class A(object):
__metaclass__ = China
class B(object):
__metaclass__ = Taiwan
print A._label # Made in China
print B._label # Made in Taiwan
Biên tập
Đây là một câu hỏi khá cũ, nhưng nó vẫn nhận được sự ủng hộ, vì vậy tôi nghĩ rằng tôi sẽ thêm một liên kết đến một câu trả lời toàn diện hơn. Nếu bạn muốn đọc thêm về kính đeo và công dụng của chúng, tôi vừa xuất bản một bài báo về nó ở đây .