با راهنمای جامع برای توسعهدهندگان بینالمللی پایتون، بر `functools.lru_cache`، `functools.singledispatch` و `functools.wraps` مسلط شوید و کارایی و انعطافپذیری کد را افزایش دهید.
گشودن پتانسیل پایتون: دکوراتورهای پیشرفته `functools` برای توسعهدهندگان جهانی
در چشمانداز همواره در حال تحول توسعه نرمافزار، پایتون همچنان یک نیروی غالب است و به دلیل خوانایی و کتابخانههای گستردهاش مورد تجلیل قرار میگیرد. برای توسعهدهندگان در سراسر جهان، تسلط بر ویژگیهای پیشرفته آن برای ساخت برنامههای کارآمد، قوی و قابل نگهداری بسیار مهم است. در میان قدرتمندترین ابزارهای پایتون، دکوراتورهای موجود در ماژول `functools` هستند. این راهنما به بررسی سه دکوراتور ضروری میپردازد: `lru_cache` برای بهینهسازی عملکرد، `singledispatch` برای بارگذاری انعطافپذیر تابع و `wraps` برای حفظ فرادادههای تابع. با درک و اعمال این دکوراتورها، توسعهدهندگان بینالمللی پایتون میتوانند به طور قابل توجهی شیوههای کدنویسی و کیفیت نرمافزار خود را ارتقا دهند.
چرا دکوراتورهای `functools` برای مخاطبان جهانی مهم هستند
ماژول `functools` برای پشتیبانی از توسعه توابع مرتبه بالاتر و اشیاء قابل فراخوانی طراحی شده است. دکوراتورها، یک قند نحوی که در پایتون 3.0 معرفی شد، به ما این امکان را میدهند که توابع و متدها را به روشی تمیز و خوانا تغییر دهیم یا ارتقا دهیم. برای یک مخاطب جهانی، این به چندین مزیت کلیدی ترجمه میشود:
- جهانی بودن: نحو پایتون و کتابخانههای اصلی استاندارد شدهاند و مفاهیمی مانند دکوراتورها را صرف نظر از موقعیت جغرافیایی یا پیشینه برنامهنویسی، به طور جهانی قابل درک میسازند.
- کارایی: `lru_cache` میتواند به طور چشمگیری عملکرد توابع پرهزینه محاسباتی را بهبود بخشد، که یک عامل حیاتی هنگام برخورد با تاخیرهای شبکه بالقوه متغیر یا محدودیتهای منابع در مناطق مختلف است.
- انعطافپذیری: `singledispatch` کدی را فعال میکند که میتواند با انواع دادههای مختلف سازگار شود و یک کد پایه عمومیتر و سازگارتر را ترویج کند که برای برنامههایی که به پایگاههای کاربری متنوع با فرمتهای دادهای متفاوت خدمات میدهند، ضروری است.
- قابلیت نگهداری: `wraps` تضمین میکند که دکوراتورها هویت اصلی تابع را مبهم نمیکنند و به اشکالزدایی و دروننگری کمک میکنند، که برای تیمهای توسعه بینالمللی مشترک حیاتی است.
بیایید هر یک از این دکوراتورها را با جزئیات بررسی کنیم.
1. `functools.lru_cache`: یادآوری برای بهینهسازی عملکرد
یکی از رایجترین گلوگاههای عملکرد در برنامهنویسی، ناشی از محاسبات زائد است. هنگامی که یک تابع چندین بار با آرگومانهای یکسان فراخوانی میشود و اجرای آن پرهزینه است، محاسبه مجدد نتیجه هر بار اتلاف است. اینجاست که یادآوری، تکنیک ذخیره نتایج فراخوانیهای تابع پرهزینه و بازگرداندن نتیجه ذخیره شده در صورت وقوع مجدد ورودیهای یکسان، ارزشمند میشود. دکوراتور `functools.lru_cache` پایتون یک راه حل ظریف برای این موضوع ارائه میدهد.
`lru_cache` چیست؟
`lru_cache` مخفف Least Recently Used cache (کش کمترین استفاده اخیر) است. این یک دکوراتور است که یک تابع را میپوشاند و نتایج آن را در یک دیکشنری ذخیره میکند. هنگامی که تابع دکوراتور شده فراخوانی میشود، `lru_cache` ابتدا بررسی میکند که آیا نتیجه برای آرگومانهای داده شده از قبل در کش وجود دارد یا خیر. اگر وجود داشته باشد، نتیجه ذخیره شده بلافاصله برگردانده میشود. اگر نه، تابع اجرا میشود، نتیجه آن در کش ذخیره میشود و سپس برگردانده میشود. جنبه «کمترین استفاده اخیر» به این معنی است که اگر کش به حداکثر اندازه خود برسد، کمترین مورد استفاده اخیر برای ایجاد فضا برای ورودیهای جدید حذف میشود.
کاربرد و پارامترهای اساسی
برای استفاده از `lru_cache`، کافی است آن را وارد کرده و به عنوان یک دکوراتور روی تابع خود اعمال کنید:
from functools import lru_cache
@lru_cache(maxsize=128)
def expensive_computation(x, y):
"""A function that simulates an expensive computation."""
print(f"Performing expensive computation for {x}, {y}...")
# Simulate some heavy work, e.g., network request, complex math
return x * y + x / 2
پارامتر `maxsize` حداکثر تعداد نتایج برای ذخیره را کنترل میکند. اگر `maxsize` روی `None` تنظیم شود، کش میتواند به طور نامحدود رشد کند. اگر روی یک عدد صحیح مثبت تنظیم شود، اندازه کش را مشخص میکند. هنگامی که کش پر است، کمترین ورودیهای استفاده شده اخیر را حذف میکند. مقدار پیش فرض برای `maxsize` 128 است.
ملاحظات کلیدی و کاربرد پیشرفته
- آرگومانهای Hashable: آرگومانهای ارسال شده به یک تابع کش شده باید hashable باشند. این به معنی انواع تغییرناپذیر مانند اعداد، رشتهها، تاپلها (شامل فقط موارد hashable) و frozensets قابل قبول هستند. انواع قابل تغییر مانند لیستها، دیکشنریها و مجموعهها نیستند.
- پارامتر `typed=True`: به طور پیشفرض، `lru_cache` آرگومانهای انواع مختلفی را که برابر مقایسه میشوند، یکسان در نظر میگیرد. به عنوان مثال، `cached_func(3)` و `cached_func(3.0)` ممکن است به یک ورودی کش دسترسی پیدا کنند. تنظیم `typed=True` باعث میشود که کش به انواع آرگومانها حساس شود. بنابراین، `cached_func(3)` و `cached_func(3.0)` به طور جداگانه کش میشوند. این میتواند زمانی مفید باشد که منطق خاص نوعی در تابع وجود داشته باشد.
- بیاعتبارسازی کش: `lru_cache` روشهایی برای مدیریت کش ارائه میدهد. `cache_info()` یک تاپل نامگذاری شده با آمار مربوط به hitهای کش، missها، اندازه فعلی و حداکثر اندازه را برمیگرداند. `cache_clear()` کل کش را پاک میکند.
@lru_cache(maxsize=32)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10))
print(fibonacci.cache_info())
fibonacci.cache_clear()
print(fibonacci.cache_info())
کاربرد جهانی `lru_cache`
سناریویی را در نظر بگیرید که یک برنامه نرخ ارزهای بلادرنگ را ارائه میدهد. واکشی این نرخها از یک API خارجی میتواند کند باشد و منابع را مصرف کند. `lru_cache` را میتوان روی تابعی که این نرخها را واکشی میکند اعمال کرد:
import requests
from functools import lru_cache
@lru_cache(maxsize=10)
def get_exchange_rate(base_currency, target_currency):
"""Fetches the latest exchange rate from an external API."""
# In a real-world app, handle API keys, error handling, etc.
api_url = f"https://api.example.com/rates?base={base_currency}&target={target_currency}"
try:
response = requests.get(api_url, timeout=5) # Set a timeout
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
data = response.json()
return data['rate']
except requests.exceptions.RequestException as e:
print(f"Error fetching exchange rate: {e}")
return None
# User in Europe requests EUR to USD rate
europe_user_rate = get_exchange_rate('EUR', 'USD')
print(f"EUR to USD: {europe_user_rate}")
# User in Asia requests EUR to USD rate
asian_user_rate = get_exchange_rate('EUR', 'USD') # This will hit the cache if within maxsize
print(f"EUR to USD (cached): {asian_user_rate}")
# User in Americas requests USD to EUR rate
americas_user_rate = get_exchange_rate('USD', 'EUR')
print(f"USD to EUR: {americas_user_rate}")
در این مثال، اگر چندین کاربر یک جفت ارز مشابه را در یک دوره کوتاه درخواست کنند، فراخوانی API پرهزینه فقط یک بار انجام میشود. این امر به ویژه برای خدماتی با پایگاه کاربری جهانی که به دادههای مشابه دسترسی دارند، مفید است و بار سرور را کاهش میدهد و زمان پاسخگویی را برای همه کاربران بهبود میبخشد.
2. `functools.singledispatch`: توابع عمومی و چندریختی
در بسیاری از پارادایمهای برنامهنویسی، چندریختی به اشیاء از انواع مختلف اجازه میدهد تا به عنوان اشیاء یک ابرکلاس مشترک رفتار شوند. در پایتون، این اغلب از طریق duck typing به دست میآید. با این حال، برای موقعیتهایی که نیاز دارید رفتار را بر اساس نوع خاص یک آرگومان تعریف کنید، `singledispatch` یک مکانیسم قدرتمند برای ایجاد توابع عمومی با dispatch مبتنی بر نوع ارائه میدهد. این به شما امکان میدهد یک پیادهسازی پیشفرض برای یک تابع تعریف کنید و سپس پیادهسازیهای خاص را برای انواع آرگومانهای مختلف ثبت کنید.
`singledispatch` چیست؟
`singledispatch` یک دکوراتور تابع است که توابع عمومی را فعال میکند. یک تابع عمومی تابعی است که بر اساس نوع اولین آرگومان خود رفتار متفاوتی دارد. شما یک تابع پایه را با `@singledispatch` دکوراتور میکنید و سپس از دکوراتور `@base_function.register(Type)` برای ثبت پیادهسازیهای تخصصی برای انواع مختلف استفاده میکنید.
کاربرد اساسی
بیایید با یک مثال از قالببندی دادهها برای فرمتهای خروجی مختلف نشان دهیم:
from functools import singledispatch
@singledispatch
def format_data(data):
"""Default implementation: formats data as a string."""
return str(data)
@format_data.register(int)
def _(data):
"""Formats integers with commas for thousands separation."""
return "{:,.0f}".format(data)
@format_data.register(float)
def _(data):
"""Formats floats with two decimal places."""
return "{:.2f}".format(data)
@format_data.register(list)
def _(data):
"""Formats lists by joining elements with a pipe '|'."""
return " | ".join(map(str, data))
به استفاده از `_` به عنوان نام تابع برای پیادهسازیهای ثبتشده توجه کنید. این یک قرارداد رایج است زیرا نام تابع ثبتشده مهم نیست. فقط نوع آن برای dispatch مهم است. Dispatch بر اساس نوع اولین آرگومان ارسال شده به تابع عمومی انجام میشود.
نحوه عملکرد Dispatch
هنگامی که `format_data(some_value)` فراخوانی میشود:
- پایتون نوع `some_value` را بررسی میکند.
- اگر یک ثبت برای آن نوع خاص وجود داشته باشد (به عنوان مثال، `int`، `float`، `list`)، تابع ثبت شده مربوطه فراخوانی میشود.
- اگر هیچ ثبت خاصی یافت نشد، تابع اصلی دکوراتور شده با `@singledispatch` (پیادهسازی پیشفرض) فراخوانی میشود.
- `singledispatch` همچنین وراثت را مدیریت میکند. اگر نوع `Subclass` از `BaseClass` ارث ببرد و `format_data` یک ثبت برای `BaseClass` داشته باشد، فراخوانی `format_data` با یک نمونه از `Subclass` از پیادهسازی `BaseClass` استفاده میکند اگر هیچ ثبت خاص `Subclass` وجود نداشته باشد.
کاربرد جهانی `singledispatch`
یک سرویس پردازش داده بینالمللی را تصور کنید. کاربران ممکن است دادهها را در فرمتهای مختلف (به عنوان مثال، مقادیر عددی، مختصات جغرافیایی، مهرهای زمانی، لیست موارد) ارسال کنند. تابعی که این دادهها را پردازش و استاندارد میکند، میتواند از `singledispatch` بسیار سود ببرد.
from functools import singledispatch
from datetime import datetime
@singledispatch
def process_input(value):
"""Default processing: log unknown types."""
print(f"Logging unknown input type: {type(value).__name__} - {value}")
return None
@process_input.register(str)
def _(value):
"""Processes strings, assuming they might be dates or simple text."""
try:
# Attempt to parse as ISO format date
return datetime.fromisoformat(value.replace('Z', '+00:00'))
except ValueError:
# If not a date, return as is (or perform other text processing)
return value.strip()
@process_input.register(int)
def _(value):
"""Processes integers, assuming they are valid product IDs."""
if value < 100000: # Arbitrary validation for example
print(f"Warning: Potentially invalid product ID: {value}")
return f"PID-{value:06d}" # Formats as PID-000001
@process_input.register(tuple)
def _(value):
"""Processes tuples, assuming they are geographical coordinates (lat, lon)."""
if len(value) == 2 and all(isinstance(coord, (int, float)) for coord in value):
return {'latitude': value[0], 'longitude': value[1]}
else:
print(f"Warning: Invalid coordinate tuple format: {value}")
return None
# --- Example Usage for a global audience ---
# User in Japan submits a timestamp string
input1 = "2023-10-27T10:00:00Z"
processed1 = process_input(input1)
print(f"Input: {input1}, Processed: {processed1}")
# User in the US submits a product ID
input2 = 12345
processed2 = process_input(input2)
print(f"Input: {input2}, Processed: {processed2}")
# User in Brazil submits geographical coordinates
input3 = ( -23.5505, -46.6333 )
processed3 = process_input(input3)
print(f"Input: {input3}, Processed: {processed3}")
# User in Australia submits a simple text string
input4 = "Sydney Office"
processed4 = process_input(input4)
print(f"Input: {input4}, Processed: {processed4}")
# Some other type
input5 = [1, 2, 3]
processed5 = process_input(input5)
print(f"Input: {input5}, Processed: {processed5}")
`singledispatch` به توسعهدهندگان اجازه میدهد تا کتابخانهها یا توابعی ایجاد کنند که بتوانند به طور ظریف انواع ورودیهای مختلف را بدون نیاز به بررسیهای نوع صریح (`if isinstance(...)`) در بدنه تابع مدیریت کنند. این منجر به کد تمیزتر و قابل گسترشتر میشود که برای پروژههای بینالمللی که در آن فرمتهای داده ممکن است بسیار متفاوت باشند، بسیار مفید است.
3. `functools.wraps`: حفظ فرادادههای تابع
دکوراتورها یک ابزار قدرتمند برای افزودن قابلیت به توابع موجود بدون تغییر کد اصلی آنها هستند. با این حال، یکی از عوارض جانبی اعمال یک دکوراتور این است که فرادادههای تابع اصلی (مانند نام، رشته مستندات و حاشیهنویسیها) با فرادادههای تابع پوششی دکوراتور جایگزین میشود. این میتواند برای ابزارهای دروننگری، اشکالزداها و تولیدکنندگان مستندات مشکل ایجاد کند. `functools.wraps` دکوراتوری است که این مشکل را حل میکند.
`wraps` چیست؟
`wraps` یک دکوراتور است که شما آن را به تابع پوششی داخل دکوراتور سفارشی خود اعمال میکنید. فرادادههای تابع اصلی را در تابع پوششی کپی میکند. این بدان معنی است که پس از اعمال دکوراتور خود، تابع دکوراتور شده در دنیای بیرون به گونهای ظاهر میشود که انگار تابع اصلی بوده است و نام، رشته مستندات و سایر ویژگیهای آن را حفظ میکند.
کاربرد اساسی
بیایید یک دکوراتور ثبت گزارش ساده ایجاد کنیم و اثر آن را با و بدون `wraps` ببینیم.
بدون `wraps`
def simple_logging_decorator(func):
def wrapper(*args, **kwargs):
print(f"Calling function: {func.__name__}")
result = func(*args, **kwargs)
print(f"Finished function: {func.__name__}")
return result
return wrapper
@simple_logging_decorator
def greet(name):
"""Greets a person."""
return f"Hello, {name}!"
print(f"Function name: {greet.__name__}")
print(f"Function docstring: {greet.__doc__}")
print(greet("World"))
اگر این را اجرا کنید، متوجه خواهید شد که `greet.__name__` 'wrapper' و `greet.__doc__` `None` است، زیرا فرادادههای تابع `wrapper` فرادادههای `greet` را جایگزین کرده است.
با `wraps`
اکنون، بیایید `wraps` را به تابع `wrapper` اعمال کنیم:
from functools import wraps
def robust_logging_decorator(func):
@wraps(func) # Apply wraps to the wrapper function
def wrapper(*args, **kwargs):
print(f"Calling function: {func.__name__}")
result = func(*args, **kwargs)
print(f"Finished function: {func.__name__}")
return result
return wrapper
@robust_logging_decorator
def greet_properly(name):
"""Greets a person (properly decorated)."""
return f"Hello, {name}!"
print(f"Function name: {greet_properly.__name__}")
print(f"Function docstring: {greet_properly.__doc__}")
print(greet_properly("World Again"))
اجرای این مثال دوم نشان میدهد:
Function name: greet_properly
Function docstring: Greets a person (properly decorated).
Calling function: greet_properly
Finished function: greet_properly
Hello, World Again!
`__name__` به درستی روی 'greet_properly' تنظیم شده است و رشته `__doc__` حفظ شده است. `wraps` همچنین سایر ویژگیهای مربوطه مانند `__module__`، `__qualname__` و `__annotations__` را کپی میکند.
کاربرد جهانی `wraps`
در محیطهای توسعه بینالمللی مشترک، کد واضح و قابل دسترس از اهمیت بالایی برخوردار است. هنگامی که اعضای تیم در مناطق زمانی مختلف هستند یا سطوح مختلفی از آشنایی با کد پایه دارند، اشکالزدایی میتواند چالش برانگیزتر باشد. حفظ فرادادههای تابع با `wraps` به حفظ وضوح کد کمک میکند و تلاشهای اشکالزدایی و مستندسازی را تسهیل میکند.
به عنوان مثال، دکوراتوری را در نظر بگیرید که قبل از اجرای یک handler endpoint وب API، بررسیهای احراز هویت را اضافه میکند. بدون `wraps`، نام و رشته مستندات endpoint ممکن است از دست برود و درک اینکه endpoint چه کاری انجام میدهد یا اشکالزدایی مشکلات را برای سایر توسعهدهندگان (یا ابزارهای خودکار) دشوارتر میکند. استفاده از `wraps` تضمین میکند که هویت endpoint روشن باقی میماند.
from functools import wraps
def require_admin_role(func):
@wraps(func)
def wrapper(*args, **kwargs):
# In a real app, this would check user roles from session/token
is_admin = kwargs.get('user_role') == 'admin'
if not is_admin:
raise PermissionError("Admin role required")
return func(*args, **kwargs)
return wrapper
@require_admin_role
def delete_user(user_id, user_role=None):
"""Deletes a user from the system. Requires admin privileges."""
print(f"Deleting user {user_id}...")
# Actual deletion logic here
return True
# --- Example Usage ---
# Simulating a request from an admin user
try:
delete_user(101, user_role='admin')
except PermissionError as e:
print(e)
# Simulating a request from a regular user
try:
delete_user(102, user_role='user')
except PermissionError as e:
print(e)
# Inspecting the decorated function
print(f"Function name: {delete_user.__name__}")
print(f"Function docstring: {delete_user.__doc__}")
# Note: __annotations__ would also be preserved if present on the original function.
`wraps` یک ابزار ضروری برای هر کسی است که در حال ساخت دکوراتورهای قابل استفاده مجدد یا طراحی کتابخانههایی است که برای استفاده گستردهتر در نظر گرفته شدهاند. این تضمین میکند که توابع پیشرفته تا حد امکان به طور قابل پیشبینی در مورد فرادادههای خود رفتار میکنند که برای قابلیت نگهداری و همکاری در پروژههای نرمافزاری جهانی بسیار مهم است.
ترکیب دکوراتورها: یک همافزایی قدرتمند
قدرت واقعی دکوراتورهای `functools` اغلب زمانی آشکار میشود که در ترکیب با یکدیگر استفاده شوند. بیایید سناریویی را در نظر بگیریم که میخواهیم یک تابع را با استفاده از `lru_cache` بهینهسازی کنیم، آن را با `singledispatch` به صورت چندریختی رفتار کنیم و اطمینان حاصل کنیم که فرادادهها با `wraps` حفظ میشوند.
در حالی که `singledispatch` نیاز دارد که تابع دکوراتور شده پایه برای dispatch باشد و `lru_cache` اجرای هر تابعی را بهینه میکند، آنها میتوانند با هم کار کنند. با این حال، `wraps` معمولاً در داخل یک دکوراتور سفارشی برای حفظ فرادادهها اعمال میشود. `lru_cache` و `singledispatch` معمولاً مستقیماً روی توابع اعمال میشوند یا روی تابع پایه در مورد `singledispatch`.
یک ترکیب رایجتر استفاده از `lru_cache` و `wraps` در داخل یک دکوراتور سفارشی است:
from functools import lru_cache, wraps
def cached_and_logged(maxsize=128):
def decorator(func):
@wraps(func)
@lru_cache(maxsize=maxsize)
def wrapper(*args, **kwargs):
# Note: Logging inside lru_cache might be tricky
# as it only runs on cache misses. For consistent logging,
# it's often better to log outside the cached part or rely on cache_info.
print(f"(Cache miss/run) Executing: {func.__name__} with args {args}, kwargs {kwargs}")
return func(*args, **kwargs)
return wrapper
return decorator
@cached_and_logged(maxsize=4)
def complex_calculation(a, b):
"""Performs a simulated complex calculation."""
print(f" - Performing calculation for {a}+{b}...")
return a + b * 2
print(f"Call 1: {complex_calculation(1, 2)}") # Cache miss
print(f"Call 2: {complex_calculation(1, 2)}") # Cache hit
print(f"Call 3: {complex_calculation(3, 4)}") # Cache miss
print(f"Call 4: {complex_calculation(1, 2)}") # Cache hit
print(f"Call 5: {complex_calculation(5, 6)}") # Cache miss, may evict (1,2) or (3,4)
print(f"Function name: {complex_calculation.__name__}")
print(f"Function docstring: {complex_calculation.__doc__}")
print(f"Cache info: {complex_calculation.cache_info()}")
در این دکوراتور ترکیبی، `@wraps(func)` تضمین میکند که فرادادههای `complex_calculation` حفظ میشوند. دکوراتور `@lru_cache` محاسبه واقعی را بهینه میکند و دستور چاپ در داخل `wrapper` فقط زمانی اجرا میشود که کش miss شود و بینشی در مورد زمان فراخوانی واقعی تابع زیرین ارائه میدهد. پارامتر `maxsize` را میتوان از طریق تابع کارخانهای `cached_and_logged` سفارشی کرد.
نتیجهگیری: توانمندسازی توسعه پایتون جهانی
ماژول `functools`، با دکوراتورهایی مانند `lru_cache`، `singledispatch` و `wraps`، ابزارهای پیچیدهای را برای توسعهدهندگان پایتون در سراسر جهان ارائه میدهد. این دکوراتورها به چالشهای رایج در توسعه نرمافزار میپردازند، از بهینهسازی عملکرد و مدیریت انواع دادههای متنوع گرفته تا حفظ یکپارچگی کد و بهرهوری توسعهدهنده.
- `lru_cache` به شما این امکان را میدهد که با کش کردن هوشمندانه نتایج تابع، سرعت برنامهها را افزایش دهید، که برای خدمات جهانی حساس به عملکرد بسیار مهم است.
- `singledispatch` ایجاد توابع عمومی انعطافپذیر و قابل گسترش را ممکن میسازد و کد را با طیف گستردهای از فرمتهای دادهای که در زمینههای بینالمللی با آنها مواجه میشوید، سازگار میکند.
- `wraps` برای ساخت دکوراتورهای خوش رفتار ضروری است و اطمینان حاصل میکند که توابع پیشرفته شما شفاف و قابل نگهداری باقی میمانند، که برای تیمهای توسعه توزیعشده مشترک و جهانی حیاتی است.
با ادغام این ویژگیهای پیشرفته `functools` در گردش کار توسعه پایتون خود، میتوانید نرمافزارهای کارآمدتر، قویتر و قابل فهمتری بسازید. از آنجایی که پایتون همچنان یک زبان انتخابی برای توسعهدهندگان بینالمللی است، درک عمیق این دکوراتورهای قدرتمند بدون شک به شما یک مزیت رقابتی میدهد.
این ابزارها را در آغوش بگیرید، با آنها در پروژههای خود آزمایش کنید و سطوح جدیدی از ظرافت و عملکرد پایتونیک را برای برنامههای جهانی خود باز کنید.