# Decorators katika Python: ni nini, zinavyofanya kazi na jinsi ya kuandika yako

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

Decorator katika Python ni kazi inayopokea kazi nyingine na kurudisha mpya, kwa kawaida ni kifungashio kinachoongeza tabia kabla au baada ya wito: kurekodi kumbukumbu, kupima muda, kuhifadhi matokeo kwenye cache, kukagua ruhusa. Maandishi @decorator juu ya def ni njia fupi ya func = decorator(func); ili kifungashio kibebe jina na docstring ya kazi asili, hutumika functools.wraps.

Decorator katika Python ni kazi inayopokea kazi nyingine na kurudisha kazi mpya, kwa kawaida kifungashio (wrapper) kinachozunguka ile ya awali. Kwa njia hii tabia huongezwa kwa kazi — kurekodi kumbukumbu, kupima muda, cache, majaribio ya kurudia — bila kubadilisha msimbo wake. Decorators huandikwa kwa mstari `@jina` juu ya tangazo la kazi, na zinapatikana kila mahali: katika Flask na FastAPI, pytest, na maktaba ya kawaida.

## Decorator kwa lugha rahisi

Fikiria zawadi na karatasi ya kuifungia. Zawadi (kazi) inabaki ile ile, lakini nje kuna safu inayotenda kwanza. Decorator ni kifungashio kama hicho: wakati wa wito, kwanza msimbo wa kifungashio unatekelezwa, kisha unaita kazi ya awali na unaweza kufanya jambo juu ya matokeo.

Hili linawezekana kwa sababu katika Python kazi ni vitu vya kawaida. Zinaweza kupitishwa kwa kazi nyingine, kurudishwa kutoka kwa kazi, na kutangazwa ndani ya kazi. Decorators zimejengwa juu ya sifa hizo tatu.

## Jinsi decorator inavyofanya kazi: @ ni sukari ya sintaksia

Chukua decorator rahisi kabisa, inayochapisha ujumbe kabla na baada ya wito:

```
def log_calls(func):
    def wrapper():
        print(f"Ninaita {func.__name__}")
        result = func()
        print(f"{func.__name__} imekamilika")
        return result
    return wrapper

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

hello()
```

```
Ninaita hello
Habari!
hello imekamilika
```

Maandishi `@log_calls` juu ya `def` ni sawa kabisa na mstari unaokuja baada ya tangazo:

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

hello = log_calls(hello)
```

Sasa jina `hello` linaelekeza kwenye `wrapper`, na kazi asili inaishi ndani ya closure — `wrapper` inakumbuka kigezo `func` kutoka kwa kazi ya nje. Jambo muhimu: decorator inatumika mara moja, wakati `def` inatekelezwa, yaani wakati moduli inapoingizwa, na kifungashio kinafanya kazi kila mara kazi inapoitwa.

## Closures na decorators

Closure ni kazi iliyoingizwa ambayo inakumbuka vigezo vya kazi ya nje hata baada ya hiyo kumaliza. Bila closures decorators zisingekuwepo: ndivyo `wrapper` inavyopata ufikiaji wa `func`. Ndani ya closure pia unaweza kuhifadhi hali, kwa mfano kaunta ya miito:

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

## Jinsi ya kuandika decorator katika Python: kiolezo cha jumla

Mfano wa kwanza una kasoro mbili: kifungashio hakipokei hoja, na kinafuta jina la kazi asili. Kiolezo kinachofanya kazi, ambacho ni vyema kuanza nacho kila decorator, ni hiki:

```
import functools

def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # msimbo kabla ya wito
        result = func(*args, **kwargs)
        # msimbo baada ya wito
        return result
    return wrapper
```

Kanuni tatu za kiolezo:

1. **`*args, **kwargs`** — kifungashio kinapokea hoja zozote na kuzipitisha mbele bila mabadiliko.
2. **`return result`** — usisahau kurudisha matokeo, vinginevyo kazi iliyopambwa itatoa `None` kila mara.
3. **`@functools.wraps(func)`** — kuhamisha metadata ya kazi asili kwenda kwenye kifungashio.

Mfano wa decorator: kupima muda wa utekelezaji:

```
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__} ilichukua sekunde {elapsed:.3f}")
        return result
    return wrapper

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

slow_sum(10_000_000)   # slow_sum ilichukua sekunde 0.142
```

## Kwa nini functools.wraps inahitajika

Bila `wraps`, kazi iliyopambwa inapoteza jina lake, docstring na sifa nyingine — nje inaonekana `wrapper`:

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

@plain
def add(a, b):
    """Inaongeza namba mbili."""
    return a + b

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

Kwa `@functools.wraps(func)` kifungashio kinapata `__name__`, `__doc__`, `__module__`, `__qualname__`, `__annotations__` za kazi asili, na sifa `__wrapped__` inashikilia rejeleo la asili. Hili ni muhimu kwa utatuzi na traceback, kwa `help()`, kwa mifumo inayosajili kazi kwa jina, na kwa `inspect.signature` inayoonyesha sahihi halisi.

## Decorator yenye vigezo (yenye hoja)

Wakati mwingine decorator inahitaji mipangilio: `@retry(times=3)`, `@app.route("/")`. Kwa hili kunaongezwa kiwango kingine cha kuingizwa. Kazi ya nje inapokea vigezo na kurudisha decorator halisi — muundo huu huitwa kiwanda cha decorators (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 — mistari matatu
```

Mpangilio wa wito: `repeat(times=3)` inarudisha `decorator`, ambayo inatumika kwa `ping` na kurudisha `wrapper`. Kwa maneno mengine, `@repeat(times=3)` ni `ping = repeat(times=3)(ping)`.

Mfano wa vitendo — kurudia pale kosa linapotokea:

```
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 kadhaa kwenye kazi moja

Decorators zinaweza kuwekwa moja juu ya nyingine. Hutumika kutoka chini kwenda juu: ile iliyo karibu zaidi na `def` ndiyo ya kwanza.

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

# sawa na hiyo:
report = timer(log_calls(report))
```

Wakati wa wito, safu ya nje (`timer`) inatekelezwa kwanza, kwa hiyo itapima pia muda wa `log_calls`. Mpangilio una umuhimu: kwa mfano, ikiwa decorator moja inakagua ruhusa na nyingine inahifadhi matokeo kwenye cache.

## Decorators za kawaida na zilizojengwa ndani ya Python

Decorators nyingi muhimu tayari zipo katika lugha na maktaba ya kawaida:

| Decorator | Inafanya nini |
| --- | --- |

| `@property` | inageuza mbinu kuwa sifa ya kusoma tu; kwa `@x.setter` inakuwa sifa inayodhibitiwa |

| `@staticmethod` | mbinu ya darasa bila `self`, kazi ya kawaida ndani ya nafasi ya majina ya darasa |

| `@classmethod` | mbinu inapokea darasa `cls` badala ya mfano — hivyo huandikwa wajenzi mbadala |

| `@functools.cache`, `@functools.lru_cache` | huhifadhi matokeo kwa hoja |

| `@functools.wraps` | hunakili metadata ya kazi kwenda kwenye kifungashio |

| `@dataclasses.dataclass` | hutengeneza `__init__`, `__repr__`, `__eq__` kwa darasa |

| `@contextlib.contextmanager` | hugeuza jenereta kuwa meneja wa muktadha kwa `with` |

### Cache: functools.cache na lru_cache

Decorator ya cache inakumbuka matokeo kwa kila seti ya hoja, na wito unaorudiwa unarudisha matokeo bila kuhesabu tena. Mfano wa kawaida ni namba za Fibonacci za kujirudia, ambazo bila cache zinahesabiwa kwa muda wa kiekponenti:

```
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` (ilionekana Python 3.9) huhifadhi matokeo yote bila kikomo. `@functools.lru_cache(maxsize=128)` huhifadhi tu rekodi za hivi karibuni na kufuta za zamani — ni salama zaidi kwa huduma zinazoendesha muda mrefu. Takwimu zinaonekana kwa `fib.cache_info()`, na kusafishwa kwa `fib.cache_clear()`. Hoja za kazi kama hizi lazima ziweze kuhashiwa: orodha kama hoja itasababisha `TypeError: unhashable type: 'list'`. Na cache inafaa tu kwa kazi safi — zile zinazorudisha kitu kile kile kwa hoja zile zile na hazina athari za pembeni.

### Decorator ya property hufanya nini

`@property` inaruhusu kufikia mbinu kama sifa, bila mabano, na kudhibiti upangaji thamani inapohitajika:

```
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("Salio haliwezi kuwa hasi")
        self._balance = value

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

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

Ni `@classmethod` inayoruhusu mbinu kufanya kazi na darasa lenyewe, si na mifano yake: `from_rubles` itaunda kwa usahihi hata kitu cha darasa dogo (subclass).

## Decorators za madarasa na madarasa kama decorators

Decorator inaweza kutumika kwa darasa: inapokea darasa na kurudisha darasa (lile lile au lililobadilishwa). Ndivyo `@dataclass` inavyofanya kazi. Mfano rahisi — rejista ya madarasa:

```
registry = {}

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

@register
class CsvExporter:
    pass

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

Kinyume chake ni darasa kuwa decorator. Kitu chochote chenye mbinu `__call__` kinafaa; hali inahifadhiwa vizuri kwenye sifa za mfano:

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

Kwa kazi za `async def`, kifungashio pia lazima kiwe coroutine na kusubiri matokeo kwa `await`, vinginevyo kitarudisha kitu cha coroutine ambacho hakijatekelezwa:

```
import functools

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

## Kwa nini decorators zinahitajika katika Python

- **Mantiki ya pamoja** — kurekodi kumbukumbu, vipimo, kupima muda bila kunakili msimbo kwenye kila kazi.
- **Cache** — `@functools.cache` kwa hesabu ghali na safi.
- **Ukaguzi** — ruhusa, uthibitishaji wa hoja, uidhinishaji katika vishughulikiaji vya wavuti.
- **Usajili** — njia katika Flask na FastAPI (`@app.get("/")`), fixtures za pytest.
- **Uaminifu** — majaribio ya kurudia, muda wa juu wa kusubiri, ushughulikiaji wa makosa.

Faida kuu ni mgawanyo wa majukumu: kazi inashughulikia kazi yake, na tabia ya ziada inaunganishwa kwa mstari mmoja na pia inaondolewa kwa urahisi.

## Makosa ya kawaida

- **Kifungashio hakirudishi matokeo** — kazi baada ya kupambwa inatoa `None`.
- **Hakuna `*args, **kwargs`** — `TypeError` wakati wa wito kwa hoja.
- **Kusahau `wraps`** — `__name__` na docstring si sahihi, na logi zinachanganya.
- **Mabano ya kiwanda** — `@repeat` badala ya `@repeat(3)`: kazi itaingia kwenye kigezo `times`.
- **Msimbo mzito ndani ya mwili wa decorator** — unatekelezwa wakati wa kuingiza moduli, si wakati wa wito.

## Hitimisho

Decorators katika Python ni kazi za mpangilio wa juu: zinapokea kazi, zinarudisha kifungashio, na `@` ni maandishi mafupi tu ya kazi ya mgawo. Anza na kiolezo chenye `*args, **kwargs`, `return result` na `functools.wraps`; kwa mipangilio ongeza ngazi ya kiwanda. Kazi, closures na type hints ambazo decorators zinategemea zinafundishwa kwa vitendo katika kozi ya Python.

## FAQ

**Je, decorator inaweza kuondolewa kwenye kazi?**

Ikiwa decorator inatumia functools.wraps, asili inapatikana kupitia sifa __wrapped__: func.__wrapped__(...) itaita kazi bila kifungashio. Vinginevyo inabaki tu kuondoa mstari wenye @ kwenye msimbo.

**Msimbo wa decorator unatekelezwa lini?**

Mwili wa decorator yenyewe unatekelezwa mara moja, wakati kazi inatangazwa, kwa kawaida wakati wa kuingiza moduli. Msimbo ulio ndani ya wrapper unatekelezwa kila mara kazi iliyopambwa inapoitwa.

**Je, naweza kupamba mbinu za darasa?**

Ndiyo, decorator ya kawaida yenye *args, **kwargs inafanya kazi pia kwa mbinu: self itakuwa kipengele cha kwanza katika args tu.

**Decorator inatofautianaje na urithi?**

Urithi hubadilisha tabia ya darasa kupitia mfumo wa madarasa, wakati decorator inafunga kazi au darasa mahususi kutoka nje na inachanganyika kwa urahisi na decorators nyingine bila kubadilisha msimbo.


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