تعريف الطبقة الديناميكية في بايثون

يمكن فهم تعريف الكائن الديناميكي على أنه تعريف في وقت التشغيل. بخلاف التعريف الثابت ، المستخدم في تعريف الكلمات الأساسية المألوف للفئة class، يستخدم التعريف الديناميكي فئة مضمنة type.



اكتب metaclass



غالبًا ما يتم استخدام فئة النوع للحصول على نوع الكائن. على سبيل المثال مثل هذا:



h = "hello"
type(h)
<class 'str'>


لكن لها استخدامات أخرى. يمكنه تهيئة أنواع جديدة.كما تعلم ، كل شيء في بايثون هو كائن. ويترتب على ذلك أن جميع التعريفات لها أنواع ، بما في ذلك الفئات والكائنات. على سبيل المثال:



class A:
    pass
type(A)
<class 'type'>


قد لا يكون من الواضح تمامًا سبب تخصيص فئة لنوع فئة type، على عكس حالاتها:



a = A()
type(a)
<class '__main__.A'>


aيتم تعيين فئة كنوع للكائن . هذه هي الطريقة التي يتعامل بها المترجم مع الكائن كمثيل للفئة. يحتوي الفصل نفسه على نوع فئة typeلأنه يرثه من الفئة الأساسية object:



A.__bases__
(<class 'object'>,)


نوع الفصل object:



type(object)
<class 'type'>


يتم objectتوارث الفئة بواسطة جميع الفئات افتراضيًا ، أي:



class A(object):
    pass


مثل:



class A:
    pass


الفئة التي يتم تعريفها ترث الفئة الأساسية كنوع. ومع ذلك ، هذا لا يفسر سبب كون الفئة الأساسية objectمن نوع الفئة type. النقطة typeالمهمة هي أنها metaclass. كما تعلم بالفعل ، ترث جميع الفئات من الفئة الأساسية object، وهي من النوع metaclass type. لذلك ، تحتوي جميع الفئات أيضًا على هذا النوع ، بما في ذلك metaclass نفسه type:



type(type)
<class 'type'>


هذه هي "نقطة نهاية الكتابة" في بايثون. يتم إغلاق سلسلة وراثة النوع في الفصل type. تعد metaclass typeبمثابة قاعدة لجميع الفئات في Python. من السهل التحقق من ذلك:



builtins = [list, dict, tuple]
for obj in builtins:
    type(obj)
<class 'type'>
<class 'type'>
<class 'type'>


الفئة هي نوع بيانات مجردة ، ومثيلاتها لها مرجع فئة كنوع.



تهيئة أنواع جديدة بفئة النوع



عند التحقق من الأنواع ، تتم typeتهيئة الفئة باستخدام وسيط واحد:



type(object) -> type


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



type(name, bases, dict) -> new type


اكتب معلمات التهيئة



  • name

    سلسلة تحدد اسم الفئة الجديدة (النوع).
  • bases

    مجموعة من الفئات الأساسية (الفئات التي سيرثها الفصل الجديد).
  • dict

    القاموس مع سمات الطبقة المستقبلية. عادة مع سلاسل في المفاتيح وأنواع قابلة للاستدعاء في القيم.


تعريف الطبقة الديناميكية



نقوم بتهيئة فئة النوع الجديد ، مع توفير جميع الحجج اللازمة ونسميها:



MyClass = type("MyClass", (object, ), dict())
MyClass
<class '__main__.MyClass'>


يمكنك العمل مع الفصل الجديد كالمعتاد:



m = MyClass()
m
<__main__.MyClass object at 0x7f8b1d69acc0>


علاوة على ذلك ، فإن الطريقة تعادل تعريف الفئة المعتاد:



class MyClass:
    pass


تحديد سمات الفئة ديناميكيًا



لا فائدة من وجود فئة فارغة ، لذا فإن السؤال الذي يطرح نفسه: كيف نضيف سمات وطرق؟



للإجابة على هذا السؤال ، ضع في اعتبارك رمز التهيئة الأولي:



MyClass = type(“MyClass”, (object, ), dict())


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



MyClass = type(“MyClass”, (object, ), dict(foo=“bar”)
m = MyClass()
m.foo
'bar'


تعريف الطريقة الديناميكية



يمكن أيضًا تمرير الكائنات القابلة للاستدعاء إلى القاموس ، على سبيل المثال ، الطرق:



def foo(self):
    return “bar”
MyClass = type(“MyClass”, (object, ), dict(foo=foo))
m = MyClass()
m.foo
'bar'


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



MyClass = type(“MyClass”, (object, ), dict())


بعد تهيئة فئة فارغة ، يمكنك إضافة عمليات إليها ديناميكيًا ، أي بدون تعريف ثابت واضح:



code = compile('def foo(self): print(“bar”)', "<string>", "exec")


compileهي وظيفة مضمنة تقوم بتجميع التعليمات البرمجية المصدر في كائن. يمكن تنفيذ الكود عن طريق الوظائف exec()أو eval().



تجميع معلمات الوظيفة



  • مصدر

    شفرة المصدر ، يمكن أن يكون رابطًا إلى وحدة نمطية.
  • filename

    اسم الملف الذي سيتم تجميع الكائن فيه.
  • الوضع

    إذا تم تحديده "exec"، ستقوم الوظيفة بترجمة شفرة المصدر إلى وحدة نمطية.


نتيجة العمل compileكائن فئة code:



type(code)
<class 'code'>


codeيجب تحويل الكائن إلى طريقة. نظرًا لأن الطريقة هي وظيفة ، نبدأ بتحويل codeكائن فئة إلى كائن فئة function. للقيام بذلك ، قم باستيراد الوحدة النمطية types:



from types import FunctionType, MethodType


سأقوم باستيرادها MethodTypeلأنني سأحتاجها لاحقًا لتحويل الوظيفة إلى طريقة فئة.



function = FunctionType(code.co_consts[0], globals(), “foo”)


معلمات أسلوب التهيئة FunctionType



  • code

    كائن فئة code. code.co_consts[0]هو استدعاء إلى واصف co_constsفئة code، وهو عبارة عن مجموعة تحتوي على ثوابت في كود الكائن. تخيل كائنًا codeكوحدة نمطية ذات وظيفة واحدة نحاول إضافتها كطريقة للفصل. 0هو الفهرس الخاص به لأنه الثابت الوحيد في الوحدة.
  • globals()

    قاموس المتغيرات العالمية.
  • name

    معلمة اختيارية تحدد اسم الوظيفة.


والنتيجة هي وظيفة:



function
<function foo at 0x7fc79cb5ed90>
type(function)
<class 'function'>


بعد ذلك ، تحتاج إلى إضافة هذه الوظيفة كطريقة للفصل MyClass:



MyClass.foo = MethodType(function, MyClass)


تعبير بسيط إلى حد ما يعين وظيفتنا لطريقة الصنف MyClass.



m = MyClass()
m.foo()
bar


تحذير



في 99٪ من الحالات ، يمكنك الحصول على تعريفات فئة ثابتة. ومع ذلك ، فإن مفهوم metaprogramming جيد في الكشف عن العناصر الداخلية لـ Python. على الأرجح ، سيكون من الصعب عليك العثور على تطبيق الأساليب الموضحة هنا ، على الرغم من وجود مثل هذه الحالة في عملي.



هل عملت مع كائنات ديناميكية؟ ربما بلغات أخرى؟



الروابط






All Articles