7.1 Default dəyərlər üçün dəyişdirilə bilən ifadələrin düzgün istifadə edilməməsi
Artıq çox şey öyrənmisiniz, gəlin başlanğıcda edilən ən klassik səhvləri araşdıraq – başqalarının səhvlərindən öyrənmək öz səhvlərinizdən öyrənməkdən daha yaxşıdır, elə deyilmi?
Python funksiyalarda Default dəyərləri təyin edərək seçimlik arqumentlərin yaradılmasına icazə verir. Bu, əlbəttə ki, dilin çox əla xüsusiyyətidir, amma həmin dəyərin tipi dəyişdirilə bilirsə, xoşagəlməz nəticələrə səbəb ola bilər. Məsələn, aşağıdakı funksiya təyinini nəzərdən keçirək:
def foo(bar=[]): # bar - seçimlik arqumentdir
# və default olaraq boş siyahıya bərabərdir.
bar.append("baz") # bu sətir problem yarada bilər...
return bar
Buradakı yayılmış səhv belə düşünməkdir ki, seçimlik arqumentin dəyəri hər dəfə funksiya həmin arqumentsiz çağırıldıqda default dəyərinə yenilənəcək.
Yuxarıdakı kodda, məsələn, foo()
funksiyasını yenidən çağıraraq (yəni bar
üçün dəyər göstərilmədən), hər zaman ["baz"]
qaytaracağını düşünmək olar, çünki hər dəfə foo()
çağırıldıqda (arqumentsiz), bar
[]
olaraq (yeni bir boş siyahı) qurulacağı güman edilir.
Amma gəlin, həqiqətən nə baş verdiyinə nəzər yetirək:
result = foo()
print(result) # ["baz"]
result = foo()
print(result) # ["baz", "baz"]
result = foo()
print(result) # ["baz", "baz", "baz"]
Niyə funksiya hər dəfə foo()
çağırıldığında mövcud siyahıya baz
dəyərini əlavə etməyə davam edir, əvəzində isə hər dəfə yeni siyahı yaratmır?
Bu suala cavab Python-un "qapağı altında" nə baş verdiyini daha dərindən anlamaqda gizlidir. Konkret olaraq: Funksiyalar üçün Default dəyərlər yalnız bir dəfə, funksiyanın təyin edildiyi zamana görə başlanılır.
Bu səbəbdən, bar
arqumenti ilk dəfə foo()
təyin edildikdə default olaraq (yəni boş siyahı kimi) başlanılır, lakin növbəti foo()
çağırışları (yəni bar
arqumentsiz) həmin siyahını istifadə edir ki, bu siyahı ilk dəfə funksiyanın təyinində yaradılmışdır.
Bu səhvdən qaçmaq üçün ümumi olaraq istifadə edilən "workaround" belədir:
def foo(bar=None):
if bar is None:
bar = []
bar.append("baz")
return bar
result = foo()
print(result) # ["baz"]
result = foo()
print(result) # ["baz"]
result = foo()
print(result) # ["baz"]
7.2 Sinif dəyişənlərinin səhv istifadəsi
Gəlin aşağıdakı nümunəni nəzərdən keçirək:
class A(object):
x = 1
class B(A):
pass
class C(A):
pass
print(A.x, B.x, C.x)
# 1 1 1
Elə bil hər şey qaydasındadır.
İndi isə B
sinfinin x
sahəsinə dəyər təyin edək:
B.x = 2
print(A.x, B.x, C.x)
# 1 2 1
Hər şey gözlədiyimiz kimi.
İndi isə A
sinfinin dəyişənini dəyişək:
A.x = 3
print(A.x, B.x, C.x)
# 3 2 3
Qəribədir, biz sadəcə A.x
dəyişdik. Bəs niyə C.x
da dəyişdi?
Python-da sinif dəyişənləri sözlüklər (dictionary) kimi işlənir və tez-tez Metodların Həlli Qaydası (MRO) adlandırılan mexanizmə tabedir. Yuxarıdakı kodda isə, çünki C
sinfində x
atributu tapılmadığı üçün o, onun əsas siniflərində (yuxarıdakı nümunədə yalnız A
, baxmayaraq ki, Python çoxsaylı mirası dəstəkləyir) tapılacaq.
Başqa sözlə, C
-nin özünə məxsus, A
-dan asılı olmayan x
xüsusiyyəti yoxdur. Beləliklə, C.x
-ə istinadlar əslində A.x
-ə istinad edir. Əgər bu cür halları düzgün nəzərə almasanız, bu problemlər yarada bilər. Python-u öyrənərkən sinif atributlarına və onlarla işləməyə xüsusi diqqət yetirin.
7.3 İstisna bloku üçün parametrlərin səhv göstərilməsi
Təsəvvür edin ki, aşağıdakı kimi bir kodunuz var:
try:
l = ["a", "b"]
int(l[2])
except ValueError, IndexError: # Hər iki istisnanı tutsun deyə, düzdür?
pass
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
IndexError: list index out of range
Buradakı problem except
ifadəsinin bu şəkildə göstərilmiş istisna siyahısını qəbul etməməsidir. Nəticədə yuxarıdakı kodda IndexError
istisnası except
ifadəsi tərəfindən tutulmur; bunun əvəzinə istisna IndexError
adı ilə olan parametrə bağlanaraq tamamlanır.
Vacib!
İnternetdə nümunələrdə belə kodlara rast gələ bilərsiniz, çünki bu Python 2.x-də istifadə edilirdi.
Bir neçə istisnanı except
ifadəsi ilə düzgün şəkildə tutmaq üçün birinci parametri tutulacaq bütün istisnaları ehtiva edən tuple şəklində göstərmək lazımdır. Bundan əlavə, daha yaxşı oxunaqlıq üçün as
açar sözündən istifadə edə bilərsiniz, məsələn, bu şəkildə:
try:
l = ["a", "b"]
int(l[2])
except (ValueError, IndexError) as e:
pass
GO TO FULL VERSION