# Python-இல் அலங்கரிப்பாளர்கள் (decorators): அவை என்ன, எப்படி வேலை செய்கின்றன, சொந்தமாக எப்படி எழுதுவது

URL: https://skillaria.top/ta/guides/python/dekoratory-python

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
```

வார்ப்புருவின் மூன்று விதிகள்:

1. **`*args, **kwargs`** — உறை எந்த வாதங்களையும் ஏற்று மாற்றாமல் அனுப்புகிறது.
2. **`return result`** — முடிவைத் திருப்பித் தர மறக்காதீர்கள்; இல்லையென்றால் அலங்கரிக்கப்பட்ட செயல்பாடு எப்போதும் `None` தரும்.
3. **`@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': }
```

எதிர்த் திசையில், ஒரு வகுப்பு 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 பாடநெறியில் நடைமுறையில் விளக்கப்பட்டுள்ளன.

## FAQ

**decorator-ஐ ஒரு செயல்பாட்டிலிருந்து நீக்க முடியுமா?**

decorator functools.wraps பயன்படுத்தினால் அசல் __wrapped__ பண்புக்கூறில் கிடைக்கும்: func.__wrapped__(...) உறை இல்லாமல் அழைக்கும். இல்லையென்றால் குறியீட்டிலிருந்து @ வரியை நீக்குவதுதான் வழி.

**decorator-இன் குறியீடு எப்போது இயங்கும்?**

decorator-இன் உடல் ஒருமுறை மட்டும், செயல்பாடு அறிவிக்கப்படும்போது, பொதுவாக தொகுதி இறக்குமதியின்போது இயங்கும். wrapper-க்குள் உள்ள குறியீடு அலங்கரிக்கப்பட்ட செயல்பாடு அழைக்கப்படும் ஒவ்வொரு முறையும் இயங்கும்.

**வகுப்பின் முறைகளுக்கும் decorators பயன்படுத்தலாமா?**

ஆம், *args, **kwargs கொண்ட சாதாரண decorator முறைகளுக்கும் வேலை செய்யும்: self வெறுமனே args-இன் முதல் உறுப்பாக இருக்கும்.

**decorator மற்றும் மரபுரிமை (inheritance) இடையே வேறுபாடு என்ன?**

மரபுரிமை படிநிலை மூலம் வகுப்பின் நடத்தையை மாற்றுகிறது; decorator குறிப்பிட்ட செயல்பாட்டையோ வகுப்பையோ வெளியிலிருந்து சுற்றுகிறது; குறியீட்டை மாற்றாமல் மற்ற decorators-உடன் எளிதாக இணைகிறது.


---
Source: Skillaria.Top (https://skillaria.top)
