بيثون مثيل

دعنا نقول أن لديك فئة فو :


class Foo(object): def __init__(self, x, y=0): self.x = x self.y = y 

ماذا يحدث عند إنشاء الكائن الخاص به؟


 f = Foo(1, y=2) 

الطريقة التي تسمى أولاً مع هذه الدعوة إلى فو ؟ معظم القادمين الجدد ، وربما عدد غير قليل من أهل بيثون ذوي الخبرة ، سوف يردون على الفور: " __init__ method". ولكن إذا نظرت عن كثب إلى المقتطفات أعلاه ، فسوف يتضح قريبًا أن هذه الإجابة غير صحيحة.


__init__ لا تُرجع أي نتيجة ، و Foo (1 ، ص = 2) ، على العكس ، تُرجع مثيلًا للفئة . بالإضافة إلى ذلك ، __init__ تأخذ النفس كمعلمة أولى ، وهذا لا يحدث عند استدعاء Foo (1 ، ص = 2) . إنشاء مثيل أكثر تعقيدًا قليلاً ، والذي سنتحدث عنه في هذه المقالة.


كائن إنشاء الإجراء


يتكون مثيل بايثون من عدة مراحل. إن فهم كل خطوة يجعلنا أقرب إلى فهم اللغة ككل. Foo عبارة عن فصل ، لكن في Python ، تعتبر الفصول كائنات أيضًا! الفصول والوظائف والطرق والمثيلات كلها كائنات ، وكلما وضعت أقواس بعد اسمها ، يمكنك استدعاء طريقة __call__ الخاصة بها . إذن Foo (1 ، y = 2) هي المكافئ لـ Foo .__ call __ (1 ، y = 2) . علاوة على ذلك ، يتم الإعلان عن الأسلوب __call__ في فئة كائن Foo . ما هي فئة كائن فو ؟


 >>> Foo.__class__ <class 'type'> 

لذا فإن فئة Foo هي مثيل لفئة الكتابة وتعيد طريقة __call__ الأخيرة إرجاع فئة Foo . الآن دعونا نلقي نظرة على طريقة __call__ لفئة الكتابة . فيما يلي تطبيقاته في C في CPython و PyPy. إذا مللت من مشاهدتها ، فانتقل قليلاً للعثور على إصدار مبسط:


سي بايثون


رابط للمصدر .


 static PyObject * type_call(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyObject *obj; if (type->tp_new == NULL) { PyErr_Format(PyExc_TypeError, "cannot create '%.100s' instances", type->tp_name); return NULL; } obj = type->tp_new(type, args, kwds); obj = _Py_CheckFunctionResult((PyObject*)type, obj, NULL); if (obj == NULL) return NULL; /* Ugly exception: when the call was type(something), don't call tp_init on the result. */ if (type == &PyType_Type && PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1 && (kwds == NULL || (PyDict_Check(kwds) && PyDict_Size(kwds) == 0))) return obj; /* If the returned object is not an instance of type, it won't be initialized. */ if (!PyType_IsSubtype(Py_TYPE(obj), type)) return obj; type = Py_TYPE(obj); if (type->tp_init != NULL) { int res = type->tp_init(obj, args, kwds); if (res < 0) { assert(PyErr_Occurred()); Py_DECREF(obj); obj = NULL; } else { assert(!PyErr_Occurred()); } } return obj; } 

PyPy


رابط للمصدر .


 def descr_call(self, space, __args__): promote(self) # invoke the __new__ of the type if not we_are_jitted(): # note that the annotator will figure out that self.w_new_function # can only be None if the newshortcut config option is not set w_newfunc = self.w_new_function else: # for the JIT it is better to take the slow path because normal lookup # is nicely optimized, but the self.w_new_function attribute is not # known to the JIT w_newfunc = None if w_newfunc is None: w_newtype, w_newdescr = self.lookup_where('__new__') if w_newdescr is None: # see test_crash_mro_without_object_1 raise oefmt(space.w_TypeError, "cannot create '%N' instances", self) w_newfunc = space.get(w_newdescr, self) if (space.config.objspace.std.newshortcut and not we_are_jitted() and isinstance(w_newtype, W_TypeObject)): self.w_new_function = w_newfunc w_newobject = space.call_obj_args(w_newfunc, self, __args__) call_init = space.isinstance_w(w_newobject, self) # maybe invoke the __init__ of the type if (call_init and not (space.is_w(self, space.w_type) and not __args__.keywords and len(__args__.arguments_w) == 1)): w_descr = space.lookup(w_newobject, '__init__') if w_descr is not None: # see test_crash_mro_without_object_2 w_result = space.get_and_call_args(w_descr, w_newobject, __args__) if not space.is_w(w_result, space.w_None): raise oefmt(space.w_TypeError, "__init__() should return None") return w_newobject 



إذا نسيت كل أنواع عمليات التحقق من الأخطاء ، فإن الرموز أعلاه تعادل هذا تقريبًا:


 def __call__(obj_type, *args, **kwargs): obj = obj_type.__new__(*args, **kwargs) if obj is not None and issubclass(obj, obj_type): obj.__init__(*args, **kwargs) return obj 

__new__ يخصص الذاكرة لكائن "فارغ" ويدعو __init__ لتهيئته .


لتلخيص:


  1. Foo (* args ، ** kwargs) تعادل Foo .__ call __ (* args ، ** kwargs) .
  2. نظرًا لأن كائن Foo هو مثيل لفئة الكتابة ، فإن استدعاء Foo .__ call __ (* args ، ** kwargs) مكافئ للكتابة .__ call __ (Foo ، * args ، ** kwargs) .
  3. type .__ call __ (Foo، * args، ** kwargs) يستدعي type .__ new __ (Foo، * args، ** kwargs) ، والتي تُرجع obj .
  4. تتم تهيئة obj عن طريق استدعاء obj .__ init __ (* args ، ** kwargs) .
  5. نتيجة العملية برمتها هي عبارة عن توصيف مبدئي .

التخصيص


الآن دعونا نحول انتباهنا إلى __new__ . هذه الطريقة تخصص ذاكرة للكائن وتعيده. أنت حر في تخصيص هذه العملية بعدة طرق مختلفة. تجدر الإشارة إلى أنه على الرغم من أن __new__ هي طريقة ثابتة ، إلا أنك لست بحاجة إلى الإعلان عنها باستخدام @ staticethod : يعامل المترجم __new__ كحالة خاصة.


من الأمثلة الشائعة على تجاوزات __new__ إنشاء Singleton:


 class Singleton(object): _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls, *args, **kwargs) return cls._instance 

ثم:


 >>> s1 = Singleton() ... s2 = Singleton() ... s1 is s2 True 

لاحظ أنه سيتم استدعاء __init__ في كل مرة يتم فيها استدعاء Singleton () ، لذلك يجب توخي الحذر.


مثال آخر على التخطيات __new__ هو تنفيذ نمط Borg ("Borg") :


 class Borg(object): _dict = None def __new__(cls, *args, **kwargs): obj = super().__new__(cls, *args, **kwargs) if cls._dict is None: cls._dict = obj.__dict__ else: obj.__dict__ = cls._dict return obj 

ثم:


 >>> b1 = Borg() ... b2 = Borg() ... b1 is b2 False >>> b1.x = 8 ... b2.x 8 

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


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


- آريون سبراغ ، المنسي القديم في بيثون


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

Source: https://habr.com/ru/post/ar480022/


All Articles