# Mga decorator sa Python: ano ang mga ito, paano gumagana, at paano gumawa ng sarili

URL: https://skillaria.top/tl/guides/python/decorator-python

Ang decorator sa Python ay function na tumatanggap ng ibang function at nagbabalik ng bago, karaniwang wrapper, na nagdaragdag ng gawi bago o pagkatapos ng tawag: logging, pagsukat ng oras, caching, o pagsusuri ng pahintulot. Ang @decorator sa ibabaw ng def ay shortcut ng func = decorator(func); para mapanatili ng wrapper ang pangalan at docstring ng orihinal na function, gamitin ang functools.wraps.

Ang decorator sa Python ay function na tumatanggap ng isa pang function at nagbabalik ng bago — kadalasan ay wrapper sa paligid ng orihinal. Sa ganitong paraan, nadadagdagan ng gawi ang isang function — logging, pagsukat ng oras, cache, o muling pagsubok — nang hindi binabago ang code nito. Isinusulat ang mga decorator sa Python sa pamamagitan ng linyang `@pangalan` sa itaas ng deklarasyon ng function, at makikita ang mga ito kahit saan: sa Flask at FastAPI, sa pytest, at sa standard library.

## Ano ang decorator sa simpleng salita

Isipin ang regalo at ang pambalot nito. Nananatiling pareho ang regalo (function), pero may patong sa labas na unang gumagana. Ganyan ang decorator: kapag tinawag ang function, unang tatakbo ang code ng wrapper, tatawagin nito ang orihinal na function, at maaari nitong gawin ang anuman sa resulta.

Gumagana ito dahil sa Python, ordinaryong object ang mga function. Maaari silang ipasa sa ibang function, ibalik mula sa function, at ideklara sa loob ng function. Nakabatay ang mga decorator sa tatlong katangiang ito.

## Paano gumagana ang decorator: ang @ ay syntactic sugar

Narito ang pinakasimpleng decorator, na nagpi-print ng mensahe bago at pagkatapos ng tawag:

```
def log_calls(func):
    def wrapper():
        print(f"Tinatawag ang {func.__name__}")
        result = func()
        print(f"Natapos ang {func.__name__}")
        return result
    return wrapper

@log_calls
def hello():
    print("Kumusta!")

hello()
```

```
Tinatawag ang hello
Kumusta!
Natapos ang hello
```

Ang `@log_calls` sa itaas ng `def` ay ganap na katumbas ng linyang nasa ilalim ng deklarasyon:

```
def hello():
    print("Kumusta!")

hello = log_calls(hello)
```

Ngayon, ang pangalang `hello` ay tumuturo na sa `wrapper`, at nabubuhay ang orihinal na function sa loob ng closure — naaalala ng `wrapper` ang variable na `func` mula sa panlabas na function. Mahalagang detalye: isang beses lang nailalapat ang decorator, sa sandaling tumakbo ang `def`, ibig sabihin kapag na-import ang module; pero tumatakbo ang wrapper sa bawat tawag.

## Mga closure at decorator

Ang closure ay nested na function na naaalala ang mga variable ng panlabas na function kahit tapos na ito. Kung walang closure, walang decorator: sa ganitong paraan nakukuha ng `wrapper` ang access sa `func`. Maaari ring mag-imbak ng estado sa closure, gaya ng counter ng tawag:

```
def count_calls(func):
    calls = 0
    def wrapper(*args, **kwargs):
        nonlocal calls
        calls += 1
        print(f"{func.__name__}: tawag blg. {calls}")
        return func(*args, **kwargs)
    return wrapper
```

## Paano gumawa ng decorator sa Python: pangkalahatang template

May dalawang kahinaan ang unang halimbawa: hindi tumatanggap ng argumento ang wrapper, at "nilalamon" nito ang pangalan ng orihinal na function. Ito ang gumaganang template na dapat simulan sa anumang decorator:

```
import functools

def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # code bago tawagin
        result = func(*args, **kwargs)
        # code pagkatapos tawagin
        return result
    return wrapper
```

Tatlong tuntunin ng template:

1. **`*args, **kwargs`** — tumatanggap ang wrapper ng anumang argumento at ipinapasa ang mga ito nang hindi binabago.
2. **`return result`** — huwag kalimutang ibalik ang resulta, kung hindi, laging `None` ang ibibigay ng decorated na function.
3. **`@functools.wraps(func)`** — ilipat ang metadata ng orihinal na function sa wrapper.

Halimbawa ng decorator — pagsukat ng oras ng pagpapatakbo:

```
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"Tumagal ng {elapsed:.3f} s ang {func.__name__}")
        return result
    return wrapper

@timer
def slow_sum(n):
    return sum(range(n))

slow_sum(10_000_000)   # Tumagal ng 0.142 s ang slow_sum
```

## Bakit kailangan ang functools.wraps

Kung wala ang `wraps`, mapapalitan ng decorated na function ang pangalan at docstring nito — makikita ang `wrapper` sa labas:

```
def plain(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@plain
def add(a, b):
    """Nagdaragdag ng dalawang numero."""
    return a + b

print(add.__name__, add.__doc__)   # wrapper None
```

Sa `@functools.wraps(func)`, nakukuha ng wrapper ang `__name__`, `__doc__`, `__module__`, `__qualname__`, at `__annotations__` ng orihinal, at nakatago sa attribute na `__wrapped__` ang reference sa orihinal. Mahalaga ito para sa debugging at traceback, para sa `help()`, para sa mga framework na nagre-rehistro ng function ayon sa pangalan, at para sa `inspect.signature`, na nagpapakita ng tunay na signature.

## Decorator na may parameter (na may argumento)

Minsan kailangan ng decorator ng mga setting: `@retry(times=3)`, `@app.route("/")`. Para dito, nagdaragdag ng isa pang antas ng nesting. Tumatanggap ang panlabas na function ng mga parameter at nagbabalik ng tunay na decorator — ang ganitong konstruksiyon ay tinatawag na decorator factory.

```
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 — tatlong linya
```

Pagkakasunod ng mga tawag: ibinabalik ng `repeat(times=3)` ang `decorator`, na inilalapat sa `ping` at nagbabalik ng `wrapper`. Sa madaling sabi, ang `@repeat(times=3)` ay katumbas ng `ping = repeat(times=3)(ping)`.

Praktikal na halimbawa — muling pagsubok kapag may error:

```
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():
    ...
```

## Maraming decorator sa iisang function

Maaaring magkasunod-sunod na ilagay ang mga decorator. Inilalapat ang mga ito mula ibaba pataas: ang pinakamalapit sa `def` ang unang nailalapat.

```
@timer
@log_calls
def report():
    ...

# katumbas nito:
report = timer(log_calls(report))
```

Sa pagtawag, unang tumatakbo ang panlabas na layer (`timer`), kaya masusukat din nito ang oras ng `log_calls`. Mahalaga ang pagkakasunod, halimbawa kung ang isang decorator ay sumusuri ng pahintulot at ang isa pa ay nag-cache ng resulta.

## Mga karaniwan at built-in na decorator ng Python

Marami nang kapaki-pakinabang na decorator sa wika at sa standard library:

| Decorator | Ginagawa |
| --- | --- |

| `@property` | ginagawang read-only na attribute ang method; kasama ng `@x.setter`, nagiging kontroladong attribute |

| `@staticmethod` | method ng klase na walang `self`, ordinaryong function sa namespace ng klase |

| `@classmethod` | tumatanggap ang method ng klase na `cls` sa halip na instance — ganito isinusulat ang mga alternatibong constructor |

| `@functools.cache`, `@functools.lru_cache` | nagca-cache ng resulta ayon sa argumento |

| `@functools.wraps` | kinokopya ang metadata ng function sa wrapper |

| `@dataclasses.dataclass` | bumubuo ng `__init__`, `__repr__`, `__eq__` para sa klase |

| `@contextlib.contextmanager` | ginagawang context manager para sa `with` ang generator |

### Caching: functools.cache at lru_cache

Ang caching decorator ay nagtatandaan ng resulta para sa bawat hanay ng argumento, at sa susunod na tawag ay ibinabalik ito nang hindi na kinakalkula. Klasikong halimbawa ang recursive na Fibonacci, na kung walang cache ay napakabagal ng pagkalkula — exponential ang oras:

```
import functools

@functools.cache
def fib(n):
    return n if n < 2 else fib(n - 1) + fib(n - 2)

print(fib(100))   # 354224848179261915075
```

Iniimbak ng `functools.cache` (lumitaw sa Python 3.9) ang lahat ng resulta nang walang limitasyon. Ang `@functools.lru_cache(maxsize=128)` ay nagtatago lamang ng pinakahuling mga entry at inaalis ang mga luma — mas ligtas ito para sa matagal nang tumatakbong serbisyo. Makikita ang estadistika gamit ang `fib.cache_info()`, at nililinis gamit ang `fib.cache_clear()`. Dapat hashable ang mga argumento ng ganitong function: ang list bilang argumento ay magbibigay ng `TypeError: unhashable type: 'list'`. Ang caching ay dapat gamitin lamang sa mga pure function — yaong laging nagbabalik ng parehong resulta sa parehong argumento at walang side effect.

### Ano ang ginagawa ng decorator na property

Pinapayagan ng `@property` na tawagin ang method na parang attribute, nang walang panaklong, at kung kinakailangan, kontrolin ang pag-assign:

```
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("Hindi maaaring negatibo ang balanse")
        self._balance = value

acc = Account(100)
print(acc.balance)   # 100
acc.balance = -5     # ValueError
```

### staticmethod at 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
```

Ang `@classmethod` ang nagbibigay-daan sa method na gumana sa mismong klase at hindi sa mga instance nito: tamang makakalikha ang `from_rubles` ng object ng subclass.

## Mga decorator ng klase at mga klaseng decorator

Maaaring ilapat ang decorator sa klase: tumatanggap ito ng klase at nagbabalik ng klase (ang parehong klase o binago). Ganito gumagana ang `@dataclass`. Simpleng halimbawa — isang registry ng mga klase:

```
registry = {}

def register(cls):
    registry[cls.__name__] = cls
    return cls

@register
class CsvExporter:
    pass

print(registry)   # {'CsvExporter': }
```

Ang kabaligtaran ay klase bilang decorator. Puwede ang anumang object na may method na `__call__`; kapaki-pakinabang na itago ang estado sa mga attribute ng instance:

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

## Asynchronous na decorator

Para sa mga function na `async def`, dapat ding maging coroutine ang wrapper at maghintay ng resulta gamit ang `await`; kung hindi, ibabalik nito ang hindi pa natatapos na coroutine object:

```
import functools

def log_async(func):
    @functools.wraps(func)
    async def wrapper(*args, **kwargs):
        print(f"Nagsimula ang {func.__name__}")
        result = await func(*args, **kwargs)
        print(f"Tapos na ang {func.__name__}")
        return result
    return wrapper
```

## Bakit gumagamit ng decorator sa Python

- **Cross-cutting na lohika** — logging, metrics, at pagsukat ng oras nang hindi kinokopya ang code sa bawat function.
- **Caching** — `@functools.cache` para sa mamahaling pure na kalkulasyon.
- **Pagsusuri** — pahintulot, pagbeberipika ng argumento, authorization sa web handler.
- **Pagre-rehistro** — mga ruta sa Flask at FastAPI (`@app.get("/")`), mga fixture ng pytest.
- **Katatagan** — muling pagsubok, timeout, pangangasiwa ng error.

Pangunahing bentahe ang paghihiwalay ng responsibilidad: ginagawa ng function ang sariling gawain, at isinasaksak ang karagdagang gawi sa isang linya, at ganoon din kadali itong alisin.

## Karaniwang pagkakamali

- **Hindi ibinabalik ng wrapper ang resulta** — `None` ang ibinibigay ng function pagkatapos ma-decorate.
- **Walang `*args, **kwargs`** — `TypeError` kapag tinawag na may argumento.
- **Nakalimutan ang `wraps`** — maling `__name__` at docstring, at kalituhan sa mga log.
- **Nakalimutan ang panaklong sa factory** — `@repeat` sa halip na `@repeat(3)`: mapupunta ang function sa parameter na `times`.
- **Mabigat na code sa katawan ng decorator** — tumatakbo ito sa pag-import ng module, hindi sa pagtawag.

## Buod

Ang mga decorator sa Python ay higher-order function: tumatanggap ng function, nagbabalik ng wrapper, at ang `@` ay maikling anyo lamang ng assignment. Magsimula sa template na may `*args, **kwargs`, `return result`, at `functools.wraps`; para sa mga setting, magdagdag ng antas ng factory. Ang mga function, closure, at annotation na pinagbabatayan ng mga decorator ay tinalakay nang praktikal sa kurso ng Python.

## FAQ

**Maaari bang alisin ang decorator sa isang function?**

Kung gumagamit ang decorator ng functools.wraps, nasa attribute na __wrapped__ ang orihinal: ang func.__wrapped__(...) ay tatawag sa function nang walang wrapper. Kung hindi, kailangang tanggalin ang linyang may @ sa code.

**Kailan tumatakbo ang code ng decorator?**

Ang katawan mismo ng decorator ay tumatakbo nang isang beses, sa deklarasyon ng function, kadalasan habang ina-import ang module. Ang code sa loob ng wrapper ay tumatakbo sa bawat tawag ng decorated na function.

**Maaari bang i-decorate ang mga method ng klase?**

Oo, gumagana ang karaniwang decorator na may *args, **kwargs kahit sa mga method; ang self ay magiging unang elemento lamang ng args.

**Ano ang pagkakaiba ng decorator at inheritance?**

Binabago ng inheritance ang gawi ng klase sa pamamagitan ng hierarchy, samantalang binabalot ng decorator ang partikular na function o klase mula sa labas, at madaling pagsamahin sa ibang decorator nang hindi binabago ang code.


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