เมื่อเร็ว ๆ นี้ฉันถูกถามคำถามเดียวกันและมีคำตอบหลายคำตอบ ฉันหวังว่าการฟื้นฟูเธรดนี้จะเป็นเรื่องปกติเนื่องจากฉันต้องการอธิบายรายละเอียดเกี่ยวกับกรณีการใช้งานบางส่วนที่กล่าวถึงและเพิ่มใหม่สองสามอย่าง
แว่นตาส่วนใหญ่ที่ฉันเคยเห็นทำหนึ่งในสองสิ่ง:
การลงทะเบียน (การเพิ่มคลาสในโครงสร้างข้อมูล):
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
เมื่อใดก็ตามที่คุณซับคลาสคลาสModel
ของคุณจะถูกลงทะเบียนในmodels
พจนานุกรม:
>>> class A(Model):
... pass
...
>>> class B(A):
... pass
...
>>> models
{'A': <__main__.A class at 0x...>,
'B': <__main__.B class at 0x...>}
สิ่งนี้สามารถทำได้ด้วยนักตกแต่งชั้นเรียน:
models = {}
def model(cls):
models[cls.__name__] = cls
return cls
@model
class A(object):
pass
หรือด้วยฟังก์ชันการลงทะเบียนอย่างชัดเจน:
models = {}
def register_model(cls):
models[cls.__name__] = cls
class A(object):
pass
register_model(A)
อันที่จริงสิ่งนี้ก็เหมือนกันมาก: คุณพูดถึงนักตกแต่งในชั้นเรียนอย่างไม่น่าเชื่อ แต่จริงๆแล้วมันไม่มีอะไรมากไปกว่าน้ำตาลวากยสัมพันธ์สำหรับการเรียกใช้ฟังก์ชันในชั้นเรียนดังนั้นจึงไม่มีความมหัศจรรย์ใด ๆ
อย่างไรก็ตามข้อดีของ metaclasses ในกรณีนี้คือการถ่ายทอดทางพันธุกรรมเนื่องจากใช้กับคลาสย่อยใด ๆ ในขณะที่โซลูชันอื่นใช้ได้เฉพาะกับคลาสย่อยที่ได้รับการตกแต่งหรือลงทะเบียนอย่างชัดเจนเท่านั้น
>>> class B(A):
... pass
...
>>> models
{'A': <__main__.A class at 0x...> # No B :(
การปรับโครงสร้างใหม่ (การแก้ไขแอตทริบิวต์คลาสหรือเพิ่มใหม่):
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
เมื่อใดก็ตามที่คุณซับคลาสModel
และกำหนดField
แอตทริบิวต์บางอย่างพวกเขาจะถูกแทรกด้วยชื่อ (สำหรับข้อความแสดงข้อผิดพลาดที่ให้ข้อมูลเพิ่มเติมเป็นต้น) และจัดกลุ่มลงใน_fields
พจนานุกรม (เพื่อให้ง่ายต่อการทำซ้ำโดยไม่ต้องดูแอตทริบิวต์คลาสทั้งหมดและคลาสพื้นฐานทั้งหมด ' แอตทริบิวต์ทุกครั้ง):
>>> class A(Model):
... foo = Integer()
...
>>> class B(A):
... bar = String()
...
>>> B._fields
{'foo': Integer('A.foo'), 'bar': String('B.bar')}
อีกครั้งสิ่งนี้สามารถทำได้ (โดยไม่ต้องสืบทอด) ด้วยมัณฑนากรชั้นเรียน:
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')} :(
หรืออย่างชัดเจน:
class A(object):
foo = Integer('A.foo')
_fields = {'foo': foo} # Don't forget all the base classes' fields, too!
แม้ว่าในทางตรงกันข้ามกับการสนับสนุนของคุณสำหรับการเขียนโปรแกรมที่ไม่ใช่เมตาที่อ่านได้และบำรุงรักษาได้ แต่สิ่งนี้จะยุ่งยากซ้ำซ้อนและเกิดข้อผิดพลาดมากขึ้น:
class B(A):
bar = String()
# vs.
class B(A):
bar = String('bar')
_fields = {'B.bar': bar, 'A.foo': A.foo}
เมื่อพิจารณาถึงกรณีการใช้งานที่พบบ่อยและเป็นรูปธรรมแล้วกรณีเดียวที่คุณต้องใช้เมตาคลาสสิกอย่างแน่นอนคือเมื่อคุณต้องการแก้ไขชื่อคลาสหรือรายการคลาสพื้นฐานเนื่องจากเมื่อกำหนดพารามิเตอร์เหล่านี้จะรวมอยู่ในคลาสและไม่มีมัณฑนากร หรือฟังก์ชันสามารถปลดล็อคได้
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
สิ่งนี้อาจมีประโยชน์ในเฟรมเวิร์กสำหรับการออกคำเตือนเมื่อใดก็ตามที่มีการกำหนดคลาสที่มีชื่อคล้ายกันหรือโครงสร้างการสืบทอดที่ไม่สมบูรณ์ แต่ฉันไม่สามารถนึกถึงเหตุผลที่นอกเหนือจากการหลอกล่อที่จะเปลี่ยนค่าเหล่านี้ได้ บางที David Beazley ก็ทำได้
อย่างไรก็ตามใน Python 3 metaclasses ยังมี__prepare__
วิธีการซึ่งช่วยให้คุณสามารถประเมินเนื้อหาของคลาสในการแมปอื่นที่ไม่ใช่ a dict
ดังนั้นจึงรองรับแอตทริบิวต์ที่สั่งซื้อแอตทริบิวต์ที่โอเวอร์โหลดและสิ่งดีๆที่ชั่วร้ายอื่น ๆ :
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...>
คุณอาจโต้แย้งแอตทริบิวต์ที่สั่งซื้อได้โดยใช้ตัวนับการสร้างและสามารถจำลองการโอเวอร์โหลดได้ด้วยอาร์กิวเมนต์เริ่มต้น:
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)
นอกจากจะดูน่าเกลียดกว่ามากแล้วยังมีความยืดหยุ่นน้อยกว่า: ถ้าคุณต้องการสั่งแอตทริบิวต์ตามตัวอักษรเช่นจำนวนเต็มและสตริงล่ะ? เกิดอะไรขึ้นถ้าNone
เป็นค่าที่ถูกต้องสำหรับx
?
นี่เป็นวิธีที่สร้างสรรค์ในการแก้ปัญหาแรก:
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']
และนี่คือวิธีที่สร้างสรรค์ในการแก้ปัญหาข้อที่สอง:
_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)
แต่สิ่งนี้มีมากวูดูเอ้อมากกว่าเมตาคลาสสิกธรรมดา ๆ (โดยเฉพาะอย่างยิ่งอันแรกซึ่งทำให้สมองของคุณละลาย) ประเด็นของฉันคือคุณมองว่า metaclasses เป็นสิ่งที่ไม่คุ้นเคยและไม่ใช้งานง่าย แต่คุณยังสามารถมองว่ามันเป็นขั้นตอนต่อไปของวิวัฒนาการในภาษาโปรแกรม: คุณต้องปรับความคิดของคุณ ท้ายที่สุดคุณอาจทำทุกอย่างใน C ได้รวมถึงการกำหนดโครงสร้างด้วยตัวชี้ฟังก์ชันและส่งต่อเป็นอาร์กิวเมนต์แรกไปยังฟังก์ชันของมัน คนที่เห็น C ++ เป็นครั้งแรกอาจพูดว่า "เวทมนตร์นี้คืออะไรเหตุใดคอมไพเลอร์จึงส่งผ่านthis
ถึงวิธีการ แต่ไม่ใช่ฟังก์ชันปกติและแบบคงที่? เป็นการดีกว่าที่จะพูดอย่างชัดเจนและละเอียดเกี่ยวกับข้อโต้แย้งของคุณ "แต่แล้วการเขียนโปรแกรมเชิงวัตถุจะมีประสิทธิภาพมากขึ้นเมื่อคุณได้รับมันและนี่คือสิ่งนี้เอ่อ ... เข้าใจ metaclasses จริงๆแล้วมันง่ายมากทำไมไม่ใช้เมื่อสะดวกล่ะ?
และในที่สุด metaclasses ก็เป็น rad และการเขียนโปรแกรมควรเป็นเรื่องสนุก การใช้โครงสร้างการเขียนโปรแกรมมาตรฐานและรูปแบบการออกแบบตลอดเวลาเป็นเรื่องน่าเบื่อและไม่น่าสนใจและขัดขวางจินตนาการของคุณ อยู่น้อย! นี่คือ Metametaclass สำหรับคุณโดยเฉพาะ
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
แก้ไข
นี่เป็นคำถามที่ค่อนข้างเก่า แต่ก็ยังได้รับการโหวตเพิ่มขึ้นดังนั้นฉันจึงคิดว่าฉันจะเพิ่มลิงก์ไปยังคำตอบที่ครอบคลุมมากขึ้น หากคุณต้องการอ่านเพิ่มเติมเกี่ยวกับ metaclasses และการใช้งานของพวกเขาผมเพิ่งเผยแพร่บทความเกี่ยวกับเรื่องที่นี่