python - ما هي metaclasses في بايثون؟




oop python-datamodel (13)

ما هي metaclasses وماذا نستخدمها؟


Answers

إن metaclass هو صنف الصف. يعرّف الفصل الدراسي كيف يتصرف مثيل من الصف ، ويعرّف metaclass كيف تتصرف فئة. الطبقة هي مثال على طبقة ميتاتشيلاز.

بينما في بايثون يمكنك استخدام الخطافات الاستبدادية للمتقدمات (مثل عروض Jerub ) ، فإن المقاربة الأكثر فائدة هي في الواقع جعلها طبقة حقيقية بحد ذاتها. type هو metaclass المعتاد في Python. في حال كنت تتساءل ، نعم ، type هو نفسه فئة ، وهو نوعه الخاص. لن تكون قادرًا على إعادة إنشاء شيء مثل type تمامًا في بايثون ، لكن بيثون تغش قليلاً. لإنشاء metaclass الخاص بك في بيثون كنت تريد فقط type الطبقة الفرعية.

تستخدم الطبقة الشاهدة الأكثر شيوعًا كمصنّع للفصل. عندما تقوم بإنشاء مثيل للفصل الدراسي عن طريق استدعاء الفصل ، تقوم بايثون بإنشاء فئة جديدة (عندما تقوم بتنفيذ العبارة "class") عن طريق استدعاء metaclass. __new__ الطريقتين __new__ __init__ و __new__ ، تسمح لك metaclasses "بالأشياء الإضافية" عند إنشاء فصل ، مثل تسجيل الصف الجديد مع بعض التسجيل ، أو حتى استبدال الطبقة بشيء آخر تمامًا.

عند تنفيذ عبارة class ، تقوم Python أولاً بتنفيذ نص بيان class ككتلة عادية من التعليمات البرمجية. تحمل مساحة الاسم الناتجة (dict) سمات class-to-be. يتم تحديد الطبقة المادية من خلال النظر في البينات الأساسية للفئة-إلى-أن (يتم توارث metaclasses) ، في سمة __metaclass__ للفئة-إلى-أو (إن وجد) أو المتغير العام __metaclass__ . ثم يتم استدعاء metaclass مع اسم وأسس وسمات الفئة لتكوينه.

ومع ذلك ، تحدد بالفعل metaclasses نوع الطبقة ، وليس مجرد مصنع لذلك ، حتى تتمكن من القيام بالمزيد معهم. يمكنك ، على سبيل المثال ، تحديد الطرق العادية على 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 ، تحتاج إلى إتقان دروس في بيثون. ولدى بايثون فكرة غريبة للغاية عن الطبقات ، مستعارة من لغة سمولتوك.

في معظم اللغات ، تكون الطبقات عبارة عن أجزاء من التعليمات البرمجية التي تصف كيفية إنتاج كائن. هذا صحيح جدا في بايثون أيضا:

>>> class ObjectCreator(object):
...       pass
...

>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

لكن الفصول أكثر من تلك الموجودة في بايثون. الطبقات هي كائنات أيضا.

نعم ، الأشياء.

بمجرد استخدامك class الكلمة الرئيسية ، يقوم Python بتنفيذها وإنشاء كائن. التعليمات

>>> 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 ، تنشئ Python هذا الكائن تلقائيًا. ولكن كما هو الحال مع معظم الأشياء في بايثون ، فإنه يوفر لك طريقة للقيام بذلك يدويا.

تذكر type الوظيفة؟ الوظيفة القديمة الجيدة التي تمكنك من معرفة نوع الكائن:

>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

حسنا ، type لديه قدرة مختلفة تماما ، ويمكن أيضا إنشاء دروس على الطاير. يمكن أن يأخذ type وصفًا للفصل كمعلمات ، وإرجاع فصل دراسي.

(أعلم أنه من السخف أن نفس الوظيفة يمكن أن يكون لها استخدامين مختلفين تمامًا وفقًا للمعلمات التي تمررها إليها. إنها مشكلة بسبب التوافق مع الإصدارات السابقة في Python)

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 (أخيرا)

الفوقية هي "الأشياء" التي تخلق الطبقات.

يمكنك تحديد الطبقات من أجل إنشاء كائنات ، أليس كذلك؟

لكننا تعلمنا أن دروس Python هي كائنات.

حسنا ، هي metaclasses خلق هذه الأشياء. إنها فصول الدروس ، يمكنك تصويرها بهذه الطريقة:

MyClass = MetaClass()
my_object = MyClass()

لقد رأيت هذا type يتيح لك القيام بشيء كالتالي:

MyClass = type('MyClass', (), {})

ذلك لأن type الوظيفة هو في الواقع metaclass. type هي الطبقة المادية التي تستخدمها بايثون لإنشاء جميع الطبقات خلف الكواليس.

الآن تتساءل لماذا هيك مكتوب بالحرف الصغير ، وليس 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__

في Python 2 ، يمكنك إضافة سمة __metaclass__ عند كتابة فصل __metaclass__ (راجع القسم التالي لبناء جملة Python 3):

class Foo(object):
    __metaclass__ = something...
    [...]

إذا قمت بذلك ، فإن Python ستستخدم metaclass لإنشاء Class Foo .

حذرا ، انها صعبة.

يمكنك كتابة class Foo(object) أولاً ، ولكن لا يتم إنشاء كائن الفئة Foo في الذاكرة حتى الآن.

ستبحث بايثون عن __metaclass__ في تعريف الفئة. إذا وجدته ، Foo لإنشاء فئة الكائن Foo . إذا لم يحدث ذلك ، فسيستخدم type لإنشاء الفصل الدراسي.

اقرأ ذلك عدة مرات.

عندما تفعل:

class Foo(Bar):
    pass

بيثون يفعل ما يلي:

هل هناك سمة __metaclass__ في Foo ؟

إذا كانت الإجابة بنعم ، فأنشئ كائن ذاكرتي في الذاكرة (قلت كائنًا من الفئة ، __metaclass__ معي هنا) ، بالاسم Foo باستخدام ما هو في __metaclass__ .

إذا لم تتمكن بايثون من العثور على __metaclass__ ، __metaclass__ عن __metaclass__ في المستوى MODULE ، وتحاول أن تفعل الشيء نفسه (ولكن فقط للفئات التي لا ترث أي شيء ، وهي فئات من الطراز القديم).

إذا لم تتمكن من العثور على أي __metaclass__ على الإطلاق ، فسوف تستخدم الشريط الأول (الخاص بالوالد الأول) الخاص (والذي قد يكون type الافتراضي) لإنشاء كائن الفئة.

كن حذرًا هنا أن السمة __metaclass__ لن يتم توريثها ، فسيكون metaclass للوالد ( Bar.__class__ ). إذا كان Bar يستخدم سمة __metaclass__ التي تم إنشاؤها Bar مع type() (وليس type.__new__() ) ، فلن ترث الفئات الفرعية هذا السلوك.

الآن السؤال الكبير هو ، ما الذي يمكنك وضعه في __metaclass__ ؟

الإجابة هي: شيء يمكن أن يخلق صفًا.

وماذا يمكن أن تخلق فئة؟ type ، أو أي شيء يندرج تحته أو يستخدمه.

Metaclasses في Python 3

تم تغيير بناء الجملة لتعيين metaclass في Python 3:

class Foo(object, metaclass=something):
    [...]

أي لم تعد تستخدم السمة __metaclass__ ، لصالح وسيطة الكلمات الرئيسية في قائمة الفئات الأساسية.

على الرغم من ذلك ، يبقى سلوك المناظير متماثلاً إلى حد كبير .

metaclasses مخصصة

الهدف الرئيسي من metaclass هو تغيير الصف تلقائيًا ، عند إنشائه.

أنت تقوم بذلك عادةً لواجهة برمجة التطبيقات (APIs) ، حيث تريد إنشاء فئات مطابقة للسياق الحالي.

تخيل مثالًا غبيًا ، حيث تقرر أن جميع الصفوف في وحدتك يجب أن تكون مكتوبة بحروف كبيرة. هناك عدة طرق للقيام بذلك ، لكن إحدى الطرق هي تعيين __metaclass__ على مستوى الوحدة النمطية.

بهذه الطريقة ، سيتم إنشاء جميع فئات هذه الوحدة باستخدام هذا metaclass ، ويجب علينا فقط إخبار metaclass لتحويل جميع السمات إلى أحرف كبيرة.

لحسن الحظ ، يمكن أن يكون __metaclass__ الواقع قابل للاستدعاء ، ولا يحتاج إلى أن يكون فصلًا رسميًا (أعلم أن شيئًا ما باسم "class" في اسمه لا يحتاج إلى أن يكون فصلًا ، أو رقمًا ... ولكنه مفيد).

لذلك سنبدأ بمثال بسيط ، باستخدام وظيفة.

# 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__ أو نتصل __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 ، كل الحجج لها أسماء تقليدية. لذا ، ستبدو طبقة الإنتاج الحقيقية كما يلي:

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 ، توارث من النوع):

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)

هذا هو. لا يوجد في الواقع شيء أكثر حول metaclasses.

السبب وراء تعقيد الشفرة باستخدام metaclasses ليس بسبب metaclasses ، لأنه عادة ما تستخدم metaclasses للقيام بالأشياء الملتوية التي تعتمد على الاستبطان ، التلاعب في الميراث ، __dict__ مثل __dict__ ، إلخ.

في الواقع ، تعتبر المناظير مفيدة بشكل خاص للقيام بالسحر الأسود ، وبالتالي الأشياء المعقدة. لكن في حد ذاتها ، فهي بسيطة:

  • اعتراض خلق فئة
  • تعديل الفصل
  • إرجاع الطبقة المعدلة

لماذا تستخدم فئات metaclasses بدلاً من الوظائف؟

بما أن __metaclass__ يمكن أن تقبل أي شيء قابل للاستدعاء ، فلماذا تستخدم صفًا لأنه من الواضح أنه أكثر تعقيدًا؟

هناك عدة أسباب للقيام بذلك:

  • النية واضحة. عندما تقرأ UpperAttrMetaclass(type) ، أنت تعرف ما سوف يتبع
  • يمكنك استخدام OOP. يمكن أن ترث Metaclass من metaclass ، وتجاوز الأساليب الرئيسية. يمكن أن تستخدم حتى الفوقية metaclasses.
  • سوف تكون الفئات الفرعية للفئة مثيلات من metaclass الخاص بها إذا قمت بتحديد فئة metaclass ، ولكن ليس مع وظيفة metaclass.
  • يمكنك بناء التعليمات البرمجية بشكل أفضل. لا تستخدم أبدًا metaclasses لشيء صغير مثل المثال أعلاه. انها عادة لشيء معقد. إن امتلاك القدرة على القيام بعدة طرق وتجميعها في فصل واحد مفيد جدًا لجعل التعليمات البرمجية أسهل في القراءة.
  • يمكنك ربط على __new__ و __init__ و __call__ . مما يسمح لك بالقيام بأشياء مختلفة. حتى لو كان عادةً ما يمكنك فعل كل شيء في __new__ ، فإن بعض الأشخاص يكونون أكثر راحة باستخدام __init__ .
  • هذه تسمى 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 كائن IntegerField . ستقوم بإرجاع int ، ويمكن حتى أخذ مباشرة من قاعدة البيانات.

هذا ممكن لأن models.Model يعرّف __metaclass__ ويستخدم بعض السحر الذي سيحوّل Person الذي عرّفته للتو مع عبارات بسيطة إلى خطاف معقّد إلى حقل قاعدة بيانات.

يجعل Django أمرًا معقدًا بسيطًا من خلال تعريض واجهة برمجة تطبيقات بسيطة واستخدام metaclasses ، وإعادة إنشاء الشفرة من واجهة برمجة التطبيقات هذه للقيام بالمهمة الحقيقية وراء الكواليس.

الكلمة الأخيرة

أولاً ، تعلم أن الفئات هي كائنات يمكن إنشاء مثيلات.

حسنا ، في الواقع ، الطبقات هي نفسها الحالات. من metaclasses.

>>> class Foo(object): pass
>>> id(Foo)
142630324

كل شيء هو كائن في بايثون ، وكلها إما أمثلة على الطبقات أو حالات من metaclasses.

باستثناء type .

type هو في الواقع metaclass الخاصة به. هذا ليس شيئًا يمكن أن تستنسخه في بيثون النقي ، ويتم ذلك عن طريق الغش قليلاً على مستوى التنفيذ.

ثانيا ، معقدة metaclasses. قد لا ترغب في استخدامها لتعديلات بسيطة للغاية. يمكنك تغيير الفئات باستخدام طريقتين مختلفتين:

99 ٪ من الوقت الذي تحتاج فيه إلى تغيير الصف ، فأنت أفضل حالا باستخدام هذه.

لكن 98٪ من الوقت ، لا تحتاج إلى تغيير الصف على الإطلاق.


دور طريقة metaclass __call__()عند إنشاء نسخة طبق الأصل

إذا كنت قد أنجزت برمجة Python لأكثر من بضعة أشهر ، فسوف تتعثر في نهاية المطاف على رمز يبدو كالتالي:

# 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__()يتم استدعاء الأسلوب عند استخدام مثيل فئة كما للاستدعاء. ولكن كما رأينا من إجابات سابقة ، فإن الصنف نفسه هو مثال على طبقة افتراضية ، لذا فعندما نستخدم الفصل على أنه قابل للاستدعاء (أي عندما نخلق مثالًا منه) ، فإننا نسمي __call__()طريقة "metaclass" بالفعل . عند هذه النقطة ، يكون معظم مبرمجي لغة Python مرتبكون قليلاً لأنهم أخبروا أنه عند إنشاء مثل هذا المثال ، instance = SomeClass()فإنك تتصل __init__()بطريقته. بعض الذين قد حفرت أعمق قليلا نعرف أن قبل __init__()هناك __new__(). حسنًا ، اليوم يتم الكشف عن طبقة أخرى من الحقيقة ، قبل أن يكون __new__()هناك طبقة " __call__().

دعونا دراسة سلسلة استدعاء الأسلوب من منظور إنشاء مثيل لفئة معينة.

هذه هي طبقة افتراضية تسجل بالضبط اللحظة التي يتم فيها إنشاء المثيل والحالة التي على وشك إرجاعها.

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الطبقة الصورة الأم ( 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

يمكننا أن نرى أن __call__()أسلوب metaclass هو الذي يسمى أولاً. ثم يقوم بتفويض إنشاء المثيل إلى __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

ما هي metaclasses؟ ماذا يمكنك استخدامها ل؟

TLDR: يعرّف metaclass السلوك ويحدد السلوك لفئة مثلما instantiates فئة ويحدد سلوك مثيل.

شبة الكود:

>>> Class(...)
instance

ما سبق يجب أن تبدو مألوفة. حسنا ، من أين تأتي Class ؟ إنها مثال ل metaclass (pseudocode) أيضًا:

>>> Metaclass(...)
Class

في الشفرة الحقيقية ، يمكننا تمرير الطبقة الافتراضية الافتراضية ، type ، وكل ما نحتاجه لإنشاء فئة والحصول على فصل دراسي:

>>> type('Foo', (object,), {}) # requires a name, bases, and a namespace
<class '__main__.Foo'>

وضعها بشكل مختلف

  • الفصل الدراسي هو مثال حيث أن metaclass هو فصل دراسي.

    عندما نقوم بإنشاء كائن ، نحصل على مثال:

    >>> object()                          # instantiation of class
    <object object at 0x7f9069b4e0b0>     # instance
    

    وبالمثل ، عندما نحدد فئة بشكل صريح مع الطبقة الافتراضية الافتراضية ، type ، نقوم بتفعيلها:

    >>> type('Object', (object,), {})     # instantiation of metaclass
    <class '__main__.Object'>             # instance
    
  • بعبارة أخرى ، الطبقة هي مثال لفيلم metaclass:

    >>> isinstance(object, type)
    True
    
  • ضع طريقة ثالثة ، الطبقة الأولى هي فئة الفصل.

    >>> type(object) == type
    True
    >>> object.__class__
    <class 'type'>
    

عندما تقوم بكتابة تعريف فئة وتقوم Python بتنفيذها ، فإنها تستخدم metaclass لإنشاء مثيل لعنصر الفئة (والذي سيستخدم بدوره في إنشاء مثيل لهذه الفئة).

تمامًا كما يمكننا استخدام تعريفات الصفوف لتغيير سلوك مثيلات الكائن المخصص ، يمكننا استخدام تعريف فئة metaclass لتغيير طريقة تصرف كائن الفئة.

بماذا يستخدمن؟ من docs :

الاستخدامات المحتملة للمتفجرات لا حدود لها. تتضمن بعض الأفكار التي تم استكشافها تسجيل الدخول ، والتحقق من الواجهة ، والتفويض التلقائي ، وإنشاء الخاصية التلقائية ، والوكلاء ، والأطر ، والتأمين / المزامنة التلقائية للموارد.

ومع ذلك ، عادة ما يتم تشجيع المستخدمين على تجنب استخدام 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'})

تمثل الطبقة الرئيسية للكائن الذي أنشأناه ، في كلتا الحالتين ، 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))المستبعد إجراء مزيد من التدقيق (حيث أن من المستحيل أن يتم التدقيق في الوظائف من الافتراضيات الخاصة بهم __repr__).

الاستخدام المتوقع: __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')

ملاحظة ، تم تعديل هذا المثال من docs - التعداد الجديد في المكتبة القياسية يقوم بذلك.

إذن ، ما فعلناه هو إنشاء سلسلة افتتاحية عن طريق إنشاء فصل دراسي. يمكننا أيضا التعامل مع metaclass كما كنا في أي فئة أخرى. له ترتيب دقة الأسلوب:

>>> inspect.getmro(OrderedType)
(<class '__main__.OrderedType'>, <class '__main__.Type'>, <class 'type'>, <class 'object'>)

ولها تقريبا الصحيح repr(الذي لم يعد بإمكاننا أن نتحقق إلا إذا أمكننا إيجاد طريقة لتمثيل وظائفنا):

>>> 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>})

إن 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()

بالإضافة إلى الإجابات المنشورة ، يمكنني أن أقول أن metaclassتعريف سلوك الصف. لذلك ، يمكنك تعيين metaclass بشكل صريح. كلما حصلت Python على كلمة رئيسية ، 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'

ملاحظة ، هذه الإجابة لـ Python 2.x كما تمت كتابتها في عام 2008 ، تختلف metaclasses قليلاً في 3.x ، راجع التعليقات.

الفئران هي الصلصة السرية التي تجعل عمل "الطبقة". تسمى الطبقة الافتراضية الافتراضية لكائن نمط جديد باسم "type".

class type(object)
  |  type(object) -> the object's type
  |  type(name, bases, dict) -> a new type

metaclasses تأخذ 3 args. " الاسم " و " القواعد " و " الإملاء "

هنا حيث يبدأ السر. ابحث عن مكان الاسم والقواعد و dict من تعريف فئة المثال هذا.

class ThisIsTheName(Bases, Are, Here):
    All_the_code_here
    def doesIs(create, a):
        dict

يتيح تعريف metaclass يوضح كيفية استدعاء " class: ".

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

تجدر الإشارة إلى أن السلوك السحري الذي تحققه الأرباح "غير المحسَّنة" من خلال جعل init_attributes init_attributes لا يتم تمريره إلى فئة فرعية من Initalised.

في ما يلي مثال أكثر واقعية ، يوضح كيف يمكنك "تصنيف" فئة فرعية لعمل عمل متنقل يقوم بإجراء ما عندما يتم إنشاء الفصل. هذا صعب جدا:

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

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.


وشرح آخرون كيف تعمل النظريات وكيف تتناسب مع نظام بايثون. فيما يلي مثال على ما يمكن استخدامه فيه. كتبت في إطار اختبار ، كنت أرغب في تتبع الترتيب الذي تم تحديد الفئات فيه ، حتى أتمكن لاحقًا من إنشاء هذا الترتيب. لقد وجدت أنه من الأسهل القيام بذلك باستخدام 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 class class والتي تسجل الترتيب الذي تم تعريف الفئات به.


أعتقد أن مقدمة 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 )

باختصار: الطبقة هي مخطط لإنشاء مثيل ، فالمساح هو مخطط لإنشاء الفصل الدراسي. يمكن أن نرى بسهولة أنه في فصول بيثون يجب أن تكون كائنات من الدرجة الأولى أيضًا لتمكين هذا السلوك.

لم أكتب أبدا نفسي ، لكني أعتقد أنه يمكن رؤية واحدة من ألطف استخدامات metaclasses في إطار Django . تستخدم فئات النماذج منهج metaclass لتمكين أسلوب إعلاني لكتابة نماذج جديدة أو فئات نماذج. أثناء إنشاء الطبقة التدريبية للفصل الدراسي ، يحصل جميع الأعضاء على إمكانية تخصيص الفئة نفسها.

الشيء الذي يبقى ليقوله هو: إذا كنت لا تعرف ما هي metaclasses ، فإن الاحتمال الذي لن تحتاج لهم هو 99 ٪.


بايثون 3 التحديث

هناك (في هذه المرحلة) طريقتين رئيسيتين في metaclass:

  • __prepare__ و
  • __new__

__prepare__يتيح لك توفير تعيين مخصص (مثل a 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. بدون طبقة افتراضية ، سيبدو صفك كما يلي:

class Person:
    weight = ValidateType('weight', int)
    age = ValidateType('age', int)
    name = ValidateType('name', str)

كما ترى ، يجب عليك تكرار اسم السمة مرتين. وهذا يجعل من الممكن حدوث الأخطاء المطبعية بالإضافة إلى الأخطاء المزعجة.

يمكن أن تتعامل طبقة بسيطة بسيطة مع هذه المشكلة:

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')

ملاحظة : هذا المثال بسيط بما فيه الكفاية يمكن أن يكون قد تم إنجازه أيضًا مع مصمم ديكور ، لكن من المفترض أن يكون فعلاً فعلية فعلية أكثر بكثير.

الفئة "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

يمكن أن تقوم وظيفة type () بإرجاع نوع كائن أو إنشاء نوع جديد ،

على سبيل المثال ، يمكننا إنشاء فئة Hi مع الوظيفة type () ولا تحتاج إلى استخدام هذه الطريقة مع class Hi (كائن):

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

بالإضافة إلى استخدام type () لإنشاء فئات بشكل ديناميكي ، يمكنك التحكم في سلوك إنشاء الفصل واستخدام metaclass.

وفقًا لنموذج كائن Python ، تكون الفئة هي الكائن ، لذلك يجب أن يكون الفصل مثيلًا لفئة معينة أخرى. بشكل افتراضي ، تكون فئة Python هي مثيل لفئة النوع. هذا هو ، النوع هو 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 ، ويشير إلى مترجم Python لإنشاء CustomList من خلال ListMetaclass. new () ، في هذه المرحلة ، يمكننا تعديل تعريف الفئة ، على سبيل المثال ، وإضافة طريقة جديدة ثم إعادة التعريف المعدّل.


هل تريد اسم الصف كسلسلة؟

instance.__class__.__name__




python oop metaclass python-datamodel