functools --- توابع مرتبه بالاتر و عملیات روی اشیای فراخوانیپذیر¶
کد منبع: Lib/functools.py
ماژول functools برای توابع مرتبه بالاتر است: توابعی که روی توابع دیگر عمل میکنند یا آنها را برمیگردانند. بهطور کلی، هر شیء فراخوانیپذیر میتواند برای مقاصد این ماژول بهعنوان یک تابع در نظر گرفته شود.
ماژول functools توابع زیر را تعریف میکند:
- @functools.cache(user_function)¶
نهانگاه ساده، سبک و نامحدود توابع. گاهی به آن "memoize" گفته میشود.
مشابه
lru_cache(maxsize=None)را برمیگرداند و پوششی نازک حول جستوجوی آرگومانهای تابع در دیکشنری ایجاد میکند. از آنجا که هرگز نیازی به حذف مقادیر قدیمی ندارد، این از@lru_cacheبا محدودیت اندازه کوچکتر و سریعتر است.برای مثال:
@cache def factorial(n): return n * factorial(n-1) if n else 1 >>> factorial(10) # no previously cached result, makes 11 recursive calls 3628800 >>> factorial(5) # no new calls, just returns the cached result 120 >>> factorial(12) # two new recursive calls, factorial(10) is cached 479001600
نهانگاه ایمن برای نخها است تا بتوان از تابع پوششدادهشده در چندین نخ استفاده کرد. این بدان معناست که ساختار داده زیرین در حین بهروزرسانیهای همزمان منسجم باقی میماند.
ممکن است تابع پوشیدهشده بیش از یک بار فراخوانی شود، اگر نخ دیگری پیش از آنکه فراخوانی اولیه کامل شده و در نهانگاه ذخیره شده باشد، یک فراخوانی اضافی انجام دهد.
اضافه شده در نسخهی 3.9.
- @functools.cached_property(func)¶
یک متد از کلاس را به پراپرتی تبدیل میکند که مقدار آن یک بار محاسبه میشود و سپس برای طول عمر نمونه بهعنوان یک ویژگی معمولی در نهانگاه ذخیره میشود. مشابه
@property، با افزودن نهانگاهسازی. برای پراپرتیهای محاسبهشدهی پرهزینه در نمونههایی که در غیر این صورت عملاً تغییرناپذیر هستند، مفید است.مثال:
class DataSet: def __init__(self, sequence_of_numbers): self._data = tuple(sequence_of_numbers) @cached_property def stdev(self): return statistics.stdev(self._data)
سازوکار
@cached_propertyتا حدی با@propertyمتفاوت است. یک پراپرتی معمولی، نوشتن روی ویژگی را مسدود میکند، مگر اینکه یک setter تعریف شده باشد. در مقابل، یک cached_property اجازهی نوشتن میدهد.دکوراتور cached_property فقط در جستوجوها اجرا میشود و تنها زمانی که ویژگیای با همان نام وجود نداشته باشد. هنگامی که اجرا شود، cached_property در ویژگی با همان نام مینویسد. خواندنها و نوشتنهای بعدی ویژگی بر متد cached_property تقدم دارند و مانند یک ویژگی عادی کار میکند.
مقدار نهانگاهشده را میتوان با حذف ویژگی پاک کرد. این کار باعث میشود متد cached_property دوباره اجرا شود.
cached_property از ایجاد یک وضعیت رقابتی احتمالی در استفاده چندنخی جلوگیری نمیکند. تابع دریافتکننده ممکن است برای همان نمونه بیش از یک بار اجرا شود و آخرین اجرا مقدار نهانشده را تنظیم کند. اگر ویژگی نهانشده همتوان باشد یا اجرای بیش از یکبارهی آن برای یک نمونه به هر دلیل زیانآور نباشد، این موضوع مشکلی ایجاد نمیکند. اگر همگامسازی لازم است، قفلگذاری لازم را درون تابع دریافتکننده تزئینشده یا پیرامون دسترسی به ویژگی نهانشده پیادهسازی کنید.
توجه داشته باشید که این دکوراتور در عملکرد دیکشنریهای کلید-مشترک PEP 412 اختلال ایجاد میکند. این بدان معناست که دیکشنریهای نمونه میتوانند فضای بیشتری نسبت به حالت عادی اشغال کنند.
همچنین، این دکوراتور نیاز دارد که ویژگی
__dict__هر نمونه یک نگاشت تغییرپذیر باشد. این بدان معناست که با برخی از انواع کار نمیکند، مانند فراکلاسها (زیرا ویژگیهای__dict__در نمونههای type، پراکسیهای فقطخواندنی برای فضای نام کلاس هستند) و انواعی که__slots__را بدون گنجاندن__dict__بهعنوان یکی از جایگاههای تعریفشده تعیین میکنند (زیرا چنین کلاسهایی اصلاً ویژگی__dict__را فراهم نمیکنند).اگر نگاشت تغییرپذیر در دسترس نباشد یا اشتراکگذاری کلیدها بهصورت بهینه از نظر فضا مطلوب باشد، میتوان اثری مشابه
@cached_propertyرا نیز با قرار دادن@propertyبر روی@lru_cacheبهدست آورد. برای جزئیات بیشتر دربارهی تفاوت این روش با@cached_property، چگونه فراخوانیهای متد را در نهانگاه ذخیره کنم؟ را ببینید.اضافه شده در نسخهی 3.8.
تغییر یافته در نسخهی 3.12: پیش از پایتون 3.12،
@cached_propertyشامل یک قفل مستندنشده بود تا تضمین کند که در استفادهی چندنخی، تابع getter فقط یکبار بهازای هر نمونه اجرا میشود. با این حال، این قفل بهازای هر پراپرتی بود، نه بهازای هر نمونه، که میتوانست منجر به رقابت قفل بسیار بالا و غیرقابلقبول شود. در پایتون 3.12 و بالاتر این قفلسازی حذف شده است.
- functools.cmp_to_key(func)¶
یک تابع مقایسه به سبک قدیمی را به یک key function تبدیل میکند. با ابزارهایی که توابع کلید را میپذیرند استفاده میشود (مانند
sorted()،min()،max()،heapq.nlargest()،heapq.nsmallest()،itertools.groupby()). این تابع عمدتاً بهعنوان ابزاری برای گذار برنامههایی استفاده میشود که در حال تبدیل از Python 2 (که از استفاده از توابع مقایسه پشتیبانی میکرد) هستند.تابع مقایسه، هر فراخوانیپذیری است که دو آرگومان میپذیرد، آنها را مقایسه میکند و برای کوچکتر بودن یک عدد منفی، برای برابری صفر، یا برای بزرگتر بودن یک عدد مثبت برمیگرداند. تابع کلید، یک فراخوانیپذیر است که یک آرگومان میپذیرد و مقدار دیگری را برمیگرداند تا بهعنوان کلید مرتبسازی استفاده شود.
مثال:
sorted(iterable, key=cmp_to_key(locale.strcoll)) # ترتیب مرتبسازی آگاه از locale
برای مثالهای مرتبسازی و یک آموزش کوتاه مرتبسازی، به روشهای مرتبسازی مراجعه کنید.
اضافه شده در نسخهی 3.2.
- @functools.lru_cache(user_function)¶
- @functools.lru_cache(maxsize=128, typed=False)
دکوراتوری برای دربرگرفتن یک تابع با یک فراخوانیپذیر memoizing که حداکثر maxsize فراخوانی اخیر را ذخیره میکند. این دکوراتور میتواند هنگامی که یک تابع پرهزینه یا وابسته به I/O بهصورت دورهای با آرگومانهای یکسان فراخوانی میشود، باعث صرفهجویی در زمان شود.
نهانگاه ایمن برای نخها است تا بتوان از تابع پوششدادهشده در چندین نخ استفاده کرد. این بدان معناست که ساختار داده زیرین در حین بهروزرسانیهای همزمان منسجم باقی میماند.
ممکن است تابع پوشیدهشده بیش از یک بار فراخوانی شود، اگر نخ دیگری پیش از آنکه فراخوانی اولیه کامل شده و در نهانگاه ذخیره شده باشد، یک فراخوانی اضافی انجام دهد.
از آنجا که از یک دیکشنری برای نهانگاهسازی نتایج استفاده میشود، آرگومانهای جایگاهی و کلیدواژهای تابع باید hashable باشند.
الگوهای متمایز آرگومانها ممکن است بهعنوان فراخوانیهای متمایز با ورودیهای جداگانه در نهانگاه در نظر گرفته شوند. برای مثال،
f(a=1, b=2)وf(b=2, a=1)در ترتیب آرگومانهای کلیدواژهای خود تفاوت دارند و ممکن است دو ورودی جداگانه در نهانگاه داشته باشند.اگر user_function مشخص شده باشد، باید فراخوانیپذیر باشد. این امکان را فراهم میکند که دکوراتور lru_cache مستقیماً روی یک تابع کاربر اعمال شود، در حالی که maxsize روی مقدار پیشفرض خود یعنی ۱۲۸ باقی میماند:
@lru_cache def count_vowels(sentence): return sum(sentence.count(vowel) for vowel in 'AEIOUaeiou')
اگر maxsize برابر
Noneقرار داده شود، قابلیت LRU غیرفعال میشود و نهانگاه میتواند بدون محدودیت رشد کند.اگر typed روی درست تنظیم شود، آرگومانهای تابع از انواع مختلف بهطور جداگانه در نهانگاه ذخیره میشوند. اگر typed نادرست باشد، پیادهسازی معمولاً آنها را بهعنوان فراخوانیهای همارز در نظر میگیرد و فقط یک نتیجه را در نهانگاه ذخیره میکند. (برخی انواع مانند str و int ممکن است حتی وقتی typed نادرست است، بهطور جداگانه در نهانگاه ذخیره شوند.)
توجه داشته باشید که ویژگی خاصبودن نوع (type specificity) فقط به آرگومانهای مستقیم تابع اعمال میشود، نه به محتوای آنها. آرگومانهای اسکالر (scalar)،
Decimal(42)وFraction(42)، بهعنوان فراخوانیهای مجزا با نتایج مجزا در نظر گرفته میشوند. در مقابل، آرگومانهای تاپل،('answer', Decimal(42))و('answer', Fraction(42))، معادل یکدیگر در نظر گرفته میشوند.تابع دربرگرفتهشده به یک تابع
cache_parameters()مجهز شده است که یکdictجدید برمیگرداند و مقادیر maxsize و typed را نشان میدهد. این صرفاً برای اطلاعرسانی است. تغییر مقادیر هیچ تأثیری ندارد.برای کمک به اندازهگیری اثربخشی نهانگاه و تنظیم پارامتر maxsize، تابع دربرگرفتهشده به یک تابع
cache_info()مجهز شده است که یک named tuple برمیگرداند و hits، misses، maxsize و currsize را نشان میدهد.این دکوراتور همچنین یک تابع
cache_clear()برای پاکسازی یا بیاعتبار کردن نهانگاه فراهم میکند.تابع زیربنایی اصلی از طریق ویژگی
__wrapped__در دسترس است. این برای دروننگری، دور زدن نهانگاه، یا پیچیدن دوباره تابع با نهانگاهی متفاوت مفید است.نهانگاه ارجاعها به آرگومانها و مقادیر بازگشتی را نگه میدارد، تا زمانی که به دلیل انقضا از نهانگاه خارج شوند یا نهانگاه پاک شود.
اگر یک متد نهانگاهشده باشد، آرگومان نمونه
selfدر نهانگاه لحاظ میشود. چگونه فراخوانیهای متد را در نهانگاه ذخیره کنم؟ را ببینیدنهانگاه LRU (کمترین استفادهی اخیر) زمانی بهترین عملکرد را دارد که آخرین فراخوانیها بهترین پیشبینیکنندههای فراخوانیهای آینده باشند (برای مثال، محبوبترین مقالهها در یک سرور خبری معمولاً هر روز تغییر میکنند). محدودیت اندازهی نهانگاه تضمین میکند که نهانگاه در فرآیندهای طولانیمدت مانند سرورهای وب بدون محدودیت رشد نمیکند.
بهطور کلی، نهانگاه LRU فقط زمانی باید استفاده شود که بخواهید مقادیر از پیش محاسبهشده را دوباره به کار ببرید. بر همین اساس، منطقی نیست که توابع دارای اثرات جانبی، توابعی که نیاز دارند در هر فراخوانی اشیاء تغییرپذیر متمایزی ایجاد کنند (مانند تولیدگرها و توابع ناهمگام)، یا توابع غیرخالص مانند time() یا random() را نهانسازی کنید.
نمونهای از یک نهانگاه LRU برای محتوای وب ثابت:
@lru_cache(maxsize=32) def get_pep(num): 'Retrieve text of a Python Enhancement Proposal' resource = f'https://peps.python.org/pep-{num:04d}' try: with urllib.request.urlopen(resource) as s: return s.read() except urllib.error.HTTPError: return 'Not Found' >>> for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991: ... pep = get_pep(n) ... print(n, len(pep)) >>> get_pep.cache_info() CacheInfo(hits=3, misses=8, maxsize=32, currsize=8)
مثالی از محاسبهی کارآمد اعداد فیبوناچی با استفاده از یک نهانگاه برای پیادهسازی تکنیک برنامهنویسی پویا:
@lru_cache(maxsize=None) def fib(n): if n < 2: return n return fib(n-1) + fib(n-2) >>> [fib(n) for n in range(16)] [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610] >>> fib.cache_info() CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)
اضافه شده در نسخهی 3.2.
تغییر یافته در نسخهی 3.3: گزینهی typed اضافه شد.
تغییر یافته در نسخهی 3.8: گزینهی user_function افزوده شد.
تغییر یافته در نسخهی 3.9: تابع
cache_parameters()افزوده شد
- @functools.total_ordering¶
با فرض کلاسی که یک یا چند متد مرتبهبندی مقایسه غنی را تعریف میکند، این دکوراتور کلاس، بقیه را فراهم میکند. این کار تلاش لازم برای مشخص کردن تمام عملیات مقایسه غنی ممکن را ساده میکند:
کلاس باید یکی از
__lt__()،__le__()،__gt__()یا__ge__()را تعریف کند. علاوه بر این، بهتر است کلاس یک متد__eq__()نیز فراهم کند.برای مثال:
@total_ordering class Student: def _is_valid_operand(self, other): return (hasattr(other, "lastname") and hasattr(other, "firstname")) def __eq__(self, other): if not self._is_valid_operand(other): return NotImplemented return ((self.lastname.lower(), self.firstname.lower()) == (other.lastname.lower(), other.firstname.lower())) def __lt__(self, other): if not self._is_valid_operand(other): return NotImplemented return ((self.lastname.lower(), self.firstname.lower()) < (other.lastname.lower(), other.firstname.lower()))
توجه
هرچند این دکوراتور ایجاد انواع با ترتیب کامل و رفتار مناسب را آسان میسازد، اما واقعاً با هزینهی اجرای کندتر و ردگیریهای پشتهی پیچیدهتر برای متدهای مقایسهای مشتقشده همراه است. اگر محکزنی عملکرد نشان دهد که این موضوع برای یک برنامهی مشخص گلوگاه است، در عوض پیادهسازی هر شش متد مقایسهی غنی احتمالاً میتواند بهراحتی باعث افزایش سرعت شود.
توجه
این دکوراتور هیچ تلاشی برای بازنویسی متدهایی که در کلاس یا ابرکلاسهای آن تعریفشدهاند، نمیکند. به این معنا که اگر یک ابرکلاس یک عملگر مقایسه را تعریف کند، total_ordering آن را دوباره پیادهسازی نخواهد کرد، حتی اگر متد اصلی انتزاعی باشد.
اضافه شده در نسخهی 3.2.
تغییر یافته در نسخهی 3.4: بازگرداندن
NotImplementedاز تابع مقایسهی زیربنایی برای انواع ناشناخته اکنون پشتیبانی میشود.
- functools.Placeholder¶
شیء یگانهای که بهعنوان نشانگر (sentinel) برای رزرو جایگاهی برای آرگومانهای جایگاهی هنگام فراخوانی
partial()وpartialmethod()استفاده میشود.اضافه شده در نسخهی 3.14.
- functools.partial(func, /, *args, **keywords)¶
یک شیء partial جدید برمیگرداند که هنگام فراخوانی، مانند func فراخوانیشده با آرگومانهای جایگاهی args و آرگومانهای کلیدواژهای keywords رفتار میکند. اگر آرگومانهای بیشتری در فراخوانی ارائه شوند، به args افزوده میشوند. اگر آرگومانهای کلیدواژهای اضافی ارائه شوند، keywords را گسترش میدهند و بازنویسی میکنند. تقریباً معادل است با:
def partial(func, /, *args, **keywords): def newfunc(*more_args, **more_keywords): return func(*args, *more_args, **(keywords | more_keywords)) newfunc.func = func newfunc.args = args newfunc.keywords = keywords return newfunc
تابع
partial()برای اعمال جزئی تابع استفاده میشود که در آن بخشی از آرگومانها و/یا کلیدواژههای یک تابع «فریز» میشود و یک شیء جدید با امضای سادهتر به دست میآید. برای مثال، میتوان ازpartial()برای ایجاد یک شیء فراخوانیپذیر استفاده کرد که مانند تابعint()رفتار میکند، بهطوری که مقدار پیشفرض آرگومان base آن2است:>>> basetwo = partial(int, base=2) >>> basetwo.__doc__ = 'Convert base 2 string to an int.' >>> basetwo('10010') 18
اگر نشانگرهای
Placeholder(sentinels) در args وجود داشته باشند، هنگام فراخوانیpartial()ابتدا آنها پر میشوند. این کار از پیش پر کردن هر آرگومان جایگاهی را با فراخوانیpartial()ممکن میسازد؛ بدونPlaceholder، تنها میتوان تعداد انتخابشدهای از آرگومانهای جایگاهی ابتدایی را از پیش پر کرد.اگر هر نشانگر (sentinel) از
Placeholderوجود داشته باشد، همه باید در زمان فراخوانی پر شوند:>>> say_to_world = partial(print, Placeholder, Placeholder, "world!") >>> say_to_world('Hello', 'dear') Hello dear world!
فراخوانی
say_to_world('Hello')باعث پرتاب یکTypeErrorمیشود، زیرا تنها یک آرگومان جایگاهی ارائه شده است، اما دو جاینگهدار وجود دارد که باید پر شوند.اگر
partial()بر یک شیء partial موجود اعمال شود، نشانگرهایPlaceholderشیء ورودی با آرگومانهای جایگاهی جدید پر میشوند. میتوان یک جاینگهدار را با درج یک نشانگرPlaceholderجدید در جایگاهی که یکPlaceholderپیشین آن را نگهداشته است، حفظ کرد:>>> from functools import partial, Placeholder as _ >>> remove = partial(str.replace, _, _, '') >>> message = 'Hello, dear dear world!' >>> remove(message, ' dear') 'Hello, world!' >>> remove_dear = partial(remove, _, ' dear') >>> remove_dear(message) 'Hello, world!' >>> remove_first_dear = partial(remove_dear, _, 1) >>> remove_first_dear(message) 'Hello, dear world!'
Placeholderنمیتواند بهعنوان آرگومان کلیدواژهای بهpartial()ارسال شود.تغییر یافته در نسخهی 3.14: پشتیبانی از
Placeholderدر آرگومانهای جایگاهی اضافه شد.
- class functools.partialmethod(func, /, *args, **keywords)¶
یک توصیفگر
partialmethodجدید برمیگرداند که مانندpartialرفتار میکند، با این تفاوت که برای استفاده بهعنوان تعریف متد طراحی شده است، نه اینکه مستقیماً فراخوانیپذیر باشد.func باید یک توصیفگر یا فراخوانیپذیر باشد (اشیایی که هر دو هستند، مانند توابع معمولی، بهعنوان توصیفگر در نظر گرفته میشوند).
هنگامی که func یک توصیفگر باشد (مانند یک تابع معمولی پایتون،
classmethod()،staticmethod()،abstractmethod()یا نمونهای دیگر ازpartialmethod)، فراخوانیهای متد__get__به توصیفگر زیرین محول میشوند و یک شیء partial مناسب بهعنوان نتیجه برگردانده میشود.هنگامی که func یک شیء فراخوانیپذیر غیرتوصیفگر باشد، یک متد مقید مناسب بهصورت پویا ایجاد میشود. این متد هنگامی که بهعنوان یک متد استفاده شود، مانند یک تابع معمولی پایتون رفتار میکند: آرگومان self بهعنوان اولین آرگومان جایگاهی درج میشود، حتی پیش از args و keywords ارائهشده به سازندهی
partialmethod.مثال:
>>> class Cell: ... def __init__(self): ... self._alive = False ... @property ... def alive(self): ... return self._alive ... def set_state(self, state): ... self._alive = bool(state) ... set_alive = partialmethod(set_state, True) ... set_dead = partialmethod(set_state, False) ... >>> c = Cell() >>> c.alive False >>> c.set_alive() >>> c.alive True
اضافه شده در نسخهی 3.4.
- functools.reduce(function, iterable, /[, initial])¶
function با ۲ آرگومان را بهصورت تجمیعی روی آیتمهای iterable، از چپ به راست، اعمال میکند تا پیمایشپذیر را به یک مقدار واحد کاهش دهد. برای مثال،
reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])((((1+2)+3)+4)+5)را محاسبه میکند. آرگومان سمت چپ، x، مقدار انباشتهشده است و آرگومان سمت راست، y، مقدار بهروزرسانی از iterable است. اگر initial اختیاری وجود داشته باشد، در محاسبه پیش از آیتمهای پیمایشپذیر قرار میگیرد و بهعنوان مقدار پیشفرض زمانی که پیمایشپذیر خالی است عمل میکند. اگر initial داده نشود و iterable فقط یک آیتم داشته باشد، اولین آیتم برگردانده میشود.تقریباً معادل با:
initial_missing = object() def reduce(function, iterable, /, initial=initial_missing): it = iter(iterable) if initial is initial_missing: value = next(it) else: value = initial for element in it: value = function(value, element) return value
برای پیمایشگری که تمام مقادیر میانی را تولید میکند،
itertools.accumulate()را ببینید.تغییر یافته در نسخهی 3.14: اکنون initial بهعنوان یک آرگومان کلیدواژهای پشتیبانی میشود.
- @functools.singledispatch¶
یک تابع را به یک تابع عام ارسال تکگانه تبدیل میکند.
برای تعریف یک تابع عام، آن را با دکوراتور
@singledispatchبیارایید. هنگام تعریف یک تابع با استفاده از@singledispatch، توجه داشته باشید که اعزام (dispatch) بر اساس نوع اولین آرگومان انجام میشود:>>> from functools import singledispatch >>> @singledispatch ... def fun(arg, verbose=False): ... if verbose: ... print("Let me just say,", end=" ") ... print(arg)
برای افزودن پیادهسازیهای سربارگذاریشده به تابع، از ویژگی
register()تابع عام استفاده کنید، که میتوان از آن بهعنوان دکوراتور استفاده کرد. برای توابعی که با نوعها حاشیهنویسی شدهاند، دکوراتور نوع اولین آرگومان را بهطور خودکار استنتاج میکند:>>> @fun.register ... def _(arg: int, verbose=False): ... if verbose: ... print("Strength in numbers, eh?", end=" ") ... print(arg) ... >>> @fun.register ... def _(arg: list, verbose=False): ... if verbose: ... print("Enumerate this:") ... for i, elem in enumerate(arg): ... print(i, elem)
همچنین میتوان از
typing.Unionاستفاده کرد:>>> @fun.register ... def _(arg: int | float, verbose=False): ... if verbose: ... print("Strength in numbers, eh?", end=" ") ... print(arg) ... >>> from typing import Union >>> @fun.register ... def _(arg: Union[list, set], verbose=False): ... if verbose: ... print("Enumerate this:") ... for i, elem in enumerate(arg): ... print(i, elem) ...
برای کدی که از حاشیهنویسیهای نوع استفاده نمیکند، میتوان آرگومان نوع مناسب را بهصراحت به خود دکوراتور منتقل کرد:
>>> @fun.register(complex) ... def _(arg, verbose=False): ... if verbose: ... print("Better than complicated.", end=" ") ... print(arg.real, arg.imag) ...
برای کدی که بر اساس نوع یک مجموعه اعزام میکند (مثلاً
list)، اما میخواهد راهنمای نوع آیتمهای مجموعه را مشخص کند (مثلاًlist[int])، نوع اعزام باید بهصراحت به خود دکوراتور ارسال شود و راهنمای نوع در تعریف تابع قرار بگیرد:>>> @fun.register(list) ... def _(arg: list[int], verbose=False): ... if verbose: ... print("Enumerate this:") ... for i, elem in enumerate(arg): ... print(i, elem)
توجه
در رانتایم، تابع بر اساس یک نمونه از فهرست اعزام میشود، صرفنظر از نوع موجود در فهرست؛ یعنی
[1,2,3]همانند["foo", "bar", "baz"]اعزام میشود. حاشیهنویسی ارائهشده در این مثال فقط برای بررسیکنندههای نوع ایستا است و هیچ تأثیری در رانتایم ندارد.برای فراهم کردن امکان ثبت لامبداها و توابع از پیش موجود، همچنین میتوان از ویژگی
register()بهشکل تابعی استفاده کرد:>>> def nothing(arg, verbose=False): ... print("Nothing.") ... >>> fun.register(type(None), nothing)
ویژگی
register()تابع بدون دکوراتور را برمیگرداند. این امر امکان چیدن دکوراتورها روی هم،picklingو ایجاد آزمونهای واحد برای هر گونه بهطور مستقل را فراهم میکند:>>> @fun.register(float) ... @fun.register(Decimal) ... def fun_num(arg, verbose=False): ... if verbose: ... print("Half of your number:", end=" ") ... print(arg / 2) ... >>> fun_num is fun False
هنگام فراخوانی، تابع عام بر اساس نوع اولین آرگومان، اعزام میشود:
>>> fun("Hello, world.") Hello, world. >>> fun("test.", verbose=True) Let me just say, test. >>> fun(42, verbose=True) Strength in numbers, eh? 42 >>> fun(['spam', 'spam', 'eggs', 'spam'], verbose=True) Enumerate this: 0 spam 1 spam 2 eggs 3 spam >>> fun(None) Nothing. >>> fun(1.23) 0.615
هرگاه برای یک نوع خاص، پیادهسازی ثبتشدهای وجود نداشته باشد، از ترتیب حل متد آن برای یافتن یک پیادهسازی عامتر استفاده میشود. تابع اصلی که با
@singledispatchآراسته شده است، برای نوع پایهیobjectثبت شده است؛ این یعنی اگر پیادهسازی بهتری یافت نشود، از آن استفاده میشود.اگر یک پیادهسازی برای یک abstract base class ثبت شده باشد، زیرکلاسهای مجازیِ کلاس پایه به آن پیادهسازی ارسال میشوند:
>>> from collections.abc import Mapping >>> @fun.register ... def _(arg: Mapping, verbose=False): ... if verbose: ... print("Keys & Values") ... for key, value in arg.items(): ... print(key, "=>", value) ... >>> fun({"a": "b"}) a => b
برای بررسی اینکه تابع عام کدام پیادهسازی را برای یک نوع مشخص انتخاب میکند، از ویژگی
dispatch()استفاده کنید:>>> fun.dispatch(float) <function fun_num at 0x1035a2840> >>> fun.dispatch(dict) # note: default implementation <function fun at 0x103fe0000>
برای دسترسی به همه پیادهسازیهای ثبتشده، از ویژگی
registryفقطخواندنی استفاده کنید:>>> fun.registry.keys() dict_keys([<class 'NoneType'>, <class 'int'>, <class 'object'>, <class 'decimal.Decimal'>, <class 'list'>, <class 'float'>]) >>> fun.registry[float] <function fun_num at 0x1035a2840> >>> fun.registry[object] <function fun at 0x103fe0000>
اضافه شده در نسخهی 3.4.
تغییر یافته در نسخهی 3.7: ویژگی
register()اکنون از حاشیهنویسیهای نوع (type annotations) پشتیبانی میکند.تغییر یافته در نسخهی 3.11: ویژگی
register()اکنون ازtyping.Unionبهعنوان یک حاشیهنویسی نوع پشتیبانی میکند.
- class functools.singledispatchmethod(func)¶
یک متد را به یک تابع عام ارسال تکگانه (single-dispatch) تبدیل میکند.
برای تعریف یک متد عام، از دکوراتور
@singledispatchmethodاستفاده کنید. هنگام تعریف یک متد با استفاده از@singledispatchmethod، توجه داشته باشید که اعزام بر اساس نوع اولین آرگومان بهجز self یا cls صورت میگیرد:class Negator: @singledispatchmethod def neg(self, arg): raise NotImplementedError("Cannot negate a") @neg.register def _(self, arg: int): return -arg @neg.register def _(self, arg: bool): return not arg
@singledispatchmethodاز تودرتو شدن با سایر دکوراتورها مانند@classmethodپشتیبانی میکند. توجه داشته باشید که برای امکان استفاده ازdispatcher.register،singledispatchmethodباید بیرونیترین دکوراتور باشد. در اینجا کلاسNegatorبا متدهایnegآمده است که بهجای نمونهای از کلاس، به خود کلاس متصل شدهاند:class Negator: @singledispatchmethod @classmethod def neg(cls, arg): raise NotImplementedError("Cannot negate a") @neg.register @classmethod def _(cls, arg: int): return -arg @neg.register @classmethod def _(cls, arg: bool): return not arg
میتوان از همین الگو برای دکوراتورهای مشابه دیگر نیز استفاده کرد:
@staticmethod،@~abc.abstractmethodو دیگر موارد.اضافه شده در نسخهی 3.8.
- functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)¶
یک تابع پوششی را بهروزرسانی کنید تا شبیه تابع پوشیدهشده به نظر برسد. آرگومانهای اختیاری، تاپلهایی هستند که مشخص میکنند کدام ویژگیهای تابع اصلی مستقیماً به ویژگیهای متناظر در تابع پوششی انتساب داده میشوند و کدام ویژگیهای تابع پوششی با ویژگیهای متناظر از تابع اصلی بهروزرسانی میشوند. مقادیر پیشفرض این آرگومانها، ثابتهای سطح ماژول
WRAPPER_ASSIGNMENTS(که به ویژگیهای تابع پوششی شامل__module__،__name__،__qualname__،__annotations__،__type_params__و__doc__، یعنی رشته مستندسازی، مقدار انتساب میدهد) وWRAPPER_UPDATES(که__dict__تابع پوششی، یعنی دیکشنری نمونه، را بهروزرسانی میکند) هستند.برای امکان دسترسی به تابع اصلی جهت دروننگری و سایر اهداف (مثلاً دور زدن یک دکوراتور نهانسازی مانند
@lru_cache)، این تابع بهطور خودکار یک ویژگی__wrapped__را که به تابع پوشیدهشده اشاره میکند، به تابع پوششی اضافه میکند.کاربرد اصلی در نظر گرفتهشده برای این تابع، در توابع دکوراتور، آراینده است که تابع دکورشده را میپوشانند و تابع پوششی را برمیگردانند. اگر تابع پوششی بهروزرسانی نشود، فراداده تابع برگرداندهشده، تعریف تابع پوششی را به جای تعریف تابع اصلی منعکس میکند، که معمولاً چندان مفید نیست.
update_wrapper()میتواند با فراخوانیپذیرهایی غیر از توابع نیز استفاده شود. هر یک از ویژگیهایی که در assigned یا updated نام برده شدهاند و در شیءای که پوشش داده میشود وجود نداشته باشند، نادیده گرفته میشوند (یعنی این تابع تلاش نخواهد کرد آنها را روی تابع پوششی تنظیم کند). اگر خود تابع پوششی فاقد هر یک از ویژگیهایی باشد که در updated نام برده شدهاند، همچنانAttributeErrorپرتاب میشود.تغییر یافته در نسخهی 3.2: ویژگی
__wrapped__اکنون بهصورت خودکار اضافه میشود. ویژگی__annotations__اکنون بهطور پیشفرض کپی میشود. ویژگیهای ناموجود دیگر باعثAttributeErrorنمیشوند.تغییر یافته در نسخهی 3.4: ویژگی
__wrapped__اکنون همیشه به تابع پوششدادهشده ارجاع میدهد، حتی اگر آن تابع ویژگی__wrapped__را تعریف کرده باشد. (ببینید bpo-17482)تغییر یافته در نسخهی 3.12: ویژگی
__type_params__اکنون بهطور پیشفرض کپی میشود.
- @functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)¶
این یک تابع کمکی برای فراخوانی
update_wrapper()بهعنوان دکوراتور تابع هنگام تعریف یک تابع پوششی است. این معادلpartial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)است. برای مثال:>>> from functools import wraps >>> def my_decorator(f): ... @wraps(f) ... def wrapper(*args, **kwds): ... print('Calling decorated function') ... return f(*args, **kwds) ... return wrapper ... >>> @my_decorator ... def example(): ... """Docstring""" ... print('Called example function') ... >>> example() Calling decorated function Called example function >>> example.__name__ 'example' >>> example.__doc__ 'Docstring'
بدون استفاده از این کارخانهی دکوراتور، نام تابع نمونه
'wrapper'میشد و رشتهی مستندسازی تابع اصلیexample()از بین میرفت.
اشیای partial¶
اشیای partial، اشیای فراخوانیپذیری هستند که توسط partial() ایجاد شدهاند. آنها ۳ ویژگی فقطخواندنی دارند:
- partial.func¶
یک شیء یا تابع فراخوانیپذیر. فراخوانیهای شیء
partialبهfuncارجاع داده میشوند، همراه با آرگومانها و کلیدواژههای جدید.
- partial.args¶
چپترین آرگومانهای جایگاهی که به ابتدای آرگومانهای جایگاهی ارائهشده به فراخوانی شیء
partialاضافه میشوند.
اشیاء partial از این جهت شبیه اشیاء تابعی هستند که فراخوانیپذیر و قابل ارجاع ضعیف (weak referenceable) هستند و میتوانند ویژگی داشته باشند. برخی تفاوتهای مهم وجود دارد. برای مثال، ویژگیهای __name__ و __doc__ بهصورت خودکار ایجاد نمیشوند.