تقييم الموضوع :
  • 0 أصوات - بمعدل 0
  • 1
  • 2
  • 3
  • 4
  • 5
الوظائف الخاصة في بيثون special methods الجزء الثاني
#1
الوظائف الخاصة في بيثون special methods الجزء الثاني



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

سنبني لهذا الغرض فئة جديدة نحاول أن نجعلها سهلة الفهم مع امكانية اثرائها بأكثر ما يمكن من الوظائف الخاصة. اخترنا لهذا الغرض فئة نستخدمها لمعالجة حسابات 'مصرفية' (بين ظفرين لأنها مبسطة للغاية): Account تجدونها في المرفقات

## الوظيفة __init__

تحدثنا عن هذه الوظيفة في الموضوع السابق، ولا بأس من التذكير بسرعة بأننا نستخدم __init__ لتهيئة الكائن بصدد الانشاء:

كود :
class Account:
   """A simple account class"""

   def __init__(self, owner, start_amount=0):
       """
       This is the constructor that lets us create
       objects from this class
       """
       self.owner = owner
       self.start_amount = start_amount
       self.transtemp = []
       self.transactions = [start_amount]
سنحتاج الى تعريف بعض الـproperties لحساب الرصيد المصرفي هدفها توفير بعض الحماية للسمات، وسنحتاج أيضا الى تعريف وظيفة بسيطة (add_transaction) تتولى اضافة معاملات مصرفية. سنبقي عليها بسيطة لأننا بصدد شرح الوظائف الخاصة ولسنا بصدد كتابة برنامج محاسبة حقيقي:

كود :
   @property
   def start_amount(self):
       return self._start_amount

   @start_amount.setter
   def start_amount(self, amount):
       if not isinstance(amount, (int, float)):
           raise TypeError('Start amount needs to be a number')
       if amount < 0:
           raise ValueError('Start amount cannot be negative')
       self._start_amount = amount
   
   @start_amount.deleter
   def start_amount(self):
       raise AttributeError('Cannot delete start_amount attr')

   @property
   def balance(self):
       return sum(self.transactions)


   def add_transaction(self, amount):
       """transaction management with special methods (+=, -=)"""
       if not isinstance(amount, (int, float)):
           raise ValueError('please use int or float for amount')
       self.transtemp.append(amount)
## الوظيفتان __str__ و __repr__

نذكر بأننا نستخدم __str__ لطباعة الكائن على الشاشة بواسطة print مثلا، و __repr__ لعرض الكائن سواء للمستخدم البشري على الشاشة او للبرنامج من أجل معالجة لاحقة مثلا:

كود :
   def __repr__(self):
       """Called by the repr() built-in function to compute the 'official'
       string representation of an object.
       """
       return 'Account({!r}, {!r})'.format(self.owner, self.balance)

   def __str__(self):
       """Called by str(object) and the built-in functions format() and print()
       to compute the 'informal' or nicely printable string representation of an object.
       """
       return 'Account of {} with actuel amount: {}'.format(
           self.owner, self.balance)
## التجربة 1

للتجربة احفظ الملف باسم account.py مثلا ثم نفذه في IDLE عبر F5 او استورده في مفسر بيثون (from account import Account) وجربه مثلا :

كود :
acc = Account('Bob', 10)
acc
print(acc)
str(acc)
acc.add_transaction(20)
acc.add_transaction(-10)
acc.add_transaction(50)
acc.add_transaction(-20)
acc.add_transaction(30)
acc.transtemp
acc.transactions
acc.balance
نلاحظ أن التحويلات التي قمنا بها قد تم حفظها في قائمة وقتية transtemp وأنها لم تدخل الى الرصيد. سنبرمج لاحقا وظيفة(validate_transactions) تتولى بعض التثبت قبل تأكيد العملية.

## الوظائف __len__ و __getitem__ و __reversed__ و __contains__ و __bool__

نريد أن نتمكن من توظيف عمليات التكرار على الكائنات المثيلة من فئتنا Account، نرغب مثلا في:

- استخدام الكائن في تعبير شرطي

- معرفة عدد المعاملات المصرفية التي تمت؛

- فهرسة الحساب المصرفي للحصول على رقم المعاملة

- تنفيذ حلقات تكرار بترتيب عكسي

- تنفيذ حلقات تكرار على المعاملات

- اختبار انتماء معاملة مصرفية الى الحساب

- تكوين قائمة من معاملات الحساب المصرفية ...

- ما نستطيع القيام به على الكائنات التكرارية

(العديد من الوظائف التالية ليس له قيمة فعلية سوى رغبتنا في التدرب على اعادة تعريفها)



كود :
   def __bool__(self):
       """Special method to allow for 'if acc:' ..."""
       return self.balance > 0

   def __len__(self):
       """Special method to allow for 'len(acc)'"""
       return len(self.transtemp)

   def __getitem__(self, position):
       """Special method to allow for 'acc[n]'"""
       return self.transtemp[position]
   
   def __reversed__(self):
       """Special method to allow reverse ordering"""
       return self.transtemp[::-1]

   def __contains__(self):
       """Special method to allow for 'transaction in acc' ..."""
       return t in self.transtemp
## التجربة 2

كود :
len(acc)
if acc: print('Account balance is OK')
for t in acc: print(t)
list(acc)
acc[1]
list(reversed(acc))
## وظائف المقارنة __eq__ و __lt__

وظائف المقارنة عديدة: == ، > ، < ، >= ، <= ، != وحتى لا نضطر لاعادة تعريفها كلها، سنستورد الدالة total_ordering من الوحدة functools. هذه الدالة لا تحتاج الا الى اعادة تعريف احدى وظائف المقارنة التالية: __lt__أو __le__أو  __gt__ أو __ge__ بالاضافة الى __eq__. نضيف اذا سطر الاستيراد التالي في اول السكربت (وكذلك سطر الـديكورتور @total_ordering):

كود :
from functools import total_ordering

@total_ordering
class Account:
   # ... (like above)
ثم نعرف الوظيفتين __eq__ و __lt__

كود :
   def __eq__(self, other):
       """Special method to test acc1 == acc2"""
       if not isinstance(other, Account):
           raise TypeError('Cannot compare Account object with object of different type')
       return self.balance == other.balance

   def __lt__(self, other):
       """Special method to test acc1 < acc2"""
       if not isinstance(other, Account):
           raise TypeError('Cannot compare Account object with object of different type')
       return self.balance < other.balance
## التجربة 3

كود :
acc1 = Account('Eric', 150)
acc2 = Account('tim', 180)
acc3 = Account('Paul', 200)
acc1 > acc2
acc2 == acc3
acc3 >= acc1
acc1 < acc2 <= acc3
## الوظيفة __add__

رأينا كيف نعيد تعريف المعامل '+' في موضوع سابق، لكن لابأس من اعادة تعريفه هنا لمزيد الفهم. هنا سنقوم بتجميع حسابين مصرفيين أو أكثر في حساب مصرفي واحد:

كود :
   def __add__(self, other):
       """Special method to merge two accounts into new one using +"""
       if not isinstance(other, Account):
           raise TypeError('Cannot merge Account object with object of different type')
       owner = '{}&{}'.format(self.owner, other.owner)
       start_amount = self.balance + other.balance
       return Account(owner, start_amount)
## الوظيفتان __iadd__ و __isub__

الوظيفة __iadd__ تستخدم لاعادة تعريف المعامل += أما الوظيفة __isub__ فتستخدم لاعادة تعريف -=. بالنسبة للفئة Account التي نحن بصدد بنائها، نريد أن نتمكن من سحب واضافة مبالغ من والى الحساب المصرفي باستخدام الوظيفة add_transaction مباشرة او عبر المعاملين += او -=

لاحظ أن كلا المعاملين يعملان in place اي على الحساب نفسه، لذلك سيردان الكائن نفسه (self) بعد معالجته:

كود :
   def __iadd__(self, amount):
       """Special method to allow for acc += amount"""
       self.add_transaction(amount)
       return self

   def __isub__(self, amount):
       """Special method to allow for acc -= amount"""
       if not isinstance(amount, (int, float)):
           raise TypeError("amount needs to be a number")
       self.add_transaction(-amount)
       return self
## التجربة 4

كود :
acc1 + acc2
acc4 = acc2 + acc3
acc4
acc4 += 100
acc4 -= 900
acc4.transtemp
acc4.balance
list(acc4)
## الوظيفة __call__

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

كود :
   def __call__(self):
       """Special method to allow for acc()"""
       print('Start amount: {}'.format(self.start_amount))
       print('Pending transactions: {}'.format(self.transtemp))
       print('Completed transactions: {}'.format(self.transactions))
       print('Balance: {}'.format(self.balance))
## التجربة 5

كود :
acc4()
## الوظيفتان __enter__ و __exit__

تحدثنا قليلا عن مفهوم الـcontext manager في موضوع سابق حول التعامل مع الملفات في بيثون والتعبير with. بشكل مبسط يمكننا تعريف الـcontex manager على أنه عبارة عن بروتوكول بسيط يحتاج الكائن الى اتباعه حتى يتمكن من استخدام التعبير with. كل ما علينا القيام به اساسا هو اعادة تعريف الوظيفتين __enter__ و __exit__ اذا كنا نرغب من كائناتنا ان تعمل مع الـcontext manager.

سنجعل الفئة تدعم مفهوم الـcontext manager وذلك للتراجع آليا اذا اصبح الرصيد سلبيا عند اضافة معاملة مصرفية.

كود :
   def __enter__(self):
       """Special method to allow using 'with' in context manager"""
       print('ENTER WITH: Making backup of transactions for rollback')
       self._copy_transactions = list(self.transactions)
       return self

   def __exit__(self, exc_type, exc_val, exc_tb):
       """Special method to allow using 'with' in context manager"""
       print('EXIT WITH:', end=' ')
       if exc_type:
           self.transactions = list(self._copy_transactions)
           print('Rolling back to previous transactions')
           print('Transaction resulted in {} ({})'.format(
               exc_type.__name__, exc_val))
       else:
           self.transtemp = []
           print('Transaction OK')
       return True                 # أردت تجنب رفع الاستثناء بشكل متسلسل.. غيرها لترى
يجب اطلاق استثناء لتشغيل التراجع عن المعاملة المصرفية التي ادت الى رصيد سلبي. سنعرف لهذا الغرض وظيفة تتحقق من المعاملات المصرفية قبل تنفيذها.

كود :
   def validate_transactions(self):
       """helper method to validate transaction"""
       with self as a:
           print('Committing {} to account'.format(sum(self.transtemp)))
           a.transactions.extend(self.transtemp)
           print('New balance would be: {}'.format(a.balance))
           if a.balance < 0:
               raise ValueError('sorry cannot go in debt!')
## التجربة 6

كود :
acc.validate_transactions()
acc()

acc4.validate_transactions()
## خاتمة

العديد من الوظائف الخاصة في الفئة Account مصطنعة والهدف من تعريفها هو فقط فهم تلك الوظائف الخاصة. أرجو أن يكون هذا الموضوع قد وضحها أكثر. الاستخدام المخطط له جيدا لهذه الوظائف يجعل الكائنات التي تنشئها انطلاقا من فئاتك تتصرف كالكائنات المدمجة في بيثون. يوجد تقريبا 80 وظيفة خاصة يمكن اعادة تعريفها بدرجات صعوبة مختلفة. القائمة الكاملة تجدها في الرابط التالي: https://docs.python.org/3/reference/data...ecialnames . بعضها يشكل مجموعة متناسقة واعادة تعريف احداها يفترض اعادة تعريف البقية كما هو الحال مثلا بالنسبة الى الوظائف __get__ و __set__ و __delete__. المهم أن نستخدمها بحذر وفقط عندما نكون بحاجة فعلية اليها على أن يكون دليل استخدامها امام اعيننا.

اذا وجدت صعوبة أو أخطاء عند نسخ الكود البرمجي للكلاس Account ففي الملحقات اصداران منها: الاول account_v1 شبيه بما في الموضوع، والثاني account_v2 فيه مزيد من الحماية للبيانات. الهدف الاهم من رفع اصدارين هو تبيان كيف يفيد التغليف encapsulation في اصلاح العلل مثلا دون تأثير على الكود البرمجي الذي كتبه مستخدم الكلاس (مستخدم الكلاس ليس بالضرورة مطور الكلاس). لتجربة اصدار منها افتح الملف ثم نفذه (F5) بواسطة IDLE أو انتقل بالطرفية الى المجلد الذي حفظت فيه الملف ونفذ في مفسر بيثون :

كود :
from account_v1 import Account
يمكنك تشغيله مباشرة ايضا عبر python3 account_v1.py لكن استيراده يتيح تجربته بشكل افضل

## بعض مراجع الموضوع:



https://docs.python.org/3/reference/data...thod-names

https://docs.python.org/3/library/functo...l_ordering

https://github.com/pybites/100DaysOfCode...account.py

https://dbader.org/blog/python-dunder-methods


الملفات المرفقة
.zip   account_v1_and_v2.zip (الحجم : 3.34 KB / التحميلات : 0)
الرد


التنقل السريع :


مستخدمين يتصفحوا هذا الموضوع: 1 ضيف