Python-இல் அலங்கரிப்பாளர்கள் (decorators): அவை என்ன, எப்படி வேலை செய்கின்றன, சொந்தமாக எப்படி எழுதுவது
Python decorator என்பது மற்றொரு செயல்பாட்டைப் பெற்று புதிய செயல்பாட்டைத் (பொதுவாக ஒரு உறை/wrapper) திருப்பித் தரும் செயல்பாடு; அது அழைப்புக்கு முன் அல்லது பின் நடத்தையைச் சேர்க்கும்: பதிவு செய்தல், நேரம் அளத்தல், தற்காலிக சேமிப்பு, அணுகல் சரிபார்ப்பு. def-க்கு மேல் @decorator எழுதுவது func = decorator(func) என்பதன் சுருக்கம்; உறை அசல் செயல்பாட்டின் பெயரையும் docstring-ஐயும் வைத்துக்கொள்ள functools.wraps பயன்படுகிறது.
Python decorator என்பது ஒரு செயல்பாட்டைப் பெற்று, வேறொரு செயல்பாட்டைத் திருப்பித் தரும் செயல்பாடு — பொதுவாக அசல் செயல்பாட்டைச் சுற்றியுள்ள உறை. இதன் மூலம் செயல்பாட்டின் குறியீட்டை மாற்றாமல் அதற்கு நடத்தையைச் சேர்க்கலாம் — பதிவு செய்தல், நேரம் அளத்தல், தற்காலிக சேமிப்பு, மீண்டும் முயற்சித்தல். Python decorators def-க்கு மேல் @பெயர் என்று எழுதப்படுகின்றன; Flask, FastAPI, pytest மற்றும் நிலையான நூலகத்தில் எங்கும் காணலாம்.
எளிய சொற்களில்: decorator என்றால் என்ன
ஒரு பரிசையும் அதன் பொதி காகிதத்தையும் கற்பனை செய்யுங்கள். பரிசு (செயல்பாடு) அதேதான், ஆனால் வெளியே முதலில் இயங்கும் ஒரு அடுக்கு சேருகிறது. Decorator அத்தகைய பொதி: அழைக்கும்போது முதலில் உறையின் குறியீடு இயங்கும், அது அசல் செயல்பாட்டை அழைக்கும், முடிவோடு ஏதாவது செய்யவும் முடியும்.
இது வேலை செய்வதற்குக் காரணம்: Python-இல் செயல்பாடுகள் சாதாரணப் பொருட்கள். அவற்றை மற்ற செயல்பாடுகளுக்கு அனுப்பலாம், செயல்பாடுகளிலிருந்து திருப்பித் தரலாம், செயல்பாடுகளுக்குள் அறிவிக்கலாம். இந்த மூன்று பண்புகளின் மேல்தான் decorators கட்டப்பட்டுள்ளன.
Decorator எப்படி வேலை செய்கிறது: @ ஒரு தொடரியல் சுருக்கம்
ஒவ்வொரு அழைப்புக்கும் முன்னும் பின்னும் செய்தி அச்சிடும் எளிய decorator-ஐ எடுத்துக்கொள்வோம்:
def log_calls(func):
def wrapper():
print(f"அழைக்கிறேன் {func.__name__}")
result = func()
print(f"{func.__name__} முடிந்தது")
return result
return wrapper
@log_calls
def hello():
print("வணக்கம்!")
hello()
அழைக்கிறேன் hello
வணக்கம்!
hello முடிந்தது
def-க்கு மேல் உள்ள @log_calls அறிவிப்புக்குப் பின் வரும் இந்த வரிக்குச் சமமானது:
def hello():
print("வணக்கம்!")
hello = log_calls(hello)
இப்போது hello பெயர் wrapper-ஐச் சுட்டுகிறது; அசல் செயல்பாடு உறைக்குள், மூடலில் வாழ்கிறது — wrapper வெளிப்புறச் செயல்பாட்டின் func மாறியை நினைவில் வைத்திருக்கிறது. முக்கியமான விவரம்: decorator ஒரே ஒருமுறை, def இயங்கும் நேரத்தில், அதாவது தொகுதி இறக்குமதியின்போது பயன்படுகிறது; உறையோ ஒவ்வொரு அழைப்பிலும் இயங்குகிறது.
மூடல்களும் decorators-உம்
மூடல் (closure) என்பது வெளிப்புறச் செயல்பாடு முடிந்த பின்னும் அதன் மாறிகளை நினைவில் வைத்திருக்கும் உள்ளமை செயல்பாடு. மூடல்கள் இல்லாமல் decorators இருக்காது: அப்படித்தான் wrapper func-ஐ அணுகுகிறது. மூடலில் நிலையையும் வைக்கலாம், உதாரணமாக அழைப்புகளின் எண்ணிக்கை:
def count_calls(func):
calls = 0
def wrapper(*args, **kwargs):
nonlocal calls
calls += 1
print(f"{func.__name__}: அழைப்பு எண் {calls}")
return func(*args, **kwargs)
return wrapper
Python-இல் decorator எழுதுவது: பொதுவான வார்ப்புரு
முதல் எடுத்துக்காட்டில் இரண்டு குறைகள் உள்ளன: உறை வாதங்களை ஏற்காது, அசல் செயல்பாட்டின் பெயரையும் "விழுங்குகிறது". எந்த decorator-ஐயும் இந்த வேலை செய்யும் வார்ப்புருவிலிருந்து தொடங்குங்கள்:
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# அழைப்புக்கு முன் உள்ள குறியீடு
result = func(*args, **kwargs)
# அழைப்புக்குப் பின் உள்ள குறியீடு
return result
return wrapper
வார்ப்புருவின் மூன்று விதிகள்:
*args, **kwargs— உறை எந்த வாதங்களையும் ஏற்று மாற்றாமல் அனுப்புகிறது.return result— முடிவைத் திருப்பித் தர மறக்காதீர்கள்; இல்லையென்றால் அலங்கரிக்கப்பட்ட செயல்பாடு எப்போதும்Noneதரும்.@functools.wraps(func)— அசல் செயல்பாட்டின் மெட்டாடேட்டாவை உறைக்குக் கொடுக்கிறது.
நேரம் அளக்கும் decorator-க்கு ஓர் எடுத்துக்காட்டு:
import functools
import time
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} எடுத்தது {elapsed:.3f} வி")
return result
return wrapper
@timer
def slow_sum(n):
return sum(range(n))
slow_sum(10_000_000) # slow_sum எடுத்தது 0.142 வி
functools.wraps ஏன் தேவை
wraps இல்லாவிட்டால் அலங்கரிக்கப்பட்ட செயல்பாட்டின் பெயரும் docstring-உம் மற்ற பண்புக்கூறுகளும் மாறிவிடும் — வெளியே wrapper தெரியும்:
def plain(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@plain
def add(a, b):
"""இரண்டு எண்களைக் கூட்டுகிறது."""
return a + b
print(add.__name__, add.__doc__) # wrapper None
@functools.wraps(func) உடன் உறைக்கு அசல் செயல்பாட்டின் __name__, __doc__, __module__, __qualname__, __annotations__ கிடைக்கும்; __wrapped__ பண்புக்கூறில் அசலுக்கான குறிப்பும் இருக்கும். இது பிழைத்திருத்தத்துக்கும் traceback-களுக்கும், help()-க்கும், பெயரால் செயல்பாடுகளைப் பதிவு செய்யும் கட்டமைப்புகளுக்கும், உண்மையான கையொப்பத்தைக் காட்டும் inspect.signature-க்கும் முக்கியம்.
அளவுருக்களுடன் decorator (வாதங்களுடன்)
சில நேரங்களில் decorator-க்கு அமைப்புகள் தேவை: @retry(times=3), @app.route("/"). அதற்கு ஒரு கூடுதல் உள்ளமை அடுக்கு சேர்க்கப்படுகிறது. வெளிப்புறச் செயல்பாடு அளவுருக்களை ஏற்று உண்மையான decorator-ஐத் திருப்பும் — அத்தகைய அமைப்பு decorator தொழிற்சாலை எனப்படும்.
import functools
def repeat(times):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
result = None
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(times=3)
def ping():
print("ping")
ping() # ping, ping, ping — மூன்று வரிகள்
அழைப்பின் வரிசை: repeat(times=3) decorator-ஐத் திருப்பும்; அது ping-க்குப் பொருந்தி wrapper-ஐத் திருப்பும். வேறு வார்த்தைகளில், @repeat(times=3) என்பது ping = repeat(times=3)(ping).
நடைமுறை எடுத்துக்காட்டு — பிழையில் மீண்டும் முயற்சித்தல்:
import functools
import time
def retry(times=3, delay=1.0, exceptions=(Exception,)):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(1, times + 1):
try:
return func(*args, **kwargs)
except exceptions:
if attempt == times:
raise
time.sleep(delay)
return wrapper
return decorator
@retry(times=5, delay=0.5, exceptions=(ConnectionError,))
def fetch_rate():
...
ஒரே செயல்பாட்டில் பல decorators
Decorators ஒன்றுக்கு மேல் ஒன்றாக வைக்கப்படலாம். அவை கீழிருந்து மேலாகப் பொருந்தும்: def-க்கு அருகில் உள்ளது முதலில்.
@timer
@log_calls
def report():
...
# அதே பொருள்:
report = timer(log_calls(report))
அழைக்கும்போது வெளிப்புற அடுக்கு (timer) முதலில் இயங்குகிறது; அதனால் அது log_calls-இன் நேரத்தையும் அளக்கும். வரிசை முக்கியம்: உதாரணமாக ஒரு decorator அணுகல் உரிமையைச் சரிபார்த்து, மற்றொன்று முடிவைத் தற்காலிகமாகச் சேமித்தால்.
Python-இன் நிலையான மற்றும் உள்ளமைவு decorators
பயனுள்ள பல decorators ஏற்கனவே மொழியிலும் நிலையான நூலகத்திலும் உள்ளன:
| Decorator | என்ன செய்கிறது |
|---|---|
@property | முறையை படிக்க மட்டும் பண்புக்கூறாக மாற்றுகிறது; @x.setter உடன் கட்டுப்படுத்தப்பட்ட பண்புக்கூறாக |
@staticmethod | self இல்லாத வகுப்பு முறை; வகுப்பின் பெயர்வெளியில் சாதாரண செயல்பாடு |
@classmethod | முறை நிகழ்வுக்கு பதிலாக வகுப்பு cls-ஐப் பெறுகிறது — மாற்று constructor-களுக்கு இதுவே பயன்படும் |
@functools.cache, @functools.lru_cache | வாதங்களின்படி முடிவுகளைத் தற்காலிகமாகச் சேமிக்கின்றன |
@functools.wraps | செயல்பாட்டின் மெட்டாடேட்டாவை உறைக்கு நகலெடுக்கிறது |
@dataclasses.dataclass | வகுப்புக்கு __init__, __repr__, __eq__ உருவாக்குகிறது |
@contextlib.contextmanager | ஒரு generator-ஐ with-க்கான context manager ஆக்குகிறது |
தற்காலிக சேமிப்பு: functools.cache மற்றும் lru_cache
தற்காலிக சேமிக்கும் decorator ஒவ்வொரு வாதத் தொகுப்புக்கும் முடிவை நினைவில் வைத்து, மீண்டும் அழைக்கப்படும்போது கணக்கிடாமல் அதைத் தரும். உன்னதமான எடுத்துக்காட்டு — ஃபிபோனாச்சி எண்கள்; சேமிப்பு இல்லாமல் அவை அதிவேகமாக நீண்ட நேரம் எடுக்கும்:
import functools
@functools.cache
def fib(n):
return n if n < 2 else fib(n - 1) + fib(n - 2)
print(fib(100)) # 354224848179261915075
functools.cache (Python 3.9-இல் வந்தது) எல்லா முடிவுகளையும் வரம்பின்றி வைத்திருக்கும். @functools.lru_cache(maxsize=128) சமீபத்திய பதிவுகளை மட்டும் வைத்து பழையவற்றை நீக்கும் — நீண்ட நேரம் இயங்கும் சேவைகளுக்கு இது பாதுகாப்பானது. புள்ளிவிவரங்களை fib.cache_info() மூலம் பார்க்கலாம், fib.cache_clear() மூலம் அழிக்கலாம். இத்தகைய செயல்பாடுகளின் வாதங்கள் ஹாஷ் செய்யக்கூடியதாக இருக்க வேண்டும்: பட்டியலை வாதமாகக் கொடுத்தால் TypeError: unhashable type: 'list' வரும். மேலும் தூய செயல்பாடுகளை மட்டுமே சேமியுங்கள் — ஒரே வாதங்களுக்கு எப்போதும் ஒரே முடிவைத் தருபவை, பக்க விளைவுகள் இல்லாதவை.
property decorator என்ன செய்கிறது
@property ஒரு முறையை அடைப்புகள் இல்லாமல் பண்புக்கூறாகப் பயன்படுத்த அனுமதிக்கும்; தேவைப்பட்டால் ஒதுக்கலைக் கட்டுப்படுத்தும்:
class Account:
def __init__(self, balance):
self._balance = balance
@property
def balance(self):
return self._balance
@balance.setter
def balance(self, value):
if value < 0:
raise ValueError("இருப்பு எதிர்மறையாக இருக்க முடியாது")
self._balance = value
acc = Account(100)
print(acc.balance) # 100
acc.balance = -5 # ValueError
staticmethod மற்றும் classmethod
class Money:
def __init__(self, kopecks):
self.kopecks = kopecks
@classmethod
def from_rubles(cls, rubles):
return cls(round(rubles * 100))
@staticmethod
def is_valid(value):
return value >= 0
m = Money.from_rubles(12.5)
print(m.kopecks, Money.is_valid(-1)) # 1250 False
@classmethod தான் ஒரு முறையை நிகழ்வுகளுக்குப் பதிலாக வகுப்பிலேயே வேலை செய்ய அனுமதிக்கிறது: from_rubles துணைவகையின் பொருளையும் சரியாக உருவாக்கும்.
வகுப்புகளின் decorators மற்றும் decorator-ஆக வகுப்புகள்
Decorator-ஐ ஒரு வகுப்புக்கும் பயன்படுத்தலாம்: அது வகுப்பைப் பெற்று அதே அல்லது மாற்றப்பட்ட வகுப்பைத் திருப்பும். @dataclass இப்படித்தான் வேலை செய்கிறது. எளிய எடுத்துக்காட்டு — வகுப்புகளின் பதிவேடு:
registry = {}
def register(cls):
registry[cls.__name__] = cls
return cls
@register
class CsvExporter:
pass
print(registry) # {'CsvExporter': <class '__main__.CsvExporter'>}
எதிர்த் திசையில், ஒரு வகுப்பு decorator-ஆகப் பயன்படும். __call__ முறை உள்ள எந்தப் பொருளும் பொருந்தும்; நிலையை நிகழ்வின் பண்புக்கூறுகளில் வைப்பது வசதியானது:
import functools
class CountCalls:
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.calls = 0
def __call__(self, *args, **kwargs):
self.calls += 1
return self.func(*args, **kwargs)
@CountCalls
def tick():
pass
tick(); tick()
print(tick.calls) # 2
ஒத்திசைவற்ற decorator
async def செயல்பாடுகளுக்கு உறையும் ஒரு coroutine ஆக இருக்க வேண்டும், முடிவை await மூலம் காத்திருக்க வேண்டும்; இல்லையென்றால் இயங்காத coroutine பொருள் திரும்பும்:
import functools
def log_async(func):
@functools.wraps(func)
async def wrapper(*args, **kwargs):
print(f"தொடக்கம் {func.__name__}")
result = await func(*args, **kwargs)
print(f"தயார் {func.__name__}")
return result
return wrapper
Python-இல் decorators எதற்காக
- பொதுவான தர்க்கம் — பதிவு செய்தல், அளவீடுகள், நேர அளவீடு; ஒவ்வொரு செயல்பாட்டிலும் குறியீட்டை நகலெடுக்காமல்.
- தற்காலிக சேமிப்பு — விலையுயர்ந்த தூய கணக்கீடுகளுக்கு
@functools.cache. - சரிபார்ப்புகள் — அணுகல் உரிமைகள், வாதங்களின் சரிபார்ப்பு, இணையக் கையாளுநர்களில் அங்கீகாரம்.
- பதிவு செய்தல் — Flask மற்றும் FastAPI-இல் வழிகள் (
@app.get("/")), pytest fixtures. - நம்பகத்தன்மை — மீண்டும் முயற்சிகள், நேர வரம்புகள், பிழை கையாளுதல்.
முக்கிய நன்மை — பொறுப்புகளின் பிரிப்பு: செயல்பாடு தன் வேலையைச் செய்கிறது, துணை நடத்தை ஒரு வரியில் இணைக்கப்படுகிறது, அதே போல எளிதாக நீக்கப்படுகிறது.
பொதுவான தவறுகள்
- உறை முடிவைத் திருப்பவில்லை — அலங்கரித்த பின் செயல்பாடு
Noneதருகிறது. *args, **kwargsஇல்லை — வாதங்களுடன் அழைக்கும்போதுTypeError.wrapsமறந்தது — தவறான__name__மற்றும் docstring, பதிவுகளில் குழப்பம்.- தொழிற்சாலையில் அடைப்புகள் —
@repeat(3)-க்குப் பதிலாக@repeat: செயல்பாடுtimesஅளவுருவுக்குள் போய்விடும். - decorator உடலில் கனமான குறியீடு — அது அழைப்பின்போது அல்ல, தொகுதி இறக்குமதியின்போதே இயங்கும்.
சுருக்கம்
Python decorators உயர்-வரிசை செயல்பாடுகள்: அவை ஒரு செயல்பாட்டைப் பெற்று உறையைத் திருப்பும்; @ என்பது ஒதுக்கலுக்கான ஒரு குறுகிய வடிவம் மட்டுமே. *args, **kwargs, return result, functools.wraps கொண்ட வார்ப்புருவிலிருந்து தொடங்குங்கள்; அமைப்புகள் தேவைப்பட்டால் தொழிற்சாலை அடுக்கைச் சேர்க்கலாம். Decorators-க்கு அடிப்படையான செயல்பாடுகள், மூடல்கள் மற்றும் annotations Python பாடநெறியில் நடைமுறையில் விளக்கப்பட்டுள்ளன.
அடிக்கடி கேட்கப்படும் கேள்விகள்
decorator-ஐ ஒரு செயல்பாட்டிலிருந்து நீக்க முடியுமா?
decorator <code>functools.wraps</code> பயன்படுத்தினால் அசல் <code>__wrapped__</code> பண்புக்கூறில் கிடைக்கும்: <code>func.__wrapped__(...)</code> உறை இல்லாமல் அழைக்கும். இல்லையென்றால் குறியீட்டிலிருந்து <code>@</code> வரியை நீக்குவதுதான் வழி.
decorator-இன் குறியீடு எப்போது இயங்கும்?
decorator-இன் உடல் ஒருமுறை மட்டும், செயல்பாடு அறிவிக்கப்படும்போது, பொதுவாக தொகுதி இறக்குமதியின்போது இயங்கும். wrapper-க்குள் உள்ள குறியீடு அலங்கரிக்கப்பட்ட செயல்பாடு அழைக்கப்படும் ஒவ்வொரு முறையும் இயங்கும்.
வகுப்பின் முறைகளுக்கும் decorators பயன்படுத்தலாமா?
ஆம், *args, **kwargs கொண்ட சாதாரண decorator முறைகளுக்கும் வேலை செய்யும்: <code>self</code> வெறுமனே args-இன் முதல் உறுப்பாக இருக்கும்.
decorator மற்றும் மரபுரிமை (inheritance) இடையே வேறுபாடு என்ன?
மரபுரிமை படிநிலை மூலம் வகுப்பின் நடத்தையை மாற்றுகிறது; decorator குறிப்பிட்ட செயல்பாட்டையோ வகுப்பையோ வெளியிலிருந்து சுற்றுகிறது; குறியீட்டை மாற்றாமல் மற்ற decorators-உடன் எளிதாக இணைகிறது.
“Python 3.15: от новичка до профи” பாடநெறியில் இந்தத் தலைப்பு பயிற்சியுடன் விரிவாக விளக்கப்பட்டுள்ளது.
பாடநெறிக்குச் செல்