اجعل كائناتك تتصرف كالأنواع المدمجة
اطبع كائنًا عاديًا فتعرض بايثون شيئًا مثل <__main__.Position object at 0x7f3a>، ويكون Position("A", 1) == Position("A", 1) هو False لأن كائنين افتراضيًا لا يتساويان إلا إذا كانا الكائن نفسه. تتيح الدوال الخاصة (وتسمى dunder نسبة إلى الشرطتين السفليتين المزدوجتين) لفئتك أن تندمج في صياغة بايثون: __repr__ يتحكم في طريقة عرض الكائن، و__eq__ في ==، و__add__ في +، و__lt__ في < وهكذا. لا تستدعيها مباشرة؛ بل تستدعيها بايثون حين تستخدم repr(x) أو a == b أو a + b أو sorted(items).
class Plain:
def __init__(self, cents):
self.cents = cents
a, b = Plain(500), Plain(500)
print(a == b) # False: different objects
print(a.cents == b.cents) # True
print("object at 0x" in str(a)) # True: the default display is unhelpful__repr__: اعرض الكائن بوضوح
تُرجع __repr__ نصًا يصف الكائن للمطورين. وعُرفًا يشبه الكود الذي يبنيه: Money(1999, 'USD'). وتستخدمها القوائم والمنقّح ورسائل الخطأ كلها، مما يجعل الأخطاء أسهل قراءة. وتستخدمها print(x) وstr(x) أيضًا ما لم تعرّف __str__ لنص أجمل موجّه للمستخدم. يجب أن تُرجع النص لا أن تطبعه، وتعطي {self.currency!r} في f-string الصيغة المقتبسة لخاصية نصية.
class Money:
def __init__(self, cents, currency="USD"):
self.cents = cents
self.currency = currency
def __repr__(self):
return f"Money({self.cents}, {self.currency!r})"
price = Money(1999)
print(price) # Money(1999, 'USD')
print([Money(5), Money(7, "EUR")]) # [Money(5, 'USD'), Money(7, 'EUR')]__eq__ و __hash__: ماذا يعني التساوي؟
يقرّر __eq__(self, other) نتيجة self == other. قارن الحقول التي تحدد الهوية، وحين لا يكون other من النوع المتوقع أرجع القيمة الخاصة NotImplemented (لا False): فتجرّب بايثون الجانب الآخر ثم تجيب هي بـFalse، فيكون Money(1) == 5 آمنًا. وتعريف __eq__ يعطّل التجزئة، فلا يعود الكائن صالحًا عضوًا في مجموعة ولا مفتاح قاموس ما لم تعرّف __hash__ بما يتسق معه، مثل hash((self.cents, self.currency)).
class Money:
def __init__(self, cents, currency="USD"):
self.cents = cents
self.currency = currency
def __eq__(self, other):
if not isinstance(other, Money):
return NotImplemented # let Python decide
return self.cents == other.cents and self.currency == other.currency
def __hash__(self):
return hash((self.cents, self.currency))
print(Money(500) == Money(500)) # True
print(Money(500) == Money(500, "EUR")) # False
print(Money(500) == 500) # False, no error
print(len({Money(1), Money(1)})) # 1: usable in sets nowالمعاملات والترتيب
يعطي __add__(self, other) فئتك +. أرجع كائنًا جديدًا بدل تغيير self، وارفض ما لا معنى له: جمع دولارات إلى يوروهات يجب أن يرفع ValueError، وجمع رقم مجرد يجب أن يُرجع NotImplemented. ويعطي __lt__(self, other) المعامل <، وهذا وحده يكفي لتُرتّب sorted() وmin() وmax() كائناتك لأنها تحتاج فقط إلى السؤال «هل a أصغر من b؟». أضف أيضًا __le__ و__gt__ و__ge__ أو دع functools.total_ordering يشتقها.
class Money:
def __init__(self, cents, currency="USD"):
self.cents = cents
self.currency = currency
def __repr__(self):
return f"Money({self.cents}, {self.currency!r})"
def __add__(self, other):
if not isinstance(other, Money):
return NotImplemented
if other.currency != self.currency:
raise ValueError("cannot add different currencies")
return Money(self.cents + other.cents, self.currency)
def __lt__(self, other):
if not isinstance(other, Money):
return NotImplemented
return self.cents < other.cents
total = Money(150) + Money(275)
print(total) # Money(425, 'USD')
print(sorted([Money(30), Money(10), Money(20)])) # [Money(10, 'USD'), Money(20, 'USD'), Money(30, 'USD')]
print(sum([Money(1), Money(2)], Money(0))) # Money(3, 'USD')__repr__ كل ما ينظر فيه __eq__. ولا تعطِ + آثارًا جانبية مفاجئة: يجب أن يُرجع قيمة جديدة ويترك المعاملين دون مساس كما تفعل الأرقام. وتفحص اختبارات المختبر هذه النقاط: بقاء الأصول دون تغيير، ورفض العملات المختلفة، وإعطاء الأنواع غير ذات الصلة NotImplemented.__repr__تُرجع نصًا (مثلMoney(1999, 'USD'))؛ ولا تطبع.__eq__تقارن الحقول وتُرجعNotImplementedللأنواع الغريبة؛ وأضف__hash__للاحتفاظ بالتجزئة.__add__تُرجع كائنًا جديدًا؛ و__lt__تكفي لـsortedوminوmax.
التالي: الأصناف البيانية (dataclasses)، التي تكتب __init__ و__repr__ و__eq__ عنك.
المختبر: ما الذي يُقيَّم
- اعرض كائن Money بوضوح باستخدام
__repr__25 نقطة - حدّد متى يتساوى مبلغان باستخدام
__eq__25 نقطة - اجمع مبلغين باستخدام
__add__25 نقطة - اجعل المبالغ قابلة للترتيب باستخدام
__lt__25 نقطة