- Funksiyaları obyekt kimi ötürmək, qaytarmaq və susmaya görə qiymətlərin nə vaxt hesablandığını izah etmək
- Qapanma yaratmaq və
nonlocal-dan düzgün istifadə etmək @wrapsilə dekorator və arqumentli dekorator yazmaqlru_cacheilə nəticələri keşləmək və onun məhdudiyyətlərini bilmək
İyirmi funksiyan var və istəyirsən ki, hər biri çağırışlarını jurnala yazsın, şəbəkə xətasından sonra yenidən cəhd etsin və ya artıq hesabladığı nəticələri yadda saxlasın. Eyni sətirləri iyirmi yerə köçürmək pis fikirdir. Python-un cavabı dekoratordur: funksiyanın üstündə yazılmış @lru_cache kimi bir sətir onun koduna toxunmadan ona yeni davranış əlavə edir. Flask, FastAPI və pytest kimi freymvorklar dekoratorlar üzərində qurulub. Öz dekoratorunu yazmaq üçün iki ideya lazımdır: funksiyalar obyektdir, daxili funksiyalar isə ətraflarını yadda saxlaya bilir — bunlar qapanmalardır.
Funksiyalar obyektdir
Python-da funksiya ədəd və ya siyahı kimi adi obyektdir. Onu başqa ada mənimsətmək, lüğətdə saxlamaq, başqa funksiyaya ötürmək və funksiyadan qaytarmaq olar. map(square, ...) məhz buna görə işləyir: funksiyanın özünü ötürürük, onu çağırmırıq (mötərizə yoxdur).
def square(x):
return x * x
f = square
print(f(5), f.__name__)
operations = {'double': lambda x: x * 2, 'square': square}
for name, op in operations.items():
print(name, op(7))
print(list(map(square, [1, 2, 3])))▸ Gözlənilən nəticə
25 square double 14 square 49 [1, 4, 9]
Funksiya obyekt olduğu üçün onun atributları da var: __name__, __doc__ və parametrlərin susmaya görə qiymətlərinin saxlandığı __defaults__. Bu qiymətlər hər çağırışda yox, def icra olunanda bir dəfə hesablanır. Python-un ən məşhur tələlərindən biri məhz bununla izah olunur:
def add_item(item, basket=[]):
basket.append(item)
return basket
print(add_item('apple'))
print(add_item('pear'))
print(add_item.__defaults__)
def add_item_fixed(item, basket=None):
if basket is None:
basket = []
basket.append(item)
return basket
print(add_item_fixed('apple'), add_item_fixed('pear'))▸ Gözlənilən nəticə
['apple'] ['apple', 'pear'] (['apple', 'pear'],) ['apple'] ['pear']
Qapanmalar və nonlocal
Python adı dörd sahədə, LEGB ardıcıllığı ilə axtarır: Local (cari funksiya), Enclosing (əhatə edən funksiyalar), Global (modul) və Built-in (len, print kimi daxili adlar). Deməli, daxili funksiya onu əhatə edən funksiyanın dəyişənlərini oxuya bilər. Ən maraqlısı odur ki, xarici funksiya işini bitirəndən sonra da bu dəyişənlər daxili funksiyada qalır:
def make_multiplier(factor):
def multiply(x):
return x * factor
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(10), triple(10))
print(double.__closure__[0].cell_contents)▸ Gözlənilən nəticə
20 30 2
Qapanma — əhatə edən sahədən yadda saxladığı dəyişənlərlə birlikdə funksiyadır. Burada double funksiyası factor = 2 qiymətini yadda saxlayır: qiymət xanada (cell) durur və onu __closure__ vasitəsilə görmək olar.
Əhatə edən dəyişəni oxumaq sərbəstdir, amma ona qiymət mənimsətsən, Python adı yeni lokal dəyişən sayar və count += 1 sətri UnboundLocalError xətası verər. **nonlocal** sözü bildirir ki, bu ad əhatə edən funksiyaya aiddir. make_counter()-in hər çağırışı ayrıca, gizli count yaradır:
def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
counter = make_counter()
print(counter(), counter(), counter())
other = make_counter()
print(other())▸ Gözlənilən nəticə
1 2 3 1
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs])
funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs])▸ Gözlənilən nəticə
[2, 2, 2] [0, 1, 2]
Dekoratorlar
Dekorator funksiya qəbul edib yeni funksiya qaytaran funksiyadır. Adətən bu, orijinalı çağırmazdan əvvəl və sonra nəsə edən sarğı funksiyadır (wrapper). def add-in üstündəki @log_calls sətri sadəcə add = log_calls(add) yazılışının sintaktik şəkəridir. Sarğı funksiya *args, **kwargs qəbul edir, ona görə istənilən imzalı funksiya ilə işləyir:
def log_calls(func):
def wrapper(*args, **kwargs):
print(f'calling {func.__name__}{args}')
result = func(*args, **kwargs)
print(f'{func.__name__} returned {result}')
return result
return wrapper
@log_calls
def add(a, b):
return a + b
total = add(2, 3)
print(total, add.__name__)▸ Gözlənilən nəticə
calling add(2, 3) add returned 5 5 wrapper
Son sətrə bax: funksiyanın adı indi wrapper-dir. Dekorator add-i əvəz edib, onun adını və sənədləşdirmə sətrini itirib — bu, sazlamanı, sənədləri və pytest kimi alətləri korlayır. Həll **functools.wraps** dekoratorudur: o, orijinal funksiyanın __name__, __doc__ və digər atributlarını sarğı funksiyaya köçürür. Sarğı funksiya öz vəziyyətini atributda da saxlaya bilər:
from functools import wraps
def count_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
wrapper.calls += 1
return func(*args, **kwargs)
wrapper.calls = 0
return wrapper
@count_calls
def greet(name):
"""Return a greeting."""
return f'Salam, {name}!'
print(greet('Leyla'))
print(greet('Elvin'))
print(greet.calls, greet.__name__, greet.__doc__)▸ Gözlənilən nəticə
Salam, Leyla! Salam, Elvin! 2 greet Return a greeting.
Arqumentli dekoratorlar
Bəs @retry(times=3) necə işləyir? Burada əvvəlcə retry(times=3) çağırılır və onun nəticəsi əsl dekoratordur. Deməli, arqumentli dekoratorun üç səviyyəsi var: arqumentləri qəbul edən fabrik, funksiyanı qəbul edən dekorator və hər çağırışda işləyən sarğı funksiya. Qapanmalar sayəsində sarğı funksiya həm times-ı, həm də func-ı görür:
from functools import wraps
def retry(times):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(1, times + 1):
try:
return func(*args, **kwargs)
except ConnectionError as e:
print(f'attempt {attempt} failed: {e}')
raise ConnectionError(f'{func.__name__} failed {times} times')
return wrapper
return decorator
calls = 0
@retry(times=3)
def fetch_rates():
global calls
calls += 1
if calls < 3:
raise ConnectionError('server busy')
return {'USD': 1.7}
print(fetch_rates())▸ Gözlənilən nəticə
attempt 1 failed: server busy
attempt 2 failed: server busy
{'USD': 1.7}Dekoratorları üst-üstə də yazmaq olar. Onlar aşağıdan yuxarıya tətbiq olunur — def-ə ən yaxın olan dekorator funksiyanı birinci bükür:
def bold(func):
return lambda: '<b>' + func() + '</b>'
def italic(func):
return lambda: '<i>' + func() + '</i>'
@bold
@italic
def title():
return 'Educora'
print(title())▸ Gözlənilən nəticə
<b><i>Educora</i></b>
Hazır dekoratorlar: lru_cache
Standart kitabxanada çoxlu faydalı dekorator var. **functools.lru_cache funksiyanın nəticələrini arqumentlərinə görə lüğətdə saxlayır — buna memoizasiya** deyilir. Sadə rekursiv fib(80) təxminən 7,6 · 10¹⁶ çağırış edərdi; keşlə isə hər qiymət yalnız bir dəfə hesablanır:
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
return n if n < 2 else fib(n - 1) + fib(n - 2)
print(fib(80))
print(fib.cache_info())▸ Gözlənilən nəticə
23416728348467685 CacheInfo(hits=78, misses=81, maxsize=None, currsize=81)
cache_info() göstərir ki, cəmi 81 real hesablama olub (misses), qalan 78 sorğuya isə keşdən cavab verilib (hits). maxsize=None limitsiz keş deməkdir; ədəd versən, məsələn, maxsize=128, ən çoxdan istifadə olunmayan (LRU) qeydlər birinci silinir. Python 3.9-dan bəri @functools.cache yazılışı lru_cache(maxsize=None)-un qısa formasıdır. Növbəti dərslərdə başqa daxili dekoratorlarla da tanış olacaqsan: @property, @classmethod, @staticmethod, @dataclass və @contextmanager.
Funksiyanın qaytardığı sətri böyük hərflərə çevirən uppercase dekoratorunu yaz. Funksiya öz adını saxlasın deyə @wraps işlət.
from functools import wraps
def uppercase(func):
# return a wrapper (with @wraps) that returns func's result in upper case
...
@uppercase
def greet(name):
return f'salam, {name}'
print(greet('Aysel'))
print(greet.__name__)▸ Gözlənilən nəticə
SALAM, AYSEL greet
make_accumulator(start) funksiyasını yaz: o, add(x) funksiyası qaytarsın; add hər çağırışda x-i ümumi cəmə əlavə edib yeni cəmi qaytarsın. Hər akkumulyatorun öz cəmi olmalıdır.
def make_accumulator(start=0):
# return a function add(x) that adds x to a running total and returns it
...
acc = make_accumulator(100)
print(acc(10))
print(acc(5))
other = make_accumulator()
print(other(1))▸ Gözlənilən nəticə
110 115 1
Əsas fikirlər
- Funksiyalar obyektdir: onları ötürmək, qaytarmaq və saxlamaq olar; susmaya görə qiymətlər
deficra olunanda bir dəfə hesablanır. - Qapanma əhatə edən funksiyanın dəyişənlərini yadda saxlayır;
nonlocalonlara yeni qiymət verməyə imkan yaradır. def f-in üstündəki@decoyazılışıf = deco(f)deməkdir; sarğı funksiya*args, **kwargsvə@wraps(func)işlədir.- Arqumentli dekorator fabrikdir:
@retry(times=3)yazılışıretry(times=3)(f)deməkdir. lru_cacheheşlənən arqumentli təmiz funksiyaların nəticələrini yadda saxlayır.
Özünü yoxla
10 sual. Hər düzgün cavab XP qazandırır.
def load(): sətrinin üstündə yazılmış @timer nə deməkdir?