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



6 Answers

فئات كأشياء

قبل فهم 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__

يمكنك إضافة سمة __metaclass__ عند كتابة فصل __metaclass__ :

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 مخصصة

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

Question

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




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

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

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

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




Python 3 update

There are (at this point) two key methods in a metaclass:

  • __prepare__ , and
  • __new__

__prepare__ lets you supply a custom mapping (such as an OrderedDict ) to be used as the namespace while the class is being created. You must return an instance of whatever namespace you choose. If you don't implement __prepare__ a normal dict is used.

__new__ is responsible for the actual creation/modification of the final class.

A bare-bones, do-nothing-extra metaclass would like:

class Meta(type):

    def __prepare__(metaclass, cls, bases):
        return dict()

    def __new__(metacls, cls, bases, clsdict):
        return super().__new__(metacls, cls, bases, clsdict)

مثال بسيط:

Say you want some simple validation code to run on your attributes -- like it must always be an int or a str . Without a metaclass, your class would look something like:

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

As you can see, you have to repeat the name of the attribute twice. This makes typos possible along with irritating bugs.

A simple metaclass can address that problem:

class Person(metaclass=Validator):
    weight = ValidateType(int)
    age = ValidateType(int)
    name = ValidateType(str)

This is what the metaclass would look like (not using __prepare__ since it is not needed):

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)

A sample run of:

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

Note : This example is simple enough it could have also been accomplished with a class decorator, but presumably an actual metaclass would be doing much more.

The 'ValidateType' class for reference:

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



The type() function can return the type of an object or create a new type,

for example, we can create a Hi class with the type() function and do not need to use this way with class Hi(object):

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

In addition to using type() to create classes dynamically, you can control creation behavior of class and use metaclass.

According to the Python object model, the class is the object, so the class must be an instance of another certain class. By default, a Python class is instance of the type class. That is, type is metaclass of most of the built-in classes and metaclass of user-defined classes.

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

Magic will take effect when we passed keyword arguments in metaclass, it indicates the Python interpreter to create the CustomList through ListMetaclass. new (), at this point, we can modify the class definition, for example, and add a new method and then return the revised definition.




أعتقد أن مقدمة ONLamp إلى برمجة metaclass مكتوبة بشكل جيد وتعطي مقدمة جيدة للموضوع على الرغم من كونها عدة سنوات بالفعل.

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html

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

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

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




Role of a metaclass's __call__() method when creating a class instance

If you've done Python programming for more than a few months you'll eventually stumble upon code that looks like this:

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

The latter is possible when you implement the __call__() magic method on the class.

class SomeClass(object):
    # ...
    # some definition here ...
    # ...

    def __call__(self, foo, bar):
        return bar + foo

The __call__() method is invoked when an instance of a class is used as a callable. But as we've seen from previous answers a class itself is an instance of a metaclass, so when we use the class as a callable (ie when we create an instance of it) we're actually calling its metaclass's __call__() method. At this point most Python programmers are a bit confused because they've been told that when creating an instance like this instance = SomeClass() you're calling it's __init__() method. Some who've dug a bit deeper know that before __init__() there's __new__() . Well, today another layer of truth is being revealed, before __new__() there's the metaclass's __call__() .

Let's study the method call chain from specifically the perspective of creating an instance of a class.

This is a metaclass that logs exactly the moment before an instance is created and the moment it's about to return it.

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

This is a class that uses that 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__()."

And now let's create an instance of 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.

The code above doesn't actually do anything other than logging the task and then delegating the actual work to the parent (ie keeping the default behavior). So with type being Meta_1 's parent class, we can imagine that this would be the pseudo implementation of 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

We can see that the metaclass's __call__() method is the one that's called first. It then delegates creation of the instance to the class's __new__() method and initialization to the instance's __init__() . It's also the one that ultimately returns the instance.

From the above it stems that the metaclass's __call__() is also given the opportunity to decide whether or not a call to Class_1.__new__() or Class_1.__init__() will eventually be made. Over the course of its execution it could actually return an object that hasn't been touched by either of these methods. Take for example this approach to the singleton pattern:

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__()."

Let's observe what happens when repeatedly trying to create an object of type 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



The tl;dr version

The type(obj) function gets you the type of an object.

The type() of a class is its metaclass .

To use a metaclass:

class Foo(object):
    __metaclass__ = MyMetaClass





Related