دعنا نقول أن لديك فئة فو :
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; if (type == &PyType_Type && PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1 && (kwds == NULL || (PyDict_Check(kwds) && PyDict_Size(kwds) == 0))) return obj; 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)
إذا نسيت كل أنواع عمليات التحقق من الأخطاء ، فإن الرموز أعلاه تعادل هذا تقريبًا:
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__ لتهيئته .
لتلخيص:
- Foo (* args ، ** kwargs) تعادل Foo .__ call __ (* args ، ** kwargs) .
- نظرًا لأن كائن Foo هو مثيل لفئة الكتابة ، فإن استدعاء Foo .__ call __ (* args ، ** kwargs) مكافئ للكتابة .__ call __ (Foo ، * args ، ** kwargs) .
- type .__ call __ (Foo، * args، ** kwargs) يستدعي type .__ new __ (Foo، * args، ** kwargs) ، والتي تُرجع obj .
- تتم تهيئة obj عن طريق استدعاء obj .__ init __ (* args ، ** kwargs) .
- نتيجة العملية برمتها هي عبارة عن توصيف مبدئي .
التخصيص
الآن دعونا نحول انتباهنا إلى __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__ . ولكن عندما يكون لديك مطرقة ، تبدأ كل مشكلة في الظهور كظفر ، لذلك تفضل دائمًا استخدام الأداة الأقوى لاستخدام الأداة الأكثر ملائمة .