ما هي metaclasses في الثعبان ؟
-
01-07-2019 - |
سؤال
ما هي metaclasses و ماذا نفعل بها ؟
المحلول
أ metaclass هي فئة من فئة.فئة يعرف كيف مثيل من فئة (أيكائن) يتصرف في حين metaclass يعرف كيف فئة يتصرف.فئة مثيل من metaclass.
بينما في بيثون يمكنك استخدام التعسفي callables على metaclasses (مثل Jerub يظهر) ، فإن أفضل نهج هو أن تجعل من الدرجة الفعلية نفسها. type
هو المعتاد metaclass في بيثون. type
هو في حد ذاته الطبقة ، و هو نوع خاص بها.أنت لن تكون قادرة على إعادة شيء مثل type
بحتة في بيثون ، ولكن الثعبان غش قليلا.إنشاء الخاصة بك metaclass في بيثون كنت حقا تريد فقط فرعية type
.
أ metaclass هو الأكثر شيوعا كطبقة المصنع.عند إنشاء كائن من استدعاء فئة ، بيثون يخلق فئة جديدة (عندما ينفذ 'الفئة' البيان) عن طريق استدعاء metaclass.جنبا إلى جنب مع العادي __init__
و __new__
أساليب metaclasses ولذلك تسمح لك أن تفعل 'أشياء إضافية' عند إنشاء فئة ، مثل تسجيل فئة جديدة مع بعض التسجيل أو استبدال الطبقة مع شيء آخر تماما.
عندما class
بيان تنفيذ الثعبان الأول ينفذ جسم class
بيان العادي كتلة من التعليمات البرمجية.مما أدى مساحة (أ ديكت) يحمل سمات الطبقة ؟ على metaclass يتم تحديدها من خلال النظر في baseclasses الطبقة إلى (metaclasses موروثة) ، __metaclass__
سمة من فئة إلى أن (إن وجدت) أو __metaclass__
متغير عمومي.على metaclass ثم دعا مع اسم والقواعد سمات الطبقة مثيل.
ومع ذلك ، metaclasses في الواقع تحديد نوع من فئة ، ليس فقط مصنع لذا يمكنك أن تفعل أكثر من ذلك بكثير معهم.يمكنك ، على سبيل المثال ، تحديد الأساليب العادية على metaclass.هذه metaclass-طرق مثل classmethods في أنها يمكن أن يطلق على فئة دون مثيل ، ولكن هم أيضا لا يحبون classmethods في أنهم لا يمكن أن تسمى على سبيل المثال من الصف. type.__subclasses__()
مثال على الأسلوب على type
metaclass.يمكنك أيضا تحديد العادي 'السحر' أساليب مثل __add__
, __iter__
و __getattr__
, لتنفيذ أو تغيير كيفية الدرجة يتصرف.
هنا تجميع مثال أجزاء:
def make_hook(f):
"""Decorator to turn 'foo' method into '__foo__'"""
f.is_hook = 1
return f
class MyType(type):
def __new__(mcls, name, bases, attrs):
if name.startswith('None'):
return None
# Go over attributes and see if they should be renamed.
newattrs = {}
for attrname, attrvalue in attrs.iteritems():
if getattr(attrvalue, 'is_hook', 0):
newattrs['__%s__' % attrname] = attrvalue
else:
newattrs[attrname] = attrvalue
return super(MyType, mcls).__new__(mcls, name, bases, newattrs)
def __init__(self, name, bases, attrs):
super(MyType, self).__init__(name, bases, attrs)
# classregistry.register(self, self.interfaces)
print "Would register class %s now." % self
def __add__(self, other):
class AutoClass(self, other):
pass
return AutoClass
# Alternatively, to autogenerate the classname as well as the class:
# return type(self.__name__ + other.__name__, (self, other), {})
def unregister(self):
# classregistry.unregister(self)
print "Would unregister class %s now." % self
class MyObject:
__metaclass__ = MyType
class NoneSample(MyObject):
pass
# Will print "NoneType None"
print type(NoneSample), repr(NoneSample)
class Example(MyObject):
def __init__(self, value):
self.value = value
@make_hook
def add(self, other):
return self.__class__(self.value + other.value)
# Will unregister the class
Example.unregister()
inst = Example(10)
# Will fail with an AttributeError
#inst.unregister()
print inst + inst
class Sibling(MyObject):
pass
ExampleSibling = Example + Sibling
# ExampleSibling is now a subclass of both Example and Sibling (with no
# content of its own) although it will believe it's called 'AutoClass'
print ExampleSibling
print ExampleSibling.__mro__
نصائح أخرى
الطبقات كما الكائنات
قبل فهم metaclasses ، تحتاج إلى فصول رئيسية في بيثون.و الثعبان جدا غريبة فكرة الطبقات ، اقترضت من Smalltalk اللغة.
في معظم اللغات, فصول هي مجرد قطعة من التعليمات البرمجية التي توضح كيفية إنتاج كائن.هذا صحيح في بيثون أيضا:
>>> class ObjectCreator(object):
... pass
...
>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>
ولكن الطبقات هي أكثر من ذلك في بيثون.الطبقات هي الأشياء أيضا.
نعم الكائنات.
في أقرب وقت كما يمكنك استخدام الكلمات الرئيسية class
, الثعبان ينفذ ذلك يخلق
كائن.التعليمات
>>> class ObjectCreator(object):
... pass
...
يخلق في الذاكرة كائن مع اسم "ObjectCreator".
هذا الكائن (الطبقة) هو في حد ذاته قادر على خلق الأجسام (الحالات), و هذا هو السبب في أنه من فئة.
لكن ما هو كائن ، وبالتالي:
- يمكنك تعيين إلى متغير
- يمكنك نسخ ذلك
- يمكنك إضافة سمات إلى ذلك
- يمكنك تمرير ذلك بوصفها وظيفة معلمة
على سبيل المثال:
>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
... print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>
إنشاء فئات حيوي
منذ فئات الكائنات ، يمكنك إنشاء عليها على الطاير مثل أي كائن.
أولا, يمكنك إنشاء فئة في وظيفة باستخدام class
:
>>> def choose_class(name):
... if name == 'foo':
... class Foo(object):
... pass
... return Foo # return the class, not an instance
... else:
... class Bar(object):
... pass
... return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>
ولكن الأمر ليس بهذه الديناميكية, منذ كنت لا تزال لديك لكتابة كل فئة نفسك.
منذ فئات الكائنات ، يجب أن تكون ولدت من شيء.
عند استخدام class
الكلمات الرئيسية ، بيثون يخلق هذا الكائن تلقائيا.ولكن كما
مع معظم الأشياء في بيثون ، فهو يوفر لك طريقة للقيام بذلك يدويا.
تذكر وظيفة type
?حسن البالغ من العمر وظيفة التي تمكنك من معرفة ما
نوع كائن هو:
>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>
حسنا ، type
مختلف تماما عن القدرة ، فإنه يمكن أيضا إنشاء فصول على الطاير. type
يمكن أن تأخذ وصف فئة المعلمات ،
والعودة فئة.
(أعرف أنها سخيفة أن نفس وظيفة يمكن أن يكون اثنين مختلفة تماما يستخدم وفقا لمعايير الذي تمر عليه.انها قضية يرجع إلى الوراء التوافق في بايثون)
type
يعمل بهذه الطريقة:
type(name of the class,
tuple of the parent class (for inheritance, can be empty),
dictionary containing attributes names and values)
على سبيل المثال:
>>> class MyShinyClass(object):
... pass
يمكن إنشاء يدويا بهذه الطريقة:
>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>
ستلاحظ أن نستخدم "MyShinyClass" كما اسم الفئة وكما المتغير إلى عقد إشارة الدرجة.أنها يمكن أن تكون مختلفة ، ولكن ليس هناك ما يدعو إلى تعقيد الأمور.
type
تقبل قاموس تحديد سمات الطبقة.لذلك:
>>> class Foo(object):
... bar = True
يمكن ترجمتها إلى:
>>> Foo = type('Foo', (), {'bar':True})
و تستخدم الطبقة العادية:
>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True
و بالطبع يمكن أن ترث منه ، لذلك:
>>> class FooChild(Foo):
... pass
سيكون:
>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True
في نهاية المطاف سوف تحتاج إلى إضافة أساليب الفئة الخاصة بك.فقط تعريف وظيفة مع توقيع المناسبة وتعيين كسمة.
>>> def echo_bar(self):
... print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True
ويمكنك إضافة المزيد من أساليب بعد حيوي إنشاء فئة مثل إضافة أساليب عادة إنشاء كائن الفئة.
>>> def echo_bar_more(self):
... print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True
ترى أين نحن ذاهبون:في بيثون ، الطبقات والكائنات يمكنك إنشاء فئة على الطاير, حيوي.
هذا ما بيثون عندما تستخدم الكلمة class
, وذلك باستخدام metaclass.
ما هي metaclasses (أخيرا)
Metaclasses هي الاشياء التي تخلق الطبقات.
يمكنك تحديد فئات من أجل خلق الكائنات ، أليس كذلك ؟
ولكن علمنا أن الثعبان فئات الكائنات.
حسنا, metaclasses ما خلق هذه الكائنات.هم فئات' الطبقات ، يمكنك أن الصورة لهم على هذا النحو:
MyClass = MetaClass()
my_object = MyClass()
كنت قد رأيت أن type
يتيح لك أن تفعل شيئا من هذا القبيل:
MyClass = type('MyClass', (), {})
لأن وظيفة type
هو في الواقع metaclass. type
هو
metaclass الثعبان يستخدم لإنشاء جميع الطبقات من وراء الكواليس.
والآن كنت أتساءل لماذا هيك هو مكتوب في صغيرة و لا Type
?
أعتقد انها مسألة الاتساق مع str
, الفئة أن يخلق
سلاسل الكائنات ، int
الفئة التي يخلق صحيح الكائنات. type
هو
فقط الفئة التي يخلق كائنات فئة.
ترى أن طريق التحقق __class__
السمة.
كل شيء و أعني كل شيء هو كائن في بيثون.يتضمن رجات ، سلاسل ووظائف الطبقات.كل منهم الكائنات.وكل منهم قد تم إنشاؤها من فئة:
>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>
الآن, ما هو __class__
أي __class__
?
>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>
لذلك ، metaclass هي فقط الأشياء التي يخلق كائنات فئة.
هل يمكن أن نسميها 'مصنع فئة' إذا كنت ترغب في ذلك.
type
المدمج في metaclass يستخدم الثعبان, ولكن بالطبع يمكنك إنشاء الخاصة بك
الخاصة metaclass.
على __metaclass__
السمة
في بيثون 2, يمكنك إضافة __metaclass__
السمة عند كتابة فئة (انظر القسم التالي من أجل بيثون 3 جملة):
class Foo(object):
__metaclass__ = something...
[...]
إذا كنت تفعل ذلك ، بيثون استخدام metaclass إلى إنشاء فئة Foo
.
كن حذرا صعبة.
تكتب class Foo(object)
الأولى, ولكن فئة الكائن Foo
لا يتم إنشاء
في الذاكرة حتى الآن.
الثعبان سوف ابحث عن __metaclass__
في تعريف الفئة.إذا كان يرى ذلك ،
فإنه سيتم استخدامه لإنشاء فئة الكائن Foo
.إذا كان لا, فإنه سيتم استخدام
type
لإنشاء فئة.
قراءة ذلك عدة مرات.
عندما تفعل:
class Foo(Bar):
pass
بيثون يفعل ما يلي:
هل هناك __metaclass__
سمة في Foo
?
إذا كان الجواب نعم ، خلق في الذاكرة كائن فئة (قلت كائن فئة ابق معي هنا) ، Foo
باستخدام ما هو في __metaclass__
.
إذا كان الثعبان لا يمكن العثور على __metaclass__
, ، وسوف ابحث عن __metaclass__
مستوى الوحدة النمطية ، حاول أن تفعل الشيء نفسه (ولكن فقط الفئات التي لا ترث أي شيء أساسا على الطراز القديم فصول).
ثم إذا كان لا يمكن العثور على أي __metaclass__
في استخدام Bar
's (الأصل الأول) الخاصة metaclass (الذي قد يكون الافتراضي type
) لإنشاء كائن الفئة.
كن حذرا هنا أن __metaclass__
السمة لن تكون موروثة ، metaclass الوالد (Bar.__class__
) سوف يكون.إذا Bar
تستخدم __metaclass__
السمة التي خلقت Bar
مع type()
(و لا type.__new__()
) ، فرعية لا ترث هذا السلوك.
الآن السؤال الكبير هو: ماذا يمكن أن تضع في __metaclass__
?
الجواب هو:شيء التي يمكن أن تخلق فئة.
وما يمكن أن تخلق الصف ؟ type
, أو أي شيء فرعية أو يستخدمه.
Metaclasses في بيثون 3
جملة لضبط metaclass تم تغيير في بيثون 3:
class Foo(object, metaclass=something):
...
أيعلى __metaclass__
السمة لم تعد تستخدم ، لصالح الكلمة حجة في قائمة قاعدة الطبقات.
سلوك metaclasses ومع ذلك يبقى إلى حد كبير نفس.
شيء واحد إضافة إلى metaclasses في بيثون 3 هو أنه يمكنك أيضا تمرير الصفات كما الكلمة-الحجج في metaclass مثل:
class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
...
قراءة أدناه للحصول كيف بيثون يعالج هذا.
مخصص metaclasses
الغرض الرئيسي من metaclass هو تغيير الطبقة تلقائيا ، عندما يتم إنشاؤها.
يمكنك عادة القيام بذلك عن واجهات برمجة التطبيقات ، حيث تريد إنشاء فصول مطابقة السياق الحالي.
تخيل مثال غبي, حيث تقرر أن جميع الطبقات في الوحدة النمطية الخاص بك
يجب أن صفاتهم مكتوب في أحرف كبيرة.هناك عدة طرق
هل هذا لكن طريقة واحدة هي مجموعة __metaclass__
مستوى الوحدة النمطية.
بهذه الطريقة, كل دروس هذه الوحدة سيتم إنشاؤها باستخدام هذا metaclass, و علينا فقط أن أقول metaclass لتحويل جميع الصفات إلى أحرف كبيرة.
لحسن الحظ ، __metaclass__
يمكن أن يكون في الواقع أي للاستدعاء ، فإنه لا حاجة إلى أن يكون
الدرجة الرسمي (أعرف شيئا مع 'الفئة' في اسمها لا تحتاج إلى أن تكون
فئة ، انتقل الرقم...لكنها مفيدة).
لذلك نحن سوف نبدأ مع مثال بسيط باستخدام وظيفة.
# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attr):
"""
Return a class object, with the list of its attribute turned
into uppercase.
"""
# pick up any attribute that doesn't start with '__' and uppercase it
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
# let `type` do the class creation
return type(future_class_name, future_class_parents, uppercase_attr)
__metaclass__ = upper_attr # this will affect all classes in the module
class Foo(): # global __metaclass__ won't work with "object" though
# but we can define __metaclass__ here instead to affect only this class
# and this will work with "object" children
bar = 'bip'
print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True
f = Foo()
print(f.BAR)
# Out: 'bip'
الآن, دعونا نفعل بالضبط نفس ، ولكن باستخدام الدرجة metaclass:
# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
# __new__ is the method called before __init__
# it's the method that creates the object and returns it
# while __init__ just initializes the object passed as parameter
# you rarely use __new__, except when you want to control how the object
# is created.
# here the created object is the class, and we want to customize it
# so we override __new__
# you can do some stuff in __init__ too if you wish
# some advanced use involves overriding __call__ as well, but we won't
# see this
def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attr):
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return type(future_class_name, future_class_parents, uppercase_attr)
ولكن هذا ليس حقا OOP.ونحن ندعو type
مباشرة ونحن لا تجاوز
أو دعوة الوالد __new__
.دعونا نفعل ذلك:
class UpperAttrMetaclass(type):
def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attr):
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
# reuse the type.__new__ method
# this is basic OOP, nothing magic in there
return type.__new__(upperattr_metaclass, future_class_name,
future_class_parents, uppercase_attr)
كنت قد لاحظت إضافية الحجة upperattr_metaclass
.هناك
لا شيء خاص حول هذا الموضوع: __new__
دائما يحصل على الدرجة المحددة في المعلمة الأولى.مثل لديك self
على الطرق العادية التي تتلقى سبيل المثال كما المعلمة الأولى أو تحديد فئة على فئة الأساليب.
بالطبع أسماء كنت هنا طويلة من أجل التوضيح ، ولكن مثل
بالنسبة self
, كل الحجج التقليدية الأسماء.لذا الإنتاج الحقيقي
metaclass تبدو مثل هذا:
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return type.__new__(cls, clsname, bases, uppercase_attr)
ونحن يمكن أن تجعل حتى أكثر نظافة باستخدام super
, والتي سوف تخفف من الميراث (لأن نعم ، هل يمكن أن يكون metaclasses ، وراثة من metaclasses ، وراثة من نوع):
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)
و في بيثون 3 إذا كنت تفعل هذه المكالمة مع الكلمات الرئيسية الحجج, مثل هذا:
class Foo(object, metaclass=Thing, kwarg1=value1):
...
فإنه يترجم إلى هذا في metaclass لاستخدامه:
class Thing(type):
def __new__(cls, clsname, bases, dct, kwargs1=default):
...
هذا هو.هناك حقا أي شيء أكثر حول metaclasses.
السبب وراء تعقيد التعليمات البرمجية باستخدام metaclasses ليس بسبب
من metaclasses, لأنك عادة استخدام metaclasses للقيام ملتوية
والاعتماد على الذات التلاعب في الميراث ، فأر مثل __dict__
, ، وما إلى ذلك.
بل metaclasses هي مفيدة بشكل خاص للقيام السحر الأسود ، وبالتالي أشياء معقدة.ولكن في حد ذاتها ، فهي بسيطة:
- اعتراض إنشاء فئة
- تعديل الدرجة
- عودة الطبقة تعديل
لماذا تستخدم metaclasses الطبقات بدلا من الوظائف ؟
منذ __metaclass__
لا يمكن أن يقبل أي للاستدعاء ، لماذا كنت تستخدم فئة
لأنه من الواضح أكثر تعقيدا ؟
هناك عدة أسباب لذلك:
- القصد واضح.عندما تقرأ
UpperAttrMetaclass(type)
, تعلم ماذا سوف تتبع - يمكنك استخدام OOP.Metaclass يمكن أن ترث من metaclass تجاوز الوالدين الأساليب.Metaclasses حتى يمكن استخدام metaclasses.
- فرعية من فئة يكون فيها metaclass إذا حددت metaclass الدرجة, ولكن ليس مع metaclass-وظيفة.
- يمكنك بنية التعليمات البرمجية الخاصة بك على نحو أفضل.كنت تستخدم أبدا metaclasses شيئا تافهة كما في المثال أعلاه.انها عادة ما تكون من أجل شيء معقد.وجود القدرة على اتخاذ عدة طرق و مجموعة منهم في صف واحد مفيد جدا لجعل رمز أسهل في القراءة.
- يمكنك ربط على
__new__
,__init__
و__call__
.والتي سوف تسمح لك أن تفعل أشياء مختلفة.حتى لو كان عادة يمكنك أن تفعل كل شيء في__new__
, بعض الناس أكثر راحة باستخدام__init__
. - وتسمى هذه metaclasses, اللعنة!يجب أن يعني شيئا!
لماذا تستخدم metaclasses?
الآن السؤال الكبير.لماذا تستخدم بعض غامضة خطأ عرضة الميزة ؟
عادة لا:
Metaclasses هي أعمق السحر الذي 99% من المستخدمين يجب أن تقلق أبدا.إذا كنت أتساءل ما إذا كنت في حاجة إليها ، لا (الناس الذين في الواقع في حاجة إليها نعلم علم اليقين أن أنهم بحاجة لهم و لا تحتاج تفسير عن السبب).
بيثون المعلم تيم بيترز
الاستخدام الرئيسي الحال بالنسبة metaclass هو خلق API.والمثال النموذجي على ذلك هو جانغو ORM.
فإنه يسمح لك لتحديد شيئا من هذا القبيل:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
ولكن إذا كنت تفعل هذا:
guy = Person(name='bob', age='35')
print(guy.age)
لن يعود IntegerField
الكائن.فإنه سيتم إعادة int
, و يمكن حتى تأخذ مباشرة من قاعدة البيانات.
وهذا ممكن لأن models.Model
يعرف __metaclass__
و
ويستخدم بعض السحر التي سوف تتحول Person
كنت مجرد تعريف بسيط البيانات
إلى مجمع ربط حقل قاعدة البيانات.
جانغو يجعل شيئا معقدة تبدو بسيطة عن طريق تعريض بسيط API واستخدام metaclasses ، وإعادة رمز من هذا API للقيام بهذا العمل الحقيقي وراء الكواليس.
الكلمة الأخيرة
أولا تعلم أن الطبقات هي الأشياء التي يمكن أن تخلق حالات.
حسنا في الواقع ، الطبقات أنفسهم الحالات.من metaclasses.
>>> class Foo(object): pass
>>> id(Foo)
142630324
كل ما هو كائن في بيثون ، وهم جميعا إما حالات الطبقات أو حالات metaclasses.
باستثناء type
.
type
هو في الواقع الخاصة metaclass.هذا ليس شيئا يمكن أن
تتكاثر في بيثون نقية ، ويتم ذلك عن طريق الغش قليلا في تنفيذ
المستوى.
ثانيا ، metaclasses معقدة.قد لا ترغب في استخدامها بسيطة جدا الدرجة التعديلات.يمكنك تغيير الفصول باستخدام أساليب مختلفة اثنين:
- القرد الترقيع
- فئة ديكور
99 ٪ من الوقت تحتاجين تغيير, كنت أفضل حالا باستخدام هذه.
ولكن 98% من الوقت, أنت لا تحتاج إلى الطبقة تغيير على الإطلاق.
ملاحظة هذا الجواب هو بيثون 2.x كما هو مكتوب في عام 2008 ، metaclasses هي مختلفة قليلا في 3.x.
Metaclasses هي الخلطة السرية التي تجعل 'الفئة' العمل.الافتراضي metaclass على نمط جديد كائن يسمى 'نوع'.
class type(object)
| type(object) -> the object's type
| type(name, bases, dict) -> a new type
Metaclasses يستغرق 3 وسائط.'اسم', 'قواعد'و 'ديكت'
هنا هو المكان السري يبدأ.البحث عن أين اسم والقواعد ديكت في هذا المثال تعريف الفئة.
class ThisIsTheName(Bases, Are, Here):
All_the_code_here
def doesIs(create, a):
dict
يتيح تحديد metaclass من شأنها أن توضح كيف 'الدرجة:'يدعو له.
def test_metaclass(name, bases, dict):
print 'The Class Name is', name
print 'The Class Bases are', bases
print 'The dict has', len(dict), 'elems, the keys are', dict.keys()
return "yellow"
class TestName(object, None, int, 1):
__metaclass__ = test_metaclass
foo = 1
def baz(self, arr):
pass
print 'TestName = ', repr(TestName)
# output =>
The Class Name is TestName
The Class Bases are (<type 'object'>, None, <type 'int'>, 1)
The dict has 4 elems, the keys are ['baz', '__module__', 'foo', '__metaclass__']
TestName = 'yellow'
و الآن, مثال ذلك يعني في الواقع شيئا ، وهذا يؤدي تلقائيا إلى جعل المتغيرات في قائمة "سمات" على الطبقة ، و تعيين إلى لا شيء.
def init_attributes(name, bases, dict):
if 'attributes' in dict:
for attr in dict['attributes']:
dict[attr] = None
return type(name, bases, dict)
class Initialised(object):
__metaclass__ = init_attributes
attributes = ['foo', 'bar', 'baz']
print 'foo =>', Initialised.foo
# output=>
foo => None
علما أن السحر السلوك 'Initalised' المكاسب من خلال وجود metaclass init_attributes
لا تنتقل إلى فئة فرعية من Initalised.
هنا هو أكثر ملموسة سبيل المثال ، تبين كيف يمكنك فرعية 'نوع' لجعل metaclass يؤدي العمل عند إنشاء الطبقة.هذا صعب جدا:
class MetaSingleton(type):
instance = None
def __call__(cls, *args, **kw):
if cls.instance is None:
cls.instance = super(MetaSingleton, cls).__call__(*args, **kw)
return cls.instance
class Foo(object):
__metaclass__ = MetaSingleton
a = Foo()
b = Foo()
assert a is b
استخدام واحد metaclasses هو إضافة خصائص جديدة و أساليب مثيل تلقائيا.
على سبيل المثال, إذا نظرتم جانغو نماذج, ، تعريف يبدو مربكا بعض الشيء.يبدو كما لو كنت فقط تحديد خصائص الطبقة:
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
ومع ذلك ، في وقت التشغيل الشخص الكائنات هي مليئة بكل أنواع من الأساليب المفيدة.ترى المصدر لبعض مذهلة metaclassery.
آخرون شرح كيف metaclasses العمل وكيف تنسجم مع بيثون نوع النظام.هنا مثال على ما يمكن أن تستخدم.في إطار اختبار كتبت أردت أن تتبع الترتيب الذي فئات محددة ، بحيث يمكن في وقت لاحق مثيل لهم في هذا الأمر.لقد وجدت أنه من الأسهل أن تفعل هذا باستخدام metaclass.
class MyMeta(type):
counter = 0
def __init__(cls, name, bases, dic):
type.__init__(cls, name, bases, dic)
cls._order = MyMeta.counter
MyMeta.counter += 1
class MyType(object): # Python 2
__metaclass__ = MyMeta
class MyType(metaclass=MyMeta): # Python 3
pass
أي فئة فرعية من MyType
ثم يحصل على سمة الفئة _order
الذي يسجل في فئات محددة.
أعتقد أن ONLamp مقدمة metaclass البرمجة هو مكتوب بشكل جيد ويعطي حقا مقدمة جيدة لهذا الموضوع على الرغم من كونها عدة سنوات بالفعل.
http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html (المحفوظة في https://web.archive.org/web/20080206005253/http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html)
باختصار:فئة مخطط إنشاء مثيل ، metaclass هو مخطط إنشاء فئة.يمكن أن ينظر إليه بسهولة في بيثون دروس يجب أن تكون كائنات من الدرجة الأولى أيضا لتمكين هذا السلوك.
لم يسبق لي أن كتبت واحدة نفسي ، ولكن أعتقد أن واحدة من اجمل يستخدم من metaclasses يمكن أن ينظر إليه في جانغو إطار.نموذج الطبقات تستخدم metaclass نهج لتمكين التعريفي أسلوب الكتابة نماذج جديدة أو شكل فصول.في حين metaclass هو خلق فئة ، جميع الأعضاء على إمكانية تخصيص الطبقة نفسها.
الشيء الذي غادر إلى قوله:إذا كنت لا تعرف ما metaclasses ، احتمال أن كنت لن تحتاج لهم هو 99%.
ما هي metaclasses?ماذا كنت تستخدم لهم ؟
TLDR:أ metaclass instantiates يحدد سلوك فئة مثل فئة instantiates يحدد السلوك على سبيل المثال.
شبة الكود:
>>> Class(...)
instance
ما سبق يجب أن تبدو مألوفة.حسنا, أين Class
تأتي من ؟ إنه مثيل metaclass (أيضا شبة الكود):
>>> Metaclass(...)
Class
في رمز حقيقي, نحن يمكن أن تمر الافتراضي metaclass, type
, كل شيء نحن بحاجة إلى إنشاء مثيل فئة ونحن الحصول على الدرجة:
>>> type('Foo', (object,), {}) # requires a name, bases, and a namespace
<class '__main__.Foo'>
وضعه بشكل مختلف
فئة هو مثيل كما metaclass إلى فئة.
عندما كنا مثيل كائن ، نحصل على سبيل المثال:
>>> object() # instantiation of class <object object at 0x7f9069b4e0b0> # instance
وبالمثل ، عندما نحدد الطبقة صراحة مع الافتراضي metaclass,
type
, نحن مثيل:>>> type('Object', (object,), {}) # instantiation of metaclass <class '__main__.Object'> # instance
بعبارة أخرى ، فئة مثيل من metaclass:
>>> isinstance(object, type) True
وضع الطريق الثالث ، metaclass هي فئة الدرجة.
>>> type(object) == type True >>> object.__class__ <class 'type'>
عند كتابة تعريف الفئة و الثعبان ينفذ ذلك ، فإنه يستخدم metaclass إلى مثيل كائن الفئة (والتي بدورها تستخدم في إنشاء مثيلات تلك الفئة).
كما يمكننا استخدام تعريفات فئة لتغيير كيفية مخصص مثيلات الكائن التصرف ، يمكننا استخدام metaclass تعريف الفئة إلى تغيير طريقة كائن فئة يتصرف.
ماذا يمكن استخدامها ؟ من مستندات:
الاستخدامات المحتملة على metaclasses لا حدود لها.بعض الأفكار التي تم بحثها تشمل قطع الأشجار ، واجهة التحقق التلقائي وفد التلقائي خاصية إنشاء الوكلاء ، أطر التلقائي الموارد تأمين/المزامنة.
ومع ذلك ، فإنه عادة ما تشجع المستخدمين على تجنب استخدام metaclasses إلا عند الضرورة القصوى.
يمكنك استخدام metaclass في كل مرة تقوم بإنشاء فئة:
عند كتابة تعريف الفئة ، على سبيل المثال ، من هذا القبيل ،
class Foo(object):
'demo'
يمكنك إنشاء مثيل فئة الكائن.
>>> Foo
<class '__main__.Foo'>
>>> isinstance(Foo, type), isinstance(Foo, object)
(True, True)
فإنه هو نفسه كما وظيفيا الدعوة type
مع الحجج المناسبة و إسناد النتيجة إلى متغير بهذا الاسم:
name = 'Foo'
bases = (object,)
namespace = {'__doc__': 'demo'}
Foo = type(name, bases, namespace)
ملاحظة بعض الأشياء تلقائيا الحصول على إضافة إلى __dict__
, أي مساحة الاسم:
>>> Foo.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Foo' objects>,
'__module__': '__main__', '__weakref__': <attribute '__weakref__'
of 'Foo' objects>, '__doc__': 'demo'})
على metaclass الكائن خلقنا ، في كلتا الحالتين ، type
.
(الجانب ملاحظة على محتويات الطبقة __dict__
: __module__
هل هناك لأن الطبقات يجب أن تعرف أين هم محددة ، __dict__
و __weakref__
هل هناك لأننا لا تعرف __slots__
- إذا كنا تعريف __slots__
سوف حفظ قليلا من الفضاء في حالات ، كما يمكننا أن عدم السماح __dict__
و __weakref__
من خلال استبعاد لهم.على سبيل المثال:
>>> Baz = type('Bar', (object,), {'__doc__': 'demo', '__slots__': ()})
>>> Baz.__dict__
mappingproxy({'__doc__': 'demo', '__slots__': (), '__module__': '__main__'})
...لكن هذا ليس موضوعنا)
ونحن يمكن أن تمتد type
تماما مثل أي فئة أخرى التعريف:
وهنا الافتراضي __repr__
من الطبقات:
>>> Foo
<class '__main__.Foo'>
واحدة من أهم الأشياء التي يمكننا القيام به بشكل افتراضي في كتابة الثعبان الهدف من ذلك هو تقديم جيد __repr__
.عندما ندعو help(repr)
ونحن نعلم أن هناك اختبارا جيدا بالنسبة __repr__
هذا أيضا يتطلب اختبار المساواة ، obj == eval(repr(obj))
.البسيطة التالية تنفيذ __repr__
و __eq__
لفئة حالات من نوع الدرجة يوفر لنا مع المظاهرة التي قد تحسن على الافتراضي __repr__
من الطبقات:
class Type(type):
def __repr__(cls):
"""
>>> Baz
Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
>>> eval(repr(Baz))
Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
"""
metaname = type(cls).__name__
name = cls.__name__
parents = ', '.join(b.__name__ for b in cls.__bases__)
if parents:
parents += ','
namespace = ', '.join(': '.join(
(repr(k), repr(v) if not isinstance(v, type) else v.__name__))
for k, v in cls.__dict__.items())
return '{0}(\'{1}\', ({2}), {{{3}}})'.format(metaname, name, parents, namespace)
def __eq__(cls, other):
"""
>>> Baz == eval(repr(Baz))
True
"""
return (cls.__name__, cls.__bases__, cls.__dict__) == (
other.__name__, other.__bases__, other.__dict__)
حتى الآن عندما نقوم بإنشاء كائن مع هذا metaclass ، __repr__
ردد على سطر الأوامر توفر أقل بكثير القبيح مرأى من الافتراضي:
>>> class Bar(object): pass
>>> Baz = Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
>>> Baz
Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
مع لطيفة __repr__
تعريف الطبقة سبيل المثال ، لدينا قدرة أقوى على تصحيح التعليمات البرمجية لدينا.ولكن أبعد من ذلك بكثير التحقق مع eval(repr(Class))
من غير المرجح (مثل الوظائف بدلا من المستحيل eval من الافتراضي __repr__
's).
المتوقع الاستخدام: __prepare__
مساحة
إذا ، على سبيل المثال ، نريد أن نعرف ما هو ترتيب فئة أساليب يتم إنشاؤها في, نحن يمكن أن توفر أمر ديكت حسب مساحة الصف.ونحن سوف تفعل هذا مع __prepare__
التي إرجاع مساحة ديكت لفئة إذا تم تنفيذه في بيثون 3:
from collections import OrderedDict
class OrderedType(Type):
@classmethod
def __prepare__(metacls, name, bases, **kwargs):
return OrderedDict()
def __new__(cls, name, bases, namespace, **kwargs):
result = Type.__new__(cls, name, bases, dict(namespace))
result.members = tuple(namespace)
return result
و الاستخدام:
class OrderedMethodsObject(object, metaclass=OrderedType):
def method1(self): pass
def method2(self): pass
def method3(self): pass
def method4(self): pass
والآن لدينا سجل من ترتيب هذه الأساليب (وغيرها من سمات الطبقة) تم إنشاؤها:
>>> OrderedMethodsObject.members
('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4')
ملاحظة هذا المثال تم تكييفها من الوثائق - الجديد التعداد في المكتبة القياسية هل هذا.
ما فعلناه كان مثيل metaclass من خلال خلق فئة.يمكننا أيضا معالجة metaclass كما كنا أي فئة أخرى.أنه يحتوي على طريقة القرار من أجل:
>>> inspect.getmro(OrderedType)
(<class '__main__.OrderedType'>, <class '__main__.Type'>, <class 'type'>, <class 'object'>)
ولها ما يقرب من الصحيح repr
(الذي لم يعد بإمكاننا eval ما لم نتمكن من إيجاد وسيلة لتمثيل لدينا وظائف.):
>>> OrderedMethodsObject
OrderedType('OrderedMethodsObject', (object,), {'method1': <function OrderedMethodsObject.method1 at 0x0000000002DB01E0>, 'members': ('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4'), 'method3': <function OrderedMet
hodsObject.method3 at 0x0000000002DB02F0>, 'method2': <function OrderedMethodsObject.method2 at 0x0000000002DB0268>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'OrderedMethodsObject' objects>, '__doc__': None, '__d
ict__': <attribute '__dict__' of 'OrderedMethodsObject' objects>, 'method4': <function OrderedMethodsObject.method4 at 0x0000000002DB0378>})
بيثون 3 التحديث
هناك (في هذه النقطة) اثنين من الطرق الرئيسية في metaclass:
__prepare__
, ،__new__
__prepare__
يتيح لك العرض مخصص رسم الخرائط (مثل OrderedDict
) أن يكون استخدام مساحة الاسم في حين أن الطبقة يجري إنشاؤه.يجب العودة مثيل مهما كان الاسم الذي تختاره.إذا كنت لا تنفذ __prepare__
عادي dict
يتم استخدامه.
__new__
هو المسؤول الفعلي إنشاء/تعديل الدرجة النهائية.
عارية العظام, تفعل أي شيء إضافي metaclass مثل:
class Meta(type):
def __prepare__(metaclass, cls, bases):
return dict()
def __new__(metacls, cls, bases, clsdict):
return super().__new__(metacls, cls, bases, clsdict)
مثال بسيط:
تقول انك تريد بعض بسيطة التحقق من صحة التعليمات البرمجية لتشغيل على الصفات الخاصة بك -- مثل دائما يجب أن يكون int
أو str
.دون metaclass ، صفك سوف ننظر بشيء من مثل:
class Person:
weight = ValidateType('weight', int)
age = ValidateType('age', int)
name = ValidateType('name', str)
كما يمكنك أن ترى, عليك أن تكرار اسم السمة مرتين.وهذا يجعل من الأخطاء المطبعية ممكن جنبا إلى جنب مع غضب البق.
بسيطة metaclass يمكن معالجة هذه المشكلة:
class Person(metaclass=Validator):
weight = ValidateType(int)
age = ValidateType(int)
name = ValidateType(str)
هذا هو ما metaclass تبدو مثل (لا تستخدم __prepare__
لأنه لا حاجة):
class Validator(type):
def __new__(metacls, cls, bases, clsdict):
# search clsdict looking for ValidateType descriptors
for name, attr in clsdict.items():
if isinstance(attr, ValidateType):
attr.name = name
attr.attr = '_' + name
# create final class and return it
return super().__new__(metacls, cls, bases, clsdict)
تشغيل عينة من:
p = Person()
p.weight = 9
print(p.weight)
p.weight = '9'
ينتج:
9
Traceback (most recent call last):
File "simple_meta.py", line 36, in <module>
p.weight = '9'
File "simple_meta.py", line 24, in __set__
(self.name, self.type, value))
TypeError: weight must be of type(s) <class 'int'> (got '9')
ملاحظة:هذا المثال هو بسيط بما فيه الكفاية أنه يمكن أن يكون أيضا تم إنجازه مع فئة الديكور ، ولكن يفترض الفعلي metaclass أن تفعل أكثر من ذلك بكثير.
إن 'ValidateType' الفئة للرجوع إليها:
class ValidateType:
def __init__(self, type):
self.name = None # will be set by metaclass
self.attr = None # will be set by metaclass
self.type = type
def __get__(self, inst, cls):
if inst is None:
return self
else:
return inst.__dict__[self.attr]
def __set__(self, inst, value):
if not isinstance(value, self.type):
raise TypeError('%s must be of type(s) %s (got %r)' %
(self.name, self.type, value))
else:
inst.__dict__[self.attr] = value
دور metaclass' __call__()
طريقة عند إنشاء مثيل فئة
إذا كنت قد فعلت البرمجة بايثون أكثر من بضعة أشهر في نهاية المطاف سوف تتعثر رمز التي تبدو مثل هذا:
# define a class
class SomeClass(object):
# ...
# some definition here ...
# ...
# create an instance of it
instance = SomeClass()
# then call the object as if it's a function
result = instance('foo', 'bar')
وهذا الأخير هو ممكن عند تنفيذ __call__()
طريقة سحرية على الدرجة.
class SomeClass(object):
# ...
# some definition here ...
# ...
def __call__(self, foo, bar):
return bar + foo
على __call__()
طريقة الاحتجاج عندما مثيل فئة يستخدم للاستدعاء.ولكن كما رأينا من الإجابات السابقة فئة في حد ذاته هو مثيل metaclass ، حتى عندما نستخدم الفئة المستحقة (أيعندما نقوم بإنشاء مثيل من ذلك) نحن في الحقيقة داعيا metaclass' __call__()
الأسلوب.في هذه المرحلة أكثر الثعبان المبرمجين مشوشة قليلا لأنها قد قيل أنه عند إنشاء مثيل مثل هذا instance = SomeClass()
كنت داعيا __init__()
الأسلوب.بعض الذين كنت حفر أعمق قليلا أعرف ذلك من قبل __init__()
هناك __new__()
.حسنا, اليوم طبقة أخرى من الحقيقة التي كشفت قبل __new__()
هناك metaclass' __call__()
.
دعونا دراسة طريقة استدعاء سلسلة من وجه التحديد منظور إنشاء مثيل فئة.
هذا هو metaclass أن سجلات بالضبط لحظة قبل أن يتم إنشاء مثيل و ذلك عن إعادته.
class Meta_1(type):
def __call__(cls):
print "Meta_1.__call__() before creating an instance of ", cls
instance = super(Meta_1, cls).__call__()
print "Meta_1.__call__() about to return instance."
return instance
هذه هي الفئة التي تستخدم هذا metaclass
class Class_1(object):
__metaclass__ = Meta_1
def __new__(cls):
print "Class_1.__new__() before creating an instance."
instance = super(Class_1, cls).__new__(cls)
print "Class_1.__new__() about to return instance."
return instance
def __init__(self):
print "entering Class_1.__init__() for instance initialization."
super(Class_1,self).__init__()
print "exiting Class_1.__init__()."
والآن دعونا إنشاء مثيل Class_1
instance = Class_1()
# Meta_1.__call__() before creating an instance of <class '__main__.Class_1'>.
# Class_1.__new__() before creating an instance.
# Class_1.__new__() about to return instance.
# entering Class_1.__init__() for instance initialization.
# exiting Class_1.__init__().
# Meta_1.__call__() about to return instance.
نلاحظ أن الكود أعلاه لا تفعل في الواقع أي شيء أكثر من تسجيل المهام.كل طريقة المندوبين العمل الفعلي إلى الوالدين التنفيذ ، وبالتالي الحفاظ على السلوك الافتراضي.منذ type
هو Meta_1
's الفئة الأصل (type
كونها الأم الافتراضي metaclass) و النظر في ترتيب تسلسل الناتج أعلاه ، لدينا الآن دليل على ما يمكن أن يكون الزائفة تنفيذ type.__call__()
:
class type:
def __call__(cls, *args, **kwarg):
# ... maybe a few things done to cls here
# then we call __new__() on the class to create an instance
instance = cls.__new__(cls, *args, **kwargs)
# ... maybe a few things done to the instance here
# then we initialize the instance with its __init__() method
instance.__init__(*args, **kwargs)
# ... maybe a few more things done to instance here
# then we return it
return instance
يمكننا أن نرى أن metaclass' __call__()
الأسلوب هو الذي يسمى الأولى.ثم المندوبين إنشاء مثيل لفئة هو __new__()
طريقة التهيئة على سبيل المثال هو __init__()
.كما أنه واحد في نهاية المطاف أن ترجع سبيل المثال.
من فوقه ينبع أن metaclass' __call__()
هو أيضا الفرصة لتقرر ما إذا كان أو ليس دعوة إلى Class_1.__new__()
أو Class_1.__init__()
في نهاية المطاف سوف تكون مصنوعة.خلال تنفيذها يمكن أن تعود في الواقع كائن التي لم تطرق من قبل أي من هذه الأساليب.خذ على سبيل المثال هذا النهج إلى نمط المفرد:
class Meta_2(type):
singletons = {}
def __call__(cls, *args, **kwargs):
if cls in Meta_2.singletons:
# we return the only instance and skip a call to __new__()
# and __init__()
print ("{} singleton returning from Meta_2.__call__(), "
"skipping creation of new instance.".format(cls))
return Meta_2.singletons[cls]
# else if the singleton isn't present we proceed as usual
print "Meta_2.__call__() before creating an instance."
instance = super(Meta_2, cls).__call__(*args, **kwargs)
Meta_2.singletons[cls] = instance
print "Meta_2.__call__() returning new instance."
return instance
class Class_2(object):
__metaclass__ = Meta_2
def __new__(cls, *args, **kwargs):
print "Class_2.__new__() before creating instance."
instance = super(Class_2, cls).__new__(cls)
print "Class_2.__new__() returning instance."
return instance
def __init__(self, *args, **kwargs):
print "entering Class_2.__init__() for initialization."
super(Class_2, self).__init__()
print "exiting Class_2.__init__()."
دعونا نلاحظ ماذا يحدث عندما تحاول مرارا وتكرارا إنشاء كائن من نوع Class_2
a = Class_2()
# Meta_2.__call__() before creating an instance.
# Class_2.__new__() before creating instance.
# Class_2.__new__() returning instance.
# entering Class_2.__init__() for initialization.
# exiting Class_2.__init__().
# Meta_2.__call__() returning new instance.
b = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.
c = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.
a is b is c # True
أ metaclass هي الطبقة التي تروي كيف (بعض) فئة أخرى يجب إنشاء.
هذا هو حال رأيت metaclass حل مشكلتي:لدي مشكلة معقدة جدا, التي ربما قد تم حلها بشكل مختلف, ولكن اخترت أن حلها باستخدام metaclass.بسبب التعقيد ، وهي واحدة من عدد قليل من وحدات كتبت فيها تعليقات في وحدة تتجاوز كمية من التعليمات البرمجية التي تم كتابتها.ومن هنا...
#!/usr/bin/env python
# Copyright (C) 2013-2014 Craig Phillips. All rights reserved.
# This requires some explaining. The point of this metaclass excercise is to
# create a static abstract class that is in one way or another, dormant until
# queried. I experimented with creating a singlton on import, but that did
# not quite behave how I wanted it to. See now here, we are creating a class
# called GsyncOptions, that on import, will do nothing except state that its
# class creator is GsyncOptionsType. This means, docopt doesn't parse any
# of the help document, nor does it start processing command line options.
# So importing this module becomes really efficient. The complicated bit
# comes from requiring the GsyncOptions class to be static. By that, I mean
# any property on it, may or may not exist, since they are not statically
# defined; so I can't simply just define the class with a whole bunch of
# properties that are @property @staticmethods.
#
# So here's how it works:
#
# Executing 'from libgsync.options import GsyncOptions' does nothing more
# than load up this module, define the Type and the Class and import them
# into the callers namespace. Simple.
#
# Invoking 'GsyncOptions.debug' for the first time, or any other property
# causes the __metaclass__ __getattr__ method to be called, since the class
# is not instantiated as a class instance yet. The __getattr__ method on
# the type then initialises the class (GsyncOptions) via the __initialiseClass
# method. This is the first and only time the class will actually have its
# dictionary statically populated. The docopt module is invoked to parse the
# usage document and generate command line options from it. These are then
# paired with their defaults and what's in sys.argv. After all that, we
# setup some dynamic properties that could not be defined by their name in
# the usage, before everything is then transplanted onto the actual class
# object (or static class GsyncOptions).
#
# Another piece of magic, is to allow command line options to be set in
# in their native form and be translated into argparse style properties.
#
# Finally, the GsyncListOptions class is actually where the options are
# stored. This only acts as a mechanism for storing options as lists, to
# allow aggregation of duplicate options or options that can be specified
# multiple times. The __getattr__ call hides this by default, returning the
# last item in a property's list. However, if the entire list is required,
# calling the 'list()' method on the GsyncOptions class, returns a reference
# to the GsyncListOptions class, which contains all of the same properties
# but as lists and without the duplication of having them as both lists and
# static singlton values.
#
# So this actually means that GsyncOptions is actually a static proxy class...
#
# ...And all this is neatly hidden within a closure for safe keeping.
def GetGsyncOptionsType():
class GsyncListOptions(object):
__initialised = False
class GsyncOptionsType(type):
def __initialiseClass(cls):
if GsyncListOptions._GsyncListOptions__initialised: return
from docopt import docopt
from libgsync.options import doc
from libgsync import __version__
options = docopt(
doc.__doc__ % __version__,
version = __version__,
options_first = True
)
paths = options.pop('<path>', None)
setattr(cls, "destination_path", paths.pop() if paths else None)
setattr(cls, "source_paths", paths)
setattr(cls, "options", options)
for k, v in options.iteritems():
setattr(cls, k, v)
GsyncListOptions._GsyncListOptions__initialised = True
def list(cls):
return GsyncListOptions
def __getattr__(cls, name):
cls.__initialiseClass()
return getattr(GsyncListOptions, name)[-1]
def __setattr__(cls, name, value):
# Substitut option names: --an-option-name for an_option_name
import re
name = re.sub(r'^__', "", re.sub(r'-', "_", name))
listvalue = []
# Ensure value is converted to a list type for GsyncListOptions
if isinstance(value, list):
if value:
listvalue = [] + value
else:
listvalue = [ None ]
else:
listvalue = [ value ]
type.__setattr__(GsyncListOptions, name, listvalue)
# Cleanup this module to prevent tinkering.
import sys
module = sys.modules[__name__]
del module.__dict__['GetGsyncOptionsType']
return GsyncOptionsType
# Our singlton abstract proxy class.
class GsyncOptions(object):
__metaclass__ = GetGsyncOptionsType()
type
هو في الواقع metaclass
-- فئة يخلق آخر فصول.معظم metaclass
هي فرعية من type
.على metaclass
يتلقى new
صف أول حجة لها وتوفير إمكانية الوصول إلى كائن الفئة مع التفاصيل كما هو مذكور أدناه:
>>> class MetaClass(type):
... def __init__(cls, name, bases, attrs):
... print ('class name: %s' %name )
... print ('Defining class %s' %cls)
... print('Bases %s: ' %bases)
... print('Attributes')
... for (name, value) in attrs.items():
... print ('%s :%r' %(name, value))
...
>>> class NewClass(object, metaclass=MetaClass):
... get_choch='dairy'
...
class name: NewClass
Bases <class 'object'>:
Defining class <class 'NewClass'>
get_choch :'dairy'
__module__ :'builtins'
__qualname__ :'NewClass'
Note:
لاحظ أن فئة لا مثيل في أي وقت ؛ قانون بسيط من خلق فئة أثار تنفيذ metaclass
.
Tl;dr الإصدار
على type(obj)
وظيفة يحصل لك نوع من كائن.
على type()
من فئة هي metaclass.
استخدام metaclass:
class Foo(object):
__metaclass__ = MyMetaClass
بيثون الطبقات هي نفسها الأشياء - كما في المثال - من الطبقة الفوقية.
الافتراضي metaclass ، والتي يتم تطبيقها عند تحديد فئات كما يلي:
class foo:
...
ميتا فئة تستخدم في تطبيق بعض القوانين إلى مجموعة كاملة من الطبقات.على سبيل المثال, افترض أنك تقوم ببناء اسندت إلى الوصول إلى قاعدة البيانات وتريد السجلات من كل جدول من فئة تعيينها إلى ذلك الجدول (استنادا إلى الحقول ، قواعد العمل ، الخ..،) ، من الممكن استخدام metaclass هو على سبيل المثال ، تجمع الاتصال المنطق الذي هو مشاركة جميع فئات سجل من جميع الجداول.استخدام آخر هو منطق لدعم المفاتيح الخارجية ، الذي ينطوي على فئات متعددة من السجلات.
عند تعريف metaclass ، نوع فرعية ، ويمكن أن overrided التالية السحر طرق لإدراج المنطق الخاص بك.
class somemeta(type):
__new__(mcs, name, bases, clsdict):
"""
mcs: is the base metaclass, in this case type.
name: name of the new class, as provided by the user.
bases: tuple of base classes
clsdict: a dictionary containing all methods and attributes defined on class
you must return a class object by invoking the __new__ constructor on the base metaclass.
ie:
return type.__call__(mcs, name, bases, clsdict).
in the following case:
class foo(baseclass):
__metaclass__ = somemeta
an_attr = 12
def bar(self):
...
@classmethod
def foo(cls):
...
arguments would be : ( somemeta, "foo", (baseclass, baseofbase,..., object), {"an_attr":12, "bar": <function>, "foo": <bound class method>}
you can modify any of these values before passing on to type
"""
return type.__call__(mcs, name, bases, clsdict)
def __init__(self, name, bases, clsdict):
"""
called after type has been created. unlike in standard classes, __init__ method cannot modify the instance (cls) - and should be used for class validaton.
"""
pass
def __prepare__():
"""
returns a dict or something that can be used as a namespace.
the type will then attach methods and attributes from class definition to it.
call order :
somemeta.__new__ -> type.__new__ -> type.__init__ -> somemeta.__init__
"""
return dict()
def mymethod(cls):
""" works like a classmethod, but for class objects. Also, my method will not be visible to instances of cls.
"""
pass
على أية حال, تلك هما الأكثر استخداما السنانير.metaclassing قوية ، وما فوق من مكان قريب و قائمة شاملة من الاستخدامات metaclassing.
نوع وظيفة() يمكن العودة إلى نوع من كائن أو إنشاء نوع جديد ،
على سبيل المثال, يمكننا إنشاء مرحبا الدرجة مع نوع() وظيفة لا تحتاج إلى استخدام هذه الطريقة مع الطبقة مرحبا(كائن):
def func(self, name='mike'):
print('Hi, %s.' % name)
Hi = type('Hi', (object,), dict(hi=func))
h = Hi()
h.hi()
Hi, mike.
type(Hi)
type
type(h)
__main__.Hi
بالإضافة إلى استخدام نوع() إلى إنشاء فصول حيوي ، يمكنك التحكم في خلق سلوك الطبقة واستخدام metaclass.
وفقا الثعبان نموذج كائن الفئة هو كائن ، حتى الصف يجب أن يكون مثيل آخر فئة معينة.بشكل افتراضي ، بيثون فئة سبيل المثال من نوع الفئة.هذا هو نوع metaclass معظم المدمج في الطبقات metaclass من المعرفة من قبل المستخدم الطبقات.
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
class CustomList(list, metaclass=ListMetaclass):
pass
lst = CustomList()
lst.add('custom_list_1')
lst.add('custom_list_2')
lst
['custom_list_1', 'custom_list_2']
السحر المفعول عند مرت علينا الكلمة الحجج في metaclass ، فإنه يشير إلى مترجم بايثون لخلق CustomList خلال ListMetaclass. جديد () في هذه المرحلة يمكننا تعديل تعريف الفئة ، على سبيل المثال ، إضافة طريقة جديدة ومن ثم العودة التعريف المنقح.
بالإضافة إلى نشر إجابات أستطيع أن أقول أن metaclass
يحدد السلوك للحصول على الدرجة.لذا يمكنك بشكل صريح تعيين metaclass.كلما الثعبان يحصل على الكلمة class
ثم يبدأ في البحث عن metaclass
.اذا كان ليس موجود الافتراضي metaclass نوع يستخدم لإنشاء فئة الكائن.باستخدام __metaclass__
السمة ، يمكنك تعيين metaclass
الفئة:
class MyClass:
__metaclass__ = type
# write here other method
# write here one more method
print(MyClass.__metaclass__)
سوف تنتج المخرجات مثل هذا:
class 'type'
و بالطبع يمكنك إنشاء الخاصة بك metaclass
لتحديد سلوك أي الطبقة التي يتم إنشاؤها باستخدام الفئة الخاصة بك.
من أجل القيام بذلك ، الافتراضي الخاص بك metaclass
نوع الدرجة يجب أن تكون ورثت هذا هو الرئيسي metaclass
:
class MyMetaClass(type):
__metaclass__ = type
# you can write here any behaviour you want
class MyTestClass:
__metaclass__ = MyMetaClass
Obj = MyTestClass()
print(Obj.__metaclass__)
print(MyMetaClass.__metaclass__)
فإن الناتج يكون:
class '__main__.MyMetaClass'
class 'type'