سؤال

ما هي 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'
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top