python - وحتى - ملخص لغة بايثون




ما هي metaclasses في بايثون؟ (10)

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


ما هي 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>})

فئات كأشياء

قبل فهم 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٪ من الوقت ، لا تحتاج إلى تغيير الصف على الإطلاق.


النسخة tl؛ dr

و type(obj)ظيفة يحصل لك نوع كائن.

و type()من فئة غير التي metaclass .

لاستخدام metaclass:

class Foo(object):
    __metaclass__ = MyMetaClass

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


استخدام واحد ل metaclasses هو إضافة خصائص وأساليب جديدة إلى مثيل تلقائيا.

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

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

ومع ذلك ، في وقت التشغيل يتم تعبئة كائنات الشخص مع جميع أنواع الأساليب المفيدة. انظر source لبعض metaclassery مذهلة.


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


ملاحظة ، هذه الإجابة لـ 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

بايثون 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

فدروس بايثون هي نفسها كائنات - على سبيل المثال - من الطبقة الفوقية.

الافتراضي metaclass ، الذي يتم تطبيقه عند تحديد الفئات كـ:

class foo:
    ...

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

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

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.


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





python-datamodel