العمل مع وحدة operator في بايثون

عندما تقوم بإجراء عمليات حسابية في بايثون، فإنك تستفيد من عوامل التشغيل المدجمة مثل + و % و **. هل تعلم أن بايثون توفر أيضًا وحدة operator؟ في حين أنه قد يبدو أن الغرض من operator هو توفير بديل لعوامل بايثون، إلا أن الوحدة لها في الواقع غرض أكثر تخصصًا من هذا.

توفر لك وحدة operator مجموعة من الدوال، العديد منها يتوافق مع عوامل التشغيل المدجمة ولكنها لا تحل محلها. توفر الوحدة أيضًا دوال إضافية، كما ستكتشف قريبًا.

استخدام الدوال الأساسية لوحدة operator

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

تعلم كيفية عمل الدوال الأساسية

تحتوي وحدة operator على أكثر من أربعين دالة، الكثير منها يعادل عوامل Python التي تعرفها بالفعل. مثال:

>>> import operator

>>> operator.add(5, 3)  # 5 + 3
8

>>> operator.__add__(5, 3)  # 5 + 3
8

هنا، يمكنك إضافة 5 و 3 معًا باستخدام كل من add() و __add__(). كلاهما ينتج نفس النتيجة. في ظاهر الأمر، توفر لك هذه الدوال نفس الوظيفة التي يوفرها عامل +، ولكن هذا ليس الغرض منها.

إذا ألقيت نظرة على قائمة الدوال التي يوفرها لك عامل  operator، فسوف تكتشف أنها لا تغطي العوامل الحسابية فحسب، بل تغطي أيضًا عوامل المساواة و الهوية والمنطق و حتى عوامل البت. جرب مجموعة عشوائية منهم:

>>> operator.truediv(5, 2)  # 5 / 2
2.5

>>> operator.ge(5, 2)  # 5 >= 2
True

>>> operator.is_("X", "Y")  # "X" is "Y"
False

>>> operator.not_(5 < 3)  # not 5 < 3
True

>>> bin(operator.and_(0b101, 0b110))  # 0b101 & 0b110
'0b100'

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

قبل قراءة بقية هذا الدرس، لا تتردد في تخصيص بعض الوقت لتجربة مجموعة من الدوال المكافئة التي توفرها لك وحدة  operator في Python. سوف تتعلم كيفية استخدامها بعد ذلك.

تمرير Operators كوسائط إلى دوال ذات ترتيب أعلى

يمكنك استخدام الدوال المكافئة ل operator بشكل شائع كوسائط للدوال ذات الترتيب الأعلى. يمكنك كتابة دالة ذات ترتيب أعلى تؤدي سلسلة من المهام المختلفة اعتمادًا على دالة operator التي تم تمريرها إليها. لنفترض، على سبيل المثال، أنك تريد دالة واحدة يمكنها إجراء عمليات الجمع و الطرح و الضرب و القسمة. إحدى الطرق الفوضوية للقيام بذلك هي استخدام عبارة if…elif كما يلي:

def perform_operation(operator_string, operand1, operand2):
    if operator_string == "+":
        return operand1 + operand2
    elif operator_string == "-":
        return operand1 - operand2
    elif operator_string == "*":
        return operand1 * operand2
    elif operator_string == "/":
        return operand1 / operand2
    else:
        return "Invalid operator."

في دالة perform_operation() المعلمة الأولى عبارة عن سلسلة تمثل إحدى العمليات الحسابية الأربع الأساسية. لاختبار الدالة، قم بتمرير كل من العوامل الأربعة. النتائج هي ما تتوقعه:

>>> number1 = 10
>>> number2 = 5
>>> calculations = ["+", "-", "*", "/"]

>>> for op_string in calculations:
...     perform_operation(op_string, number1, number2)
...
15
5
50
2.0

هذا الكود ليس فوضويًا فحسب، بل يقتصر أيضًا على العوامل الأربعة المحددة في عبارات elif. حاول، على سبيل المثال، تمرير عامل (%)، وستعرض الدالة رسالة “Invalid operator” بدلاً من النتيجة التي كنت تأمل فيها.

هذا هو المكان الذي يمكنك فيه الاستفادة بشكل ممتاز من دوال operator. يمنحك تمريرها إلى دالة العديد من المزايا:

def perform_operation(operator_function, operand1, operand2):
    return operator_function(operand1, operand2)

هذه المرة، قمت بتحسين الدالة perform_operation() بحيث يمكن للمعلمة الأولى قبول أي من دوال وحدة operator التي تأخذ وسيطتين بالضبط. المعلمتان الثانية والثالثة هي تلك الوسائط.

يشبه كود الاختبار المنقح ما قمت به من قبل، باستثناء أنك تقوم بتمرير دوال operator لتستخدمها دالة perform_operation():

>>> from operator import add, sub, mul, truediv

>>> number1 = 10
>>> number2 = 5
>>> calculations = [add, sub, mul, truediv]

>>> for op_function in calculations:
...     perform_operation(op_function, number1, number2)
...
15
5
50
2.0

هذه المرة، تحتوي قائمة العمليات الحسابية على إشارات إلى الدوال نفسها. لاحظ أنك تقوم بتمرير أسماء الدوال وليس استدعاءات الدوال. بمعنى آخر، تقوم بتمرير add إلى perform_operation() و ليس add(). أنت تقوم بتمرير كائن الدالة، وليس نتيجة تنفيذه. تذكر أن اسم الدالة هو في الواقع إشارة إلى الكود الخاص بها. باستخدام بناء الجملة () يستدعي الدالة.

هناك ميزتان لاستخدام الإصدار المحدث من Performance_operation(). الأول هو قابلية التوسع. يمكنك استخدام التعليمات البرمجية التي تمت مراجعتها مع أي من دوال operator الأخرى التي تتطلب وسيطتين بالضبط. في الواقع، قد ترغب في التجربة عن طريق تمرير دوال mod() و pow() و repeat() لوحدة operator إلى كلا الإصدارين من دالتك. يعمل الإصدار المحدث كما هو متوقع، بينما يعرض الإصدار الأصلي "Invalid operator".

الميزة الثانية هي سهولة القراءة. ألقِ نظرة على كلا الإصدارين من الدالة Performance_operation()، وستلاحظ أن الإصدار الثاني ليس فقط أقصر بكثير، ولكنه أيضًا أكثر قابلية للقراءة من الإصدار الأصلي.

يعد تمرير الدوال كوسيطات إلى دوال أخرى ميزة ستستخدمها غالبًا في البرمجة الوظيفية. هذا هو أحد الأغراض الرئيسية لوحدة operator. سوف تدرس أمثلة أخرى على ذلك لاحقا.

تسلسل دوال وحدة operator

إحدى طرق حفظ الكائنات، بما في ذلك الدوال، على القرص هي إجراء تسلسل لها. بمعنى آخر، يقوم الكود الخاص بك بتحويلها إلى تدفقات بايت وتخزينها على القرص لاستخدامها لاحقًا. على العكس من ذلك، عندما تقرأ الكائنات المتسلسلة مرة أخرى من القرص، فإنك تقوم بإلغاء تسلسلها، مما يسمح بقراءتها من القرص إلى برنامج للاستخدام.

هناك عدة أسباب وراء إجراء تسلسل للدوال، بما في ذلك حفظها للاستخدام المستقبلي في برنامج آخر أو تمريرها بين العمليات المختلفة التي يتم تشغيلها على جهاز كمبيوتر واحد أو أكثر.

إحدى الطرق الشائعة لتسلسل الدوال في بايثون هي استخدام وحدة Pickle. حيث يوفر رف غلاف القاموس الخاص به، و هوه إحدى أكثر الطرق فعالية لتخزين البيانات. ومع ذلك، عند إجراء تسلسل لدالة باستخدام Pickle، فإنك تقوم فقط بإجراء تسلسل لاسمها المؤهل بالكامل، وليس الكود الموجود في نص الدالة. عند إلغاء تسلسل إحدى الدوال، يجب أن توفر البيئة إمكانية الوصول إلى كود الدالة. لا يمكن أن تعمل الدالة بطريقة أخرى.

لرؤية مثال، عليك إعادة النظر في المثال السابق perform_operation(). ستستدعي دوال operator مختلفة لتنفيذ العمليات المختلفة. تضيف التعليمة البرمجية التالية قاموسًا ستستخدمه لتعيين عامل سلسلة لدالة المطابقة الخاصة به:

>>> import operator
>>> operators = {
...     "+": operator.add,
...     "-": operator.sub,
...     "*": operator.mul,
...     "/": operator.truediv,
... }

>>> def perform_operation(op_string, number1, number2):
...     return operators[op_string](number1, number2)
...

>>> perform_operation("-", 10, 5)
5

العمليات التي تدعمها الدالة perform_operation() هي تلك المحددة في العوامل الحسابية. على سبيل المثال، يمكنك تشغيل العملية “-“، التي تستدعي operator.sub() في الخلفية.

إحدى طرق مشاركة العوامل المدعومة بين العمليات هي إجراء تسلسل لقاموس العوامل على القرص. يمكنك القيام بذلك باستخدام pickle على النحو التالي:

import pickle
with open("operators.pkl", mode="wb") as f:
    pickle.dump(operators, f)

قمت بفتح ملف ثنائي للكتابة. لإجراء تسلسل للعوامل، يمكنك استدعاء Pickle.dump() وتمرير البنية التي تجري تسلسلها ومقبض الملف الوجهة.

يؤدي هذا إلى إنشاء الملف operators.pkl في دليل العمل المحلي. لشرح كيفية إعادة استخدام العوامل في عملية مختلفة، أعد تشغيل Python Shell وقم بتحميل الملف:

>>> import pickle
>>> with open("operators.pkl", mode="rb") as f:
...     operators = pickle.load(f)
...
>>> operators
{'+': <built-in function add>, '-': <built-in function sub>,
 '*': <built-in function mul>, '/': <built-in function truediv>}

أولاً، تقوم باستيراد pickle مرة أخرى وإعادة فتح الملف الثنائي للقراءة. لقراءة بنية operator، يمكنك استخدام Pickle.load() وتمرير مقبض الملف. يقرأ الكود الخاص بك بعد ذلك التعريف المحفوظ ويعينه لمتغير يسمى operators. لا يلزم أن يتطابق هذا الاسم مع اسمك الأصلي. يشير هذا المتغير إلى القاموس الذي يشير إلى الدوال المختلفة، على افتراض أنها متوفرة.

لاحظ أنك لا تحتاج إلى استيراد operator بشكل صريح، على الرغم من أن الوحدة يجب أن تكون متاحة لبايثون لاستيرادها في الخلفية.

يمكنك تعريف render_operation() مرة أخرى لترى أنه يمكنه الرجوع إلى operators المستعادة واستخدامها:

>>> def perform_operation(op_string, number1, number2):
...     return operators[op_string](number1, number2)
...

>>> perform_operation("*", 10, 5)
50

عظيم! يتعامل الكود الخاص بك مع الضرب كما تتوقع.

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

ومع ذلك، لا يمكنك إجراء تسلسل لدوال lambda المجهولة مثل هذا. إذا قمت بتنفيذ المثال دون استخدام وحدة operator، فمن المحتمل أن تقوم بتعريف القاموس على النحو التالي:

operators = {
    "+": lambda a, b: a + b,
    "-": lambda a, b: a - b,
    "*": lambda a, b: a * b,
    "/": lambda a, b: a / b,
}

تعد بنية lambda طريقة سريعة لتعريف الدوال البسيطة، ويمكن أن تكون مفيدة جدًا. ومع ذلك، نظرًا لأن Pickle لا يُجري تسلسلًا لنص الدالة، بل اسم الدالة فقط، فلا يمكنك إجراء تسلسل لدوال lambda المجهولة:

>>> import pickle
>>> with open("operators.pkl", mode="wb") as f:
...     pickle.dump(operators, f)
...
Traceback (most recent call last):
  ...
PicklingError: Can't pickle <function <lambda> at 0x7f5b946cfba0>: ...

إذا حاولت إجراء تسلسل لدوال lambda باستخدام Pickle، فسوف تحصل على خطأ. هذه هي الحالة التي يمكنك فيها غالبًا استخدام وظائف operator بدلاً من دوال lambda.

انظر مرة أخرى إلى كود التسلسل ولاحظ أنك قمت باستيراد كل من operator و pickle، في حين أن كود إلغاء التسلسل استورد فقط pickle. لم تكن بحاجة إلى استيراد operator لأن Pickle قام بذلك تلقائيًا نيابةً عنك عندما قمت باستدعاء دالة load() الخاصة به. يعمل هذا لأن وحدة operator المضمنة متاحة بسهولة.

التحقيق في أداء دالة operator مقابل البدائل

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

إذا كنت ترغب في توقيت دوال وحدة operator مقابل نظيراتها المدمجة أو lambda، فيمكنك استخدام وحدة timeit. أفضل طريقة للقيام بذلك هي تشغيله مباشرة من سطر الأوامر:

PS> python -m timeit "(lambda a, b: a + b)(10, 10)"
5000000 loops, best of 5: 82.3 nsec per loop
PS> python -m timeit -s "from operator import add" "add(10, 10)"
10000000 loops, best of 5: 24.5 nsec per loop
PS> python -m timeit "10 + 10"
50000000 loops, best of 5: 5.19 nsec per loop

PS> python -m timeit "(lambda a, b: a ** b)(10, 10)"
1000000 loops, best of 5: 226 nsec per loop
PS> python -m timeit -s "from operator import pow" "pow(10, 10)"
2000000 loops, best of 5: 170 nsec per loop
PS> python -m timeit "10 ** 10"
50000000 loops, best of 5: 5.18 nsec per loop

تستخدم جلسة PowerShell المذكورة أعلاه وحدة timeit لمقارنة أداء التطبيقات المختلفة للإضافة والأس. تظهر نتائجك أنه في كلتا العمليتين، يكون العامل المدمج هو الأسرع، حيث تتفوق دالة وحدة operator فقط على دالة lambda. تعتبر قيم الوقت الفعلية نفسها خاصة بالجهاز، ولكن الاختلافات النسبية بينها كبيرة.

تفضل وجرب دوال operator الأخرى بنفسك. على الرغم من أن التوقيت الدقيق سيختلف من آلة إلى أخرى، إلا أن الاختلافات النسبية بينهما ستظل تظهر أن العوامل المدمجة دائمًا أسرع من وحدة operator، والتي تكون دائمًا أسرع من دوال lambda.

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

استخدام الدوال ذات الترتيب العالي لوحدة operator

في هذا القسم، ستتعرف على ثلاث من الدوال ذات الترتيب الأعلى التي توفرها لك الوحدة : itemgetter() و attrgetter() و methodcaller(). ستتعلم كيف تسمح لك هذه الأشياء بالعمل مع مجموعات بايثون بمجموعة من الطرق المفيدة التي تشجع النمط الوظيفي لبرمجة بايثون.

اختيار القيم من المجموعات متعددة الأبعاد باستخدام itemgetter()

الدالة الأولى التي ستتعرف عليها هيoperator.itemgetter(). في شكله الأساسي، يمكنك تمرير معلمة واحدة تمثل فهرسًا. ثم يقوم itemgetter() بإرجاع دالة، عند تمرير مجموعة، تقوم بإرجاع العنصر الموجود في ذلك الفهرس.

في البداية، عليك إنشاء قائمة من القواميس:

musician_dicts = [
    {"id": 1, "fname": "Brian", "lname": "Wilson", "group": "Beach Boys"},
    {"id": 2, "fname": "Carl", "lname": "Wilson", "group": "Beach Boys"},
    {"id": 3, "fname": "Dennis", "lname": "Wilson", "group": "Beach Boys"},
    {"id": 4, "fname": "Bruce", "lname": "Johnston", "group": "Beach Boys"},
    {"id": 5, "fname": "Hank", "lname": "Marvin", "group": "Shadows"},
    {"id": 6, "fname": "Bruce", "lname": "Welch", "group": "Shadows"},
    {"id": 7, "fname": "Brian", "lname": "Bennett", "group": "Shadows"},
]

يحتوي كل قاموس على سجل لموسيقي ينتمي إلى إحدى مجموعتي Beach Boys أو The Shadows. لمعرفة كيفية عمل itemgetter()، لنفترض أنك تريد تحديد عنصر واحد من music_dicts:

>>> import operator

>>> get_element_four = operator.itemgetter(4)
>>> get_element_four(musician_dicts)
{"id": 5, "fname": "Hank", "lname": "Marvin", "group": "Shadows"}

عندما تقوم بتمرير itemgetter()‎ فهرسًا بقيمة 4، فإنه يُرجع دالة، تمت الإشارة إليها بواسطة get_element_four، والتي تُرجع العنصر في موضع الفهرس 4 في مجموعة. بمعنى آخر، get_element_four(musician_dicts) يُرجع Music_dicts[4]. تذكر أن عناصر القائمة تتم فهرستها بدءًا من 0 وليس 1. وهذا يعني أن itemgetter(4) يُرجع بالفعل العنصر الخامس في القائمة.

لنفترض بعد ذلك أنك تريد تحديد عناصر من المواضع 1 و 3 و 5. للقيام بذلك، قم بتمرير قيم فهرس متعددة لـ itemgetter():

>>> get_elements_one_three_five = operator.itemgetter(1, 3, 5)
>>> get_elements_one_three_five(musician_dicts)
({"id": 2, "fname": "Carl", "lname": "Wilson", "group": "Beach Boys"},
 {"id": 4, "fname": "Bruce", "lname": "Johnston", "group": "Beach Boys"},
 {"id": 6, "fname": "Bruce", "lname": "Welch", "group": "Shadows"})

هنا يقوم itemgetter() بإنشاء دالة تستخدمها للعثور على العناصر الثلاثة جميعها. تقوم دالتك بإرجاع  tuple يحتوي على النتائج.

لنفترض الآن أنك تريد إدراج قيم الاسم الأول والأخير فقط من القواميس في مواضع الفهرس 1 و 3 و 5. للقيام بذلك، قم بتمرير itemgetter() المفتاحين “fname” و “lname“:

>>> get_names = operator.itemgetter("fname", "lname")

>>> for musician in get_elements_one_three_five(musician_dicts):
...     print(get_names(musician))
...
("Carl", "Wilson")
("Bruce", "Johnston")
("Bruce", "Welch")

هذه المرة، يوفر لك itemgetter() دالة للحصول على القيم المرتبطة بمفاتيح “fname” و “lname“. يتكرر الكود عبر مجموعة القواميس التي يتم إرجاعها بواسطة get_elements_one_three_five() ويمرر كل واحد منها إلى دالة get_names(). يؤدي كل استدعاء إلى get_names() إلى إرجاع tuple يحتوي على القيم المرتبطة بمفاتيح القاموس “fname” و “lname” للقواميس في المواضع 1 و 3 و 5 من  musician_dicts.

هناك دلتان من ربما تكونان على دراية بهما بالفعل وهما min() و max(). يمكنك استخدام هذه للعثور على العناصر الأدنى والأعلى في القائمة:

>>> prices = [100, 45, 345, 639]
>>> min(prices)
45
>>> max(prices)
639

في الكود أعلاه، حصلت على القيمتين الأدنى و الأعلى: تُرجع min() العنصر الأرخص، بينما تُرجع max() العنصر الأكثر تكلفة.

تحتوي الدالتان min() و max() على معلمة رئيسية تقبل دالة. إذا قمت بإنشاء الدالة باستخدام itemgetter()، فيمكنك استخدامها لتوجيه min() و max() لتحليل عناصر محددة ضمن قائمة القوائم أو القواميس. لاستكشاف ذلك، عليك أولاً إنشاء قائمة بقوائم الموسيقيين:

musician_lists = [
    [1, "Brian", "Wilson", "Beach Boys"],
    [2, "Carl", "Wilson", "Beach Boys"],
    [3, "Dennis", "Wilson", "Beach Boys"],
    [4, "Bruce", "Johnston", "Beach Boys"],
    [5, "Hank", "Marvin", "Shadows"],
    [6, "Bruce", "Welch", "Shadows"],
    [7, "Brian", "Bennett", "Shadows"],
]

محتوى musician_lists مطابق لمحتوى musician_dicts، باستثناء أن كل سجل موجود في القائمة. هذه المرة، لنفترض أنك تريد العثور على عناصر القائمة ذات قيم المعرف الأدنى والأعلى:

>>> get_id = operator.itemgetter(0)

>>> min(musician_lists, key=get_id)
[1, "Brian", "Wilson", "Beach Boys"]
>>> max(musician_lists, key=get_id)
[7, "Brian", "Bennett", "Shadows"]

عليك أولاً إنشاء دالة باستخدام itemgetter()‎ لتحديد العنصر الأول من القائمة. ثم تقوم بتمرير هذا كمعلمة رئيسية لـ min() و max(). ستعيد لك الدالتان min() و max() القوائم ذات القيم الدنيا والأعلى في مواضع الفهرس 0، على التوالي.

يمكنك أن تفعل الشيء نفسه مع قائمة القواميس باستخدام itemgetter() لإنشاء دالة تحدد أسماء المفاتيح. لنفترض أنك تريد القاموس الذي يحتوي على الموسيقي الذي يأتي اسمه الأخير أولاً في الأبجدية:

>>> get_lname = operator.itemgetter("lname")

>>> min(musician_dicts, key=get_lname)
{"id": 7, "fname": "Brian", "lname": "Bennett", "group": "Shadows"}

هذه المرة، قمت بإعداد دالة itemgetter()‎ التي تحدد مفتاح القاموس “lname“. يمكنك بعد ذلك تمرير هذا كمعلمة رئيسية للدالة min(). تقوم الدالة min() بإرجاع القاموس الذي يحتوي على أقل قيمة لـ “lname“. سجل “Bennett” هو النتيجة. لماذا لا تعيد المحاولة باستخدام max()؟ حاول التنبؤ بما سيحدث قبل تشغيل التعليمات البرمجية الخاصة بك للتحقق.

استرداد السمات من الكائنات باستخدام attrgetter()

بعد ذلك ستتعرف على دالة attrgetter() الخاصة بوحدة operator. تتيح لك دالة attrgetter()‎ الحصول على سمات الكائن. تقبل الدالة سمة واحدة أو أكثر ليتم استرجاعها من كائن ما، وتقوم بإرجاع دالة ستعيد تلك السمات من أي كائن تمرر إليه. لا يلزم أن تكون الكائنات التي تم تمريرها إلى attrgetter() من نفس النوع. إنهم بحاجة فقط إلى احتواء السمة التي تريد استردادها.

لفهم كيفية عمل attrgetter()، ستحتاج أولاً إلى إنشاء فئة جديدة:

from dataclasses import dataclass

@dataclass
class Musician:
    id: int
    fname: str
    lname: str
    group: str

لقد قمت بإنشاء فئة بيانات باسم Musician. الغرض الأساسي من فئة البيانات الخاصة بك هو الاحتفاظ ببيانات حول كائنات موسيقية مختلفة، على الرغم من أنها قد تحتوي أيضًا على توابع، كما ستكتشف لاحقًا. يتيح لك مزخرف @dataclass تحديد السمات مباشرة عن طريق تحديد أسمائها وتلميح النوع لأنواع البيانات الخاصة بها. يحتوي فصلك على أربع سمات تصف الموسيقي.

بعد ذلك، تحتاج إلى قائمة بالكائنات التي يمكنك العمل معها. ستعيد استخدام musician_lists السابقة وستستخدمها لإنشاء قائمة بالكائنات المسماة group_members:

musician_lists = [
    [1, "Brian", "Wilson", "Beach Boys"],
    [2, "Carl", "Wilson", "Beach Boys"],
    [3, "Dennis", "Wilson", "Beach Boys"],
    [4, "Bruce", "Johnston", "Beach Boys"],
    [5, "Hank", "Marvin", "Shadows"],
    [6, "Bruce", "Welch", "Shadows"],
    [7, "Brian", "Bennett", "Shadows"],
]
group_members = [Musician(*musician) for musician in musician_lists]

يمكنك ملء group_members بكائنات Musician عن طريق تحويل musician_lists بفهم القائمة.

لديك الآن قائمة group_members التي تحتوي على سبعة كائنات Musician، أربعة من Beach Boys وثلاثة من Shadows. ستتعلم بعد ذلك كيف يمكنك استخدامها مع attrgetter().

لنفترض أنك تريد استرداد السمة .fname من كل عنصر من عناصر group_members:

>>> import operator

>>> get_fname = operator.attrgetter("fname")

>>> for person in group_members:
...     print(get_fname(person))
...
Brian
Carl
Dennis
Bruce
Hank
Bruce
Brian

عليك أولاً استدعاء attrgetter()‎ و تحديد أن مخرجاته ستحصل على السمة ‎.fname. تقوم الدالة attrgetter() بعد ذلك بإرجاع دالة ستمنحك السمة .fname لأي كائن تقوم بتمريره إليه. عندما تقوم بالتكرار فوق مجموعة كائنات Musician الخاصة بك، فإن get_fname() سوف يُرجع سمات .fname.

تسمح لك الدالة attrgetter() أيضًا بإعداد دالة يمكنها إرجاع عدة سمات في وقت واحد. لنفترض أنك تريد هذه المرة إرجاع سمات ‎.id و ‎.lname لكل كائن:

>>> get_id_lname = operator.attrgetter("id", "lname")

>>> for person in group_members:
...     print(get_id_lname(person))
...
(1, "Wilson")
(2, "Wilson")
(3, "Wilson")
(4, "Johnston")
(5, "Marvin")
(6, "Welch")
(7, "Bennett")

هذه المرة، عندما تتصل بـ attrgetter() وتطلب كلاً من السمتين .id و .lname، فإنك تقوم بإنشاء دالة قادرة على قراءة كلتا السمتين لأي كائن. عند التشغيل، تقوم التعليمات البرمجية بإرجاع كل من .id و .lname من قائمة كائنات Musician التي تم تمريرها إليها. بالطبع، يمكنك تطبيق هذه الدالة على أي كائن، سواء كان مضمنًا أو مخصصًا، طالما أن الكائن يحتوي على سمة ‎.id و سمة ‎.lname.

فرز قوائم الكائنات والبحث فيها حسب السمة باستخدام attrgetter()

تسمح لك الدالة attrgetter() أيضًا بفرز مجموعة من الكائنات حسب سماتها. ستجرب ذلك عن طريق فرز كائنات Musician في group_members حسب معرف كل كائن بترتيب عكسي.

أولاً، تأكد من أن لديك حق الوصول إلى group_members، كما هو محدد في القسم السابق. بعد ذلك، يمكنك استخدام قوة attrgetter() لإجراء الفرز المخصص الخاص بك:

>>> get_id = operator.attrgetter("id")
>>> for musician in sorted(group_members, key=get_id, reverse=True):
...     print(musician)
...
Musician(id=7, fname='Brian', lname='Bennett', group='Shadows')
Musician(id=6, fname='Bruce', lname='Welch', group='Shadows')
Musician(id=5, fname='Hank', lname='Marvin', group='Shadows')
Musician(id=4, fname='Bruce', lname='Johnston', group='Beach Boys')
Musician(id=3, fname='Dennis', lname='Wilson', group='Beach Boys')
Musician(id=2, fname='Carl', lname='Wilson', group='Beach Boys')
Musician(id=1, fname='Brian', lname='Wilson', group='Beach Boys')

في مقتطف التعليمات البرمجية هذا، قمت بإعداد دالة attrgetter() التي يمكنها إرجاع سمة .id. لفرز القائمة بشكل عكسي حسب .id، يمكنك تعيين مرجع get_id إلى المعلمة الرئيسية للتابع sorted() وتعيين reverse=True. عند طباعة كائنات “Musician“، يُظهر الإخراج أن عملية الفرز قد نجحت بالفعل.

إذا كنت تريد إظهار الكائن بأعلى أو أدنى قيمة .id، فاستخدم الدالة min() أو max() وتمريره مرجعًا إلى دالة get_id() كمفتاح لها:

>>> min(group_members, key=get_id)
Musician(id=1, fname='Brian', lname='Wilson', group='Beach Boys')
>>> max(group_members, key=get_id)
Musician(id=7, fname='Brian', lname='Bennett', group='Shadows')

عليك أولاً إنشاء دالة attrgetter()‎ التي يمكنها تحديد موقع سمة .id. ثم تقوم بتمريرها إلى الدالتين min() و max(). في هذه الحالة، تقوم التعليمات البرمجية بإرجاع الكائنات التي تحتوي على قيم سمات .id الأدنى والأعلى. في هذه الحالة، هذه هي الكائنات ذات قيم 1 و7. قد ترغب في تجربة ذلك بشكل أكبر عن طريق الفرز على سمات أخرى.

استدعاء التوابع على الكائنات باستخدام Methodcaller()

الدالة الأخيرة التي ستتعرف عليها هي Methodcaller(). و هي مشابهة من الناحية النظرية لـ attrgetter()، إلا أنها تعمل على التوابع. لاستخدامها، عليك تمرير اسم التابع، بالإضافة إلى أي معلمات يتطلبها التابع. ستُرجع دالة تستدعي التابع على أي كائن تمرره إليه. لا يلزم أن تكون الكائنات التي تم تمريرها إلى Methodcaller() من نفس النوع. إنهم بحاجة فقط إلى احتواء التابع التي تتصل بها.

لمعرفة المزيد حول methodcaller()، تحتاج أولاً إلى تحسين فئة بيانات Musician الموجودة لديك باستخدام التابع:

from dataclasses import dataclass

@dataclass
class Musician:
    id: int
    fname: str
    lname: str
    group: str

    def get_full_name(self, last_name_first=False):
        if last_name_first:
            return f"{self.lname}, {self.fname}"
        return f"{self.fname} {self.lname}"

يمكنك إضافة التابع .get_full_name() إلى Musician الذي يقبل معلمة واحدة تسمى last_name_first مع الإعداد الافتراضي False. يتيح لك هذا تحديد الترتيب الذي يتم به إرجاع الأسماء.

لنفترض أنك تريد استدعاء .get_full_name() على كل كائن في قائمة group_members المحددة مسبقًا:

>>> import operator
>>> first_last = operator.methodcaller("get_full_name")
>>> for person in group_members:
...     print(first_last(person))
...
Brian Wilson
Carl Wilson
Dennis Wilson
Bruce Johnston
Hank Marvin
Bruce Welch

هنا يمكنك استخدام الدالة methodcaller() لإنشاء دالة باسم first_last() والتي ستستدعي التابع .get_full_name() لأي كائن تمرر إليه. لاحظ أنك لا تمرر أي وسيطات إضافية إلى الدالة first_last()، لذا ستتلقى قائمة بالأسماء الأولى متبوعة بالأسماء الأخيرة لجميع كائنات Musician.

إذا كنت تريد أن تتبع الأسماء الأولى أسماء العائلة، فيمكنك تمرير قيمة True لـ last_name_first:

>>> last_first = operator.methodcaller("get_full_name", True)
>>> for person in group_members:
...     print(last_first(person))
...
Wilson, Brian
Wilson, Carl
Wilson, Dennis
Johnston, Bruce
Marvin, Hank
Welch, Bruce
Bennett, Brian

هذه المرة، يمكنك استخدام methodcaller() لإنشاء دالة باسم last_first() والتي ستستدعي التابع .get_full_name() لأي كائن تم تمريره إليها، ولكنها ستمرر أيضًا True إلى المعلمة last_name_first. تتلقى الآن قائمة بالأسماء الأخيرة ثم الأسماء الأولى لجميع كائنات Musician.

تمامًا كما هو الحال عند استخدام attrgetter() لاسترداد السمات، يمكن أن تكون الكائنات التي تم تمريرها إلى methodcaller() إما مدمجة أو مخصصة. إنهم بحاجة فقط إلى احتواء التابع التي تريد الاتصال بها.


اكتشاف المزيد من بايثون العربي

اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

Scroll to Top

اكتشاف المزيد من بايثون العربي

اشترك الآن للاستمرار في القراءة والحصول على حق الوصول إلى الأرشيف الكامل.

Continue reading