Functools -قدرت توابع درجه بالاتر در پایتون

گره منبع: 1865357

این مقاله به عنوان بخشی از بلاگاتون علم داده

معرفی

کتابخانه استاندارد پایتون ماژول‌های بسیار خوبی دارد که به پاک‌تر و ساده‌تر نگه داشتن کد شما کمک می‌کند و ابزارهای کاربردی قطعا یکی از

ذخیره سازی

بیایید با برخی از ساده ترین و در عین حال قدرتمندترین توابع تابع ماژول شروع کنیم. بیایید با توابع ذخیره سازی (و همچنین تزئینات) شروع کنیم - 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به ترتیب برای لغو نتایج ذخیره شده و پارامترهای تست.

اگر به ذخیره سازی گرانول بیشتری نیاز دارید، می توانید یک آرگومان اختیاری typed=true اضافه کنید که به شما امکان می دهد انواع مختلف آرگومان ها را به طور جداگانه کش کنید.

یکی دیگر از دکوراتورها برای ذخیره سازی در ابزارهای کاربردی تابعی است که به سادگی نامیده می شود 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

این مثال ساده نشان می دهد. همانطور که می توان از ویژگی cache استفاده کرد، به عنوان مثال، برای کش کردن یک صفحه HTML رندر شده که باید بارها و بارها به کاربر نشان داده شود. همین کار را می توان برای پرس و جوهای پایگاه داده خاص یا محاسبات ریاضی طولانی انجام داد.

زیبایی دیگر cached_propertyاین است که فقط در جستجو اجرا می شود، بنابراین به ما اجازه می دهد تا مقدار ویژگی را تغییر دهیم. پس از تغییر ویژگی، مقدار ذخیره شده قبلی تغییر نمی کند، در عوض، مقدار جدید محاسبه و ذخیره می شود. همچنین می‌توانید کش را پاک کنید و تنها کاری که باید انجام دهید حذف ویژگی است.

من می‌خواهم این بخش را با یک هشدار در مورد همه دکوراتورهای بالا به پایان برسانم - اگر عملکرد شما دارای عوارض جانبی است یا اگر هر بار که فراخوانی می‌شود اشیاء قابل تغییر ایجاد می‌کند از آنها استفاده نکنید زیرا واضح است که اینها توابعی نیستند که می‌خواهید حافظه پنهان کنید. .

مقایسه و ترتیب

احتمالاً می دانید که می توانید عملگرهای مقایسه را در پایتون پیاده سازی کنید <, >=or ==، با lt, gtor eq. با این حال، درک هر یک از آنها می تواند بسیار خسته کننده باشد eq, lt, le, gtor ge. خوشبختانه، functools یک دکوراتور وجود دارد @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

به این ترتیب می‌توانیم با وجود داشتن تنها eq و lt دستی، تمام عملیات مقایسه گسترده را اجرا کنیم. بارزترین مزیت آن راحتی است، یعنی نیازی به نوشتن تمام این روش‌های جادویی اضافی نیست، اما احتمالاً کاهش مقدار کد و خوانایی بهتر آن مهم‌تر است.

اضافه بار

احتمالاً به همه ما آموخته‌ایم که در پایتون اضافه بار وجود ندارد، اما در واقع یک راه آسان برای پیاده‌سازی آن با استفاده از دو تابع از functools، یعنی تک ارسال و/یا روش تک ارسال وجود دارد. این توابع به ما کمک می کند تا آنچه را که الگوریتم ارسال چندگانه می نامیم پیاده سازی کنیم که به زبان های برنامه نویسی تایپ شده پویا مانند پایتون اجازه می دهد تا بین انواع در زمان اجرا تمایز قائل شوند.

جزئي

همه ما با کتابخانه‌ها یا چارچوب‌های خارجی مختلفی کار می‌کنیم، که بسیاری از آنها توابع و رابط‌هایی را ارائه می‌کنند که ما را ملزم به ارسال فراخوانی می‌کنند، مانند عملیات ناهمزمان یا گوش دادن به رویدادها. این چیز جدیدی نیست، اما اگر لازم باشد برخی از آرگومان‌ها را نیز همراه با callback ارسال کنیم، چه می‌شود. اینجاست که در ابزارهای کاربردی مفید است.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) به عنوان یک تماس. در این مورد، از multiprocessing.apply_async استفاده می کنیم که به صورت ناهمزمان نتیجه تابع را محاسبه می کند ( concat) و نتیجه تماس برگشتی را برمی گرداند. با این حال، apply_asyncهمیشه نتیجه را به عنوان اولین آرگومان ارسال می کند، و اگر بخواهیم هر آرگومان اضافی را اضافه کنیم، همانطور که در مورد log=logger است، باید از partial استفاده کنیم.

ما یک مورد استفاده نسبتاً پیشرفته را در نظر گرفته‌ایم، و یک مثال ساده‌تر ایجاد تابعی است که به جای stdout در stderr می‌نویسد:

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

وقتی نام و مستندات تابع تزئین شده را فرا می‌خوانیم، متوجه می‌شویم که با مقادیر تابع دکوراتور جایگزین شده‌اند. این بد است زیرا وقتی از دکوراتور استفاده می کنیم نمی توانیم همه نام های تابع و مستندات خود را بازنویسی کنیم. چگونه می توان این مشکل را حل کرد؟ البته با ابزارهای کاربردی.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همچنین یک دکوراتور است، شما به سادگی می توانید آن را به actual_func ما اضافه کنید، و مشکل حل می شود!

کاستن

آخرین اما نه کم اهمیت ترین در تابع ماژول این کاهش است. شاید از زبان های دیگر، شما ممکن است آن را به عنوان 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]

نتیجه

همانطور که می بینید Functools، عملکردها و تزئینات مفید زیادی وجود دارد که می تواند زندگی شما را آسان تر کند، اما این فقط نوک کوه یخ است. همانطور که در ابتدا گفتم، توابع زیادی در کتابخانه استاندارد پایتون وجود دارد که به شما کمک می کند کد بهتری بنویسید، بنابراین علاوه بر توابعی که در اینجا به آن پرداختیم، می توانید به ماژول های دیگری مانند operatoror itertool. برای هر گونه سوال، می توانید باکس نظرات من را بزنید. تمام تلاشم را می کنم تا ورودی های شما را حل کنم و امیدوارم بتوانم خروجی های دلخواه را به شما ارائه دهم. همچنین می توانید در لینکدین با من تماس بگیرید:

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