8.1 Python-un sahə əhatəsi qaydalarının başa düşülməməsi
Python-da sahə əhatəsi LEGB adlanan qaydaya əsaslanır, bu da qısaltmadır:
-
Local
(funksiya daxilində hər hansı bir üsulla təyin edilən adlar (def
və yalambda
) və bu funksiyada global olaraq elan edilməyənlər); Enclosing
(lokal sahədə bütün statik şəkildə daxiletmə funksiyalarında təyin edilən adlar (def
və yalambda
), daxildən xaricə doğru);Global
(modul faylının ən yuxarı səviyyəsində təyin edilən adlar və yaglobal
əmri ilədef
içərisində təyin edilən adlar);-
Built-in
(daxili adlar modulunda əvvəlcədən təyin edilmiş adlar:open
,range
,SyntaxError
, və digər adlar).
Sadə kimi görünür, elə deyil?
Ancaq, Python-da bunun işləmə tərzi ilə bağlı bəzi incə məqamlar var ki, bu da Python-da proqramlaşdırma ilə bağlı çətin problemlərə gətirib çıxarır. Gəlin aşağıdakı nümunəni nəzərdən keçirək:
x = 10
def foo():
x += 1
print(x)
foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in foo
UnboundLocalError: local variable 'x' referenced before assignment
Problem nədir?
Yuxarıda göstərilən xəta, bir dəyişənə dəyər təyin etdikdə sahə daxilində,
Python onu avtomatik olaraq həmin sahənin lokal dəyişəni
kimi qəbul edir və hər hansı daha yuxarı səviyyəli sahədə olan oxşar adı gizlədir.
Buna görə də, bir çoxu daha əvvəl işləyən kodda, funksiyanın tərkibinə təyin əmri əlavə etməklə modifikasiya edildikdə UnboundLocalError
aldığında təəccüblənir.
Bu xüsusiyyət xüsusilə siyahılardan istifadə edərkən inkişaf etdiriciləri çaşdırır. Aşağıdakı nümunəyə baxaq:
lst = [1, 2, 3]
def foo1():
lst.append(5) # Bu normal işləyir...
foo1()
print(lst)
[1, 2, 3, 5]
lst = [1, 2, 3]
def foo2():
lst += [5] # ... amma bu çökür!
foo2()
Traceback (most recent call last):
File "
", line 1, in
File "
", line 2, in foo UnboundLocalError: local variable 'lst' referenced before assignment
Niyə foo2
çökür, amma foo1
normal işləyir?
Cavab əvvəlki nümunədəki ilə eynidir, lakin burada vəziyyət daha incədir. foo1
lst
-yə təyin əmri tətbiq etmir, amma foo2
edir. Yadda saxlayaraq ki, lst += [5]
əslində sadəcə lst = lst + [5]
üçün bir qısayoldur, görürük ki, lst
-yə bir dəyər təyin etməyə çalışırıq (buna görə Python bunu lokal sahədə qəbul edir). Amma lst
-yə təyin etmək istədiyimiz dəyər özü lst
-yə əsaslanır (yenə də, indi lokal sahə olduğu qəbul edilir), hansı ki, hələ təyin edilməyib. Və xəta alırıq.
8.2 Siyahının iterasiyası vaxtı onu dəyişmək
Aşağıdakı kod parçasındakı problem kifayət qədər aydın olmalıdır:
odd = lambda x: bool(x % 2)
numbers = [n for n in range(10)]
for i in range(len(numbers)):
if odd(numbers[i]):
del numbers[i] # PİS: Siyahının üzərində iterasiya edərkən elementin silinməsi
Traceback (most recent call last):
File "
", line 2, in
IndexError: list index out of range
Python-da siyahı və ya massivdən elementin iterasiya edilərkən silinməsi təcrübəli proqramçıların yaxşı tanıdığı bir problemdir. Amma yuxarıdakı misal kifayət qədər aydın görünsə də, təcrübəli proqramçılar belə daha mürəkkəb kodlarda belə səhvlərə yol verə bilərlər.
Xoşbəxtlikdən, Python-da düzgün istifadə edildikdə kodu əhəmiyyətli dərəcədə sadələşdirən və optimallaşdıran bir çox zərif proqramlaşdırma paradiqması mövcuddur. Bunun xoş təsiri odur ki, daha sadə kod yazmaqla iterasiya vaxtı siyahıdan təsadüfən element silmə səhvinə yol vermək ehtimalı azalır.
Bu paradiqmalardan biri siyahı generatorlarıdır. Siyahı generatorlarının iş prinsipini anlamaq bu konkret problemdən qaçmaq üçün xüsusilə faydalıdır, aşağıda göstərilən alternativ təsvirdə olduğu kimi, bu kod mükəmməl işləyir:
odd = lambda x: bool(x % 2)
numbers = [n for n in range(10)]
numbers[:] = [n for n in numbers if not odd(n)] # sadəcə yeni elementləri seçirik
print(numbers)
# [0, 2, 4, 6, 8]
Vacibdir!
Burada yeni siyahı obyektinin təyin edilməsi baş vermir. numbers[:]
istifadəsi siyahının bütün elementlərinə yeni dəyərlərin topluca mənimsədilməsidir.
8.3 Python-da dəyişənlərin bağlanmalarında necə əlaqələndirildiyini anlamamaq
Aşağıdakı nümunəyə baxaq:
def create_multipliers():
return [lambda x: i * x for i in range(5)] #Funksiyalar siyahısını qaytarır!
for multiplier in create_multipliers():
print(multiplier(2))
Aşağıdakı çıxışı gözləyə bilərsiniz:
0
2
4
6
8
Amma əslində aşağıdakı nəticəni alırsınız:
8
8
8
8
8
Sürpriz!
Bu gec bağlanma səbəbindən baş verir, yəni Python-da dəyişənin dəyərləri bağlanmalarda istifadə edildikdə yalnız daxili funksiyanın çağırıldığı an axtarılır.
Beləliklə, yuxarıdakı kodda hər dəfə geri qaytarılmış funksiyalardan biri çağırıldıqda, i
-nin dəyəri çağırış zamanı ətrafdakı görünürlükdə axtarılır (və o vaxta qədər dövr artıq tamamlanıb, ona görə də i
artıq son nəticəyə, yəni 4 dəyərinə təyin olunub).
Bu, Python-da yayılmış problemi həll etmək üçün həll belə olacaq:
def create_multipliers():
return [lambda x, i = i : i * x for i in range(5)]
for multiplier in create_multipliers():
print(multiplier(2))
# 0 # 2 # 4 # 6 # 8
Vualya! Burada axtarılan davranışı əldə etmək üçün anonim funksiyaların yaradılmasında default argumentlərdən istifadə edirik. Bunu bəziləri zərif həll, bəziləri incəlik, bəziləri isə nifrət ediləsi şey adlandırır. Amma Python proqramçısı olsanız, bu vacib bir məsələdir.
GO TO FULL VERSION