عندما تقوم بإجراء عمليات حسابية في بايثون، فإنك تستفيد من عوامل التشغيل المدجمة مثل +
و %
و **
. هل تعلم أن بايثون توفر أيضًا وحدة 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()
إما مدمجة أو مخصصة. إنهم بحاجة فقط إلى احتواء التابع التي تريد الاتصال بها.
اكتشاف المزيد من بايثون العربي
اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.