Functools - قوة وظائف الترتيب الأعلى في بايثون

عقدة المصدر: 1865357

تم نشر هذه المقالة كجزء من مدونة علوم البيانات

المُقدّمة

تحتوي مكتبة Python القياسية على العديد من الوحدات النمطية الرائعة للمساعدة في الحفاظ على نظافة التعليمات البرمجية الخاصة بك وأبسطها بالتأكيد واحدة من

Caching

لنبدأ ببعض من أبسط الوظائف القوية للوحدات الوظيفية للوحدات. لنبدأ بوظائف التخزين المؤقت (وكذلك المصممين) - lru_cache,cache و cached_property. أولهم - lru_cache يوفر ذاكرة تخزين مؤقت للنتائج الأخيرة لتنفيذ الوظائف ، أو بعبارة أخرى ، يتذكر نتيجة عملهم:

from functools import lru_cache
import requests @lru_cache(maxsize=32)
def get_with_cache(url): try: r = requests.get(url) return r.text except: return "Not Found" for url in ["https://google.com/", "https://reddit.com/", "https://google.com/", "https://google.com/"]: get_with_cache(url) print(get_with_cache.cache_info())
# CacheInfo(hits=2, misses=4, maxsize=32, currsize=4)
print(get_with_cache.cache_parameters())
# {'maxsize': 32, 'typed': False}

في هذا المثال ، نجري طلبات GET ونخزن نتائجها مؤقتًا (حتى 32 نتيجة) باستخدام أداة تزيين @lru_cache. لمعرفة ما إذا كان التخزين المؤقت يعمل بالفعل ، يمكنك التحقق من معلومات ذاكرة التخزين المؤقت للوظيفة باستخدام طريقة cache_infoيُظهر عدد مرات الدخول إلى ذاكرة التخزين المؤقت وعدد مرات الوصول إليها. يوفر المصمم أيضًا طرقًا clear_cacheو cache_parametersلإلغاء النتائج المخبأة ومعلمات الاختبار ، على التوالي.

إذا كنت بحاجة إلى المزيد من التخزين المؤقت الدقيق ، فيمكنك تضمين وسيطة اختيارية مكتوبة = true ، والتي تتيح لك تخزين أنواع مختلفة من الوسائط مؤقتًا بشكل منفصل.

مصمم آخر للتخزين المؤقت في functools هو وظيفة تسمى ببساطة cache. إنه غلاف بسيط lru_cacheيحذف الوسيطة max_size ، ويقللها ، ولا يزيل القيم القديمة.

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

from functools import cached_property class Page: @cached_property def render(self, value): # Do something with supplied value... # Long computation that renders HTML page... return html

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

جمال آخر cached_propertyهو أنه يعمل فقط عند البحث ، لذلك يسمح لنا بتغيير قيمة السمة. بعد تغيير السمة ، لن تتغير القيمة المخزنة مؤقتًا مسبقًا ، وبدلاً من ذلك ، سيتم حساب القيمة الجديدة وتخزينها مؤقتًا. يمكنك أيضًا مسح ذاكرة التخزين المؤقت ، وكل ما عليك فعله هو إزالة السمة.

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

المقارنة والطلب

ربما تعلم بالفعل أنه يمكنك تنفيذ عوامل المقارنة في Python مثل <, >=or ==، مع lt, gtor eq. ومع ذلك ، قد يكون من المحبط للغاية إدراك كل من eq, lt, le, gtor ge. لحسن الحظ ، هناك أدوات تزيين هناك @total_orderingيمكن أن يساعدنا في ذلك ، لأن كل ما نحتاج إلى تنفيذه هو eqإحدى الطرق المتبقية ، وسيتم إنشاء باقي مصمم الديكور تلقائيًا.

from functools import total_ordering @total_ordering
class Number: def __init__(self, value): self.value = value def __lt__(self, other): return self.value Number(3))
# True
print(Number(1) = Number(15))
# True
print(Number(10) <= Number(2))
# False

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

الزائد

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

جزئي

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

def output_result(result, log=None): if log is not None: log.debug(f"Result is: {result}") def concat(a, b): return a + b import logging
from multiprocessing import Pool
from functools import partial logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("default") p = Pool()
p.apply_async(concat, ("Hello ", "World"), callback=partial(output_result, log=logger))
p.close()
p.join()

يوضح الكود أعلاه كيف يمكنك استخدامه partialلتمرير وظيفة ( output_result) مع حجة ( log=logger) بمثابة رد اتصال. في هذه الحالة ، سنستخدم multrocessing.apply_async ، والتي تحسب بشكل غير متزامن نتيجة الوظيفة ( concat) وإرجاع نتيجة رد الاتصال. لكن، apply_asyncستمرر دائمًا النتيجة باعتبارها الوسيطة الأولى ، وإذا أردنا تضمين أي وسيطات إضافية ، كما هو الحال مع log = logger ، فنحن بحاجة إلى استخدام جزئي.

لقد درسنا حالة استخدام متقدمة إلى حد ما ، ومثال أبسط هو الإنشاء المعتاد لوظيفة تكتب في stderr بدلاً من stdout:

import sys
from functools import partial print_stderr = partial(print, file=sys.stderr)
print_stderr("This goes to standard error output")

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

ومثال جيد أخير. يمكننا ان نستخدم partialبالتزامن مع وظيفة غير معروفة iterلإنشاء مكرر عن طريق تمرير كائن قابل للاستدعاء و sentinelin iter، والتي يمكن تطبيقها على النحو التالي:

from functools import partial RECORD_SIZE = 64 # Read binary file...
with open("file.data", "rb") as file: records = iter(partial(file.read, RECORD_SIZE), b'') for r in records: # Do something with the record...

عادة ، عند قراءة ملف ، نريد أن نكرره عبر الأسطر ، ولكن في حالة البيانات الثنائية ، قد نحتاج إلى تكرار السجلات ذات الحجم الثابت. يمكنك القيام بذلك عن طريق إنشاء كائن قابل للاستدعاء باستخدام partialيقرأ الجزء المحدد من البيانات ويمررها iterلإنشاء مكرر. يستدعي هذا المكرر بعد ذلك وظيفة القراءة حتى تصل إلى نهاية الملف ، مع أخذ حجم القطعة المحدد دائمًا فقط ( RECORD_SIZE). أخيرًا ، عند الوصول إلى نهاية الملف ، القيمة sentinel(ب ") يتم إرجاعه ويتوقف التكرار.

ديكور

لقد تحدثنا بالفعل عن بعض المصممين في الأقسام السابقة ، لكن ليس عن المصممين لخلق المزيد من الزخارف. أحد هؤلاء المصممين هو functools.wraps. لفهم سبب حاجتك إليها ، دعنا نلقي نظرة على مثال:

def decorator(func): def actual_func(*args, **kwargs): """Inner function within decorator, which does the actual work""" print(f"Before Calling {func.__name__}") func(*args, **kwargs) print(f"After Calling {func.__name__}") return actual_func @decorator
def greet(name): """Says hello to somebody""" print(f"Hello, {name}!") greet("Martin")
# Before Calling greet
# Hello, Martin!
# After Calling greet

يوضح هذا المثال كيف يمكن تنفيذ مصمم بسيط. نلتف وظيفة تؤدي مهمة محددة ( actual_func) بمصمم خارجي ، ويصبح المصمم نفسه ، والذي يمكن بعد ذلك تطبيقه على وظائف أخرى ، على سبيل المثال ، كما هو الحال مع greet. عندما تستدعي الوظيفة ، greet سترى أنه يطبع الرسائل على حد سواء من actual_funcومن تلقاء نفسها. يبدو بخير ، أليس كذلك؟ لكن ماذا يحدث إذا فعلنا هذا:

print(greet.__name__)
# actual_func
print(greet.__doc__)
# Inner function within decorator, which does the actual work

عندما نسمي اسم وتوثيق الوظيفة المزخرفة ، ندرك أنه قد تم استبدالها بقيم من وظيفة الديكور. هذا أمر سيء لأننا لا نستطيع إعادة كتابة جميع أسماء وظائفنا ووثائقنا عندما نستخدم بعض المصممين. كيف يمكن حل هذه المشكلة؟ بالطبع ، مع functools.wraps:

from functools import wraps def decorator(func): @wraps(func) def actual_func(*args, **kwargs): """Inner function within decorator, which does the actual work""" print(f"Before Calling {func.__name__}") func(*args, **kwargs) print(f"After Calling {func.__name__}") return actual_func @decorator
def greet(name): """Says hello to somebody""" print(f"Hello, {name}!") print(greet.__name__)
# greet
print(greet.__doc__)
# Says hello to somebody

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

تخفيض

أخيرًا وليس آخرًا في الأدوات الوظيفية للوحدة النمطية هو هذا التقليل. ربما من لغات أخرى ، قد تعرفها على أنها fold(هاسكل). تأخذ هذه الوظيفة قابلة للتكرار وتطوي (تضيف) جميع قيمها في قيمة واحدة. هناك العديد من التطبيقات لذلك ، على سبيل المثال:

from functools import reduce
import operator def product(iterable): return reduce(operator.mul, iterable, 1) def factorial(n): return reduce(operator.mul, range(1, n)) def sum(numbers): # Use `sum` function from standard library instead return reduce(operator.add, numbers, 1) def reverse(iterable): return reduce(lambda x, y: y+x, iterable) print(product([1, 2, 3]))
# 6
print(factorial(5))
# 24
print(sum([2, 6, 8, 3]))
# 20
print(reverse("hello"))
# olleh

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

وإذا كنت تتذكر ذلك reduce غالبًا ما يقصر كل شيء إلى سطر واحد ، ويمكن دمجه تمامًا مع partial:

product = partial(reduce, operator.mul) print(product([1, 2, 3]))
# 6

وأخيرًا ، إذا كنت بحاجة إلى أكثر من مجرد النتيجة النهائية "المنهارة" ، فيمكنك استخدامها accumulate- من وحدة أخرى رائعة itertools. لحساب الحد الأقصى يمكن استخدامه على النحو التالي:

from itertools import accumulate data = [3, 4, 1, 3, 5, 6, 9, 0, 1] print(list(accumulate(data, max)))
# [3, 4, 4, 4, 5, 6, 9, 9, 9]

وفي الختام

كما ترى ، هناك العديد من الوظائف المفيدة وأدوات التزيين التي يمكن أن تجعل حياتك أسهل ، ولكن هذا مجرد غيض من فيض. كما قلت في البداية ، هناك العديد من الوظائف في مكتبة Python القياسية التي تساعدك على كتابة كود أفضل ، لذلك بالإضافة إلى الوظائف التي غطيناها هنا ، يمكنك الانتباه إلى وحدات أخرى ، مثل operatoror itertool. لأية استفسارات ، يمكنك النقر فوق مربع التعليق الخاص بي. سأبذل قصارى جهدي لحل المدخلات الخاصة بك وآمل أن أقدم لك المخرجات المرجوة. يمكنك أيضًا التواصل معي على LinkedIn: -

https://www.linkedin.com/in/shivani-sharma-aba6141b6/

الوسائط الموضحة في هذه المقالة ليست مملوكة لشركة Analytics Vidhya ويتم استخدامها وفقًا لتقدير المؤلف.

المصدر: https://www.analyticsvidhya.com/blog/2021/08/functools-the-power-of-higher-order-functions-in-python/

الطابع الزمني:

اكثر من تحليلات Vidhya