محاكاة اختبار العضوية في بيثون: تفويض __contains__ إلى كائن محتوى بشكل صحيح
-
06-07-2019 - |
سؤال
أنا معتاد على أن بيثون يسمح لبعض الحيل الأنيقة بتفويض الوظائف إلى كائنات أخرى. مثال واحد هو التفويض إلى كائنات محتوية.
لكن تحظى بالحلقة ، عندما أرغب في تفويض __contains __:
class A(object):
def __init__(self):
self.mydict = {}
self.__contains__ = self.mydict.__contains__
a = A()
1 in a
انا حصلت:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: argument of type 'A' is not iterable
ماذا أخطئ؟ عندما أتصل بـ A.__ يحتوي على __ (1) ، كل شيء يسير على نحو سلس. حتى أنني حاولت تحديد طريقة __iter __ في A لجعل مظهرًا أكبر ، لكنه لم يساعد. ماذا أفتقد هنا؟
المحلول
طرق خاصة مثل __contains__
تكون خاصة فقط عند تعريفها على الفصل ، وليس على المثيل (باستثناء الفصول القديمة في Python 2 ، والتي يجب عليك ليس استخدام على أي حال).
لذا ، هل تفويضك على مستوى الفصل:
class A(object):
def __init__(self):
self.mydict = {}
def __contains__(self, other):
return self.mydict.__contains__(other)
أفضل في الواقع تهجئة الأخيرة return other in self.mydict
, ، لكن هذه قضية طفيفة.
يحرر: إذا كان من الصعب تنفيذها وعندما "إعادة توجيه أساليب خاصة ديناميكية تمامًا" (مثل الفصول ذات الطراز القديم المعروضة) ، فليس من الصعب تنفيذها مع فصول جديدة: تحتاج فقط ملفوفة في فصلها الخاص. فمثلا:
class BlackMagic(object):
def __init__(self):
self.mydict = {}
self.__class__ = type(self.__class__.__name__, (self.__class__,), {})
self.__class__.__contains__ = self.mydict.__contains__
في الأساس ، بعد إعادة تعيين السحر الأسود self.__class__
إلى كائن فئة جديد (يتصرف تمامًا مثل الكائن السابق ولكن لديه قول فارغ ولا مثيلات أخرى باستثناء هذا self
) ، في أي مكان في فصل قديم ستخصصه self.__magicname__
, ، يسند إلى self.__class__.__magicname__
بدلاً من ذلك (وتأكد من أنها مدمجة أو staticmethod
, ، ليست وظيفة Python عادية ، ما لم تكن بالطبع في بعض الحالات المختلفة تريد أن تتلقى self
عند استدعاء مثيل).
بالمناسبة ، و in
المشغل في حالة من هذا BlackMagic
الفصل هو أسرع, ، كما يحدث ، أكثر من أي من الحلول المقترحة سابقًا - أو على الأقل ، فأنا أقيس مع موثوقي المعتاد -mtimeit
(الذهاب مباشرة إلى built-in method
, ، بدلاً من اتباع طرق البحث العادية التي تنطوي على الميراث والواصف ، يحلق القليل من النفقات العامة).
metaclass لأتمتة self.__class__
-لن يكون من الصعب الكتابة على فكرة الفكرة القذرة (يمكن أن تقوم بالعمل القذر في الفصل الذي تم إنشاؤه __new__
الطريقة ، وربما أيضًا تعيين جميع الأسماء السحرية لتعيينها فعليًا في الفصل إذا تم تعيينها على المثيل ، إما عبر __setattr__
أو كثير ، عديدة الخصائص). ولكن لن يكون له ما يبرره إلا إذا كانت الحاجة إلى هذه الميزة واسعة الانتشار حقًا (على سبيل المثال ، يقوم بنقل مشروع Python 1.5.2 القديم الضخم الذي يستخدم بحرية "الأساليب الخاصة بالثبات" إلى Python الحديثة ، بما في ذلك Python 3).
هل أنا نوصي حلول "ذكي" أو "سحر أسود"؟ لا ، أنا لا: من الأفضل دائمًا القيام بالأشياء بطرق بسيطة ومباشرة. لكن "تقريبا" هي كلمة مهمة هنا ، ومن الجيد أن يكون هناك مثل هذه الخطافات المتقدمة في المواقف النادرة ، ولكن غير الموجودة ، حيث قد يكون هناك ما يبرر استخدامها بالفعل.