9.1 Modulda tsiklik asılılıqların yaradılması
Tutaq ki, sizdə iki fayl var, a.py
və b.py
, bunların hər biri digərini aşağıdakı kimi import edir:
a.py
də:
import b
def f():
return b.x
print(f())
b.py
də:
import a
x = 1
def g():
print(a.f())
Əvvəl a.py
-ni import etməyə çalışaq:
import a
# 1
Sadəcə əla işləyir. Bu sizi təəccübləndirə bilər. Axı, modullar bir-birini tsiklik olaraq import edir və bu, ehtimal ki, problem olmalıdır, elə deyilmi?
Cavab budur ki, modullarda tsiklik import məsələsi Python-da öz-özlüyündə problem deyil. Əgər modul artıq import olunubsa, Python kifayət qədər ağıllıdır ki, onu yenidən import etməyə çalışmasın. Amma modulların digərindən funksiyalara və ya dəyişənlərə nə vaxt müraciət etməyə çalışmasına görə siz həqiqətən problemlərlə qarşılaşa bilərsiniz.
Beləliklə, nümunəmizə qayıdaraq, biz a.py
-ni import etdikdə, o, b.py
-ni import edərkən heç bir problem yaşamırdı, çünki b.py
-nin a.py
-dən bir şeyin müəyyən olunmasını tələb etmirdi. b.py
-də a
-ya olan yeganə istinad a.f()
çağırışıdır. Amma bu çağırış g()
-dədir və nə a.py
, nə də b.py
g()
-ni çağırmır. Buna görə hər şey yaxşı işləyir.
Amma nə baş verər ki, əgər biz b.py
-ni (öncədən a.py
-ni import etmədən) import etməyə çalışsaq:
import b
Traceback (most recent call last):
File "<stdin>", line 1, in
File "b.py", line 1, in
import a File "a.py", line 6, in
print(f()) File "a.py", line 4, in f return b.x AttributeError: 'module' object has no attribute 'x'
Burada problem odur ki, b.py
-ni import etmək prosesində o, a.py
-ni import etməyə çalışır, hansı ki, növbəsində, f()
-ni çağırır, o isə b.x
-yə müraciət etməyə çalışır. Amma b.x
hələ müəyyən olunmayıb. Buna görə də AttributeError
istisnası yaranır.
Bu problemin ən az bir həlli olduqca sadədir. Sadəcə b.py
-ni dəyişin ki, a.py
-ni g()
-də import etsin:
x = 1
def g():
import a # Bu yalnız g() çağırıldığında qiymətləndiriləcək
print(a.f())
İndi onu import etdiyimiz zaman hər şey yaxşıdır:
import b
b.g()
# 1 Modul 'a' 'print(f())' çağırdığı üçün birinci dəfə çap olunub
# 1 İkinci dəfə çap olunur, bu bizim 'g()'yə çağırışımızdır
9.2 Standard kitabxanadakı modulların adları ilə üst-üstə düşən adlar
Python’un gözəlliklərindən biri - çoxlu sayda modulun “qutudan çıxar-çıxmaz” təmin edilməsi. Amma nəticədə, əgər buna diqqət etməsəniz, modulunuzun adı Python-un təmin etdiyi standart kitabxananın modullarından birinin adı ilə üst-üstə düşə bilər (məsələn, sizin kodunuzda email.py
adlı modul ola bilər, bu isə standart kitabxananın eyni adlı modulu ilə konfliktə girər).
Bu ciddi problemlərə yol aça bilər. Məsələn, əgər hansısa modul standart Python kitabxanasının versiyasını import etməyə çalışırsa, amma sizin layihənizdə eyni adlı modul varsa, həmin modul səhvən sizin modulunuza istinad edəcək, standart kitabxananın moduluna yox.
Ona görə də Python-un standart kitabxanasındakı modulların adları ilə eyni adlardan istifadə etməməkdə diqqətli olmaq lazımdır. Layihənizdə modulun adını dəyişmək standart kitabxanada modul adının dəyişdirilməsi üçün sorğu göndərmək və onun təsdiq olunmasını gözləməkdən çox daha asandır.
9.3 İstisnaların görünürlüyü
Gəlin aşağıdakı main.py
faylını nəzərdən keçirək:
import sys
def bar(i):
if i == 1:
raise KeyError(1)
if i == 2:
raise ValueError(2)
def bad():
e = None
try:
bar(int("1"))
except KeyError as e:
print('açar səhvi')
except ValueError as e:
print('dəyər səhvi')
print(e)
bad()
Hər şey düz görünür, kod işləməlidir, gəlin onun bizə nə qaytaracağını yoxlayaq:
$ python main.py 1
Traceback (most recent call last):
Fayl "C:\Projects\Python\TinderBolt\main.py", sətir 19, in <module>
bad()
Fayl "C:\Projects\Python\TinderBolt\main.py", sətir 17, in bad
print(e)
^
UnboundLocalError: 'e' adlı lokal dəyişəndən istifadə etmək mümkün deyil, çünki ona heç bir dəyər verilməyib
Burada nə baş verdi? Problem ondadır ki, Python-da istisna bloku daxilində olan obyekt həmin blokun xaricində əlçatan deyil. (Bu, ona görədir ki, əks halda həmin blokda olan obyektlər yaddaşdan silinməyəcək və zibil toplayıcısı tərəfindən silinməyənə qədər saxlanılacaq).
Bu problemin qarşısını almağın bir yolu — istisna blokunun obyektinə blok xaricində də istinad etməkdir ki, bu obyekt əlçatan qala bilsin. Budur əvvəlki nümunənin bu texnikadan istifadə edən və işlək olan versiyası:
import sys
def bar(i):
if i == 1:
raise KeyError(1)
if i == 2:
raise ValueError(2)
def good():
exception = None
try:
bar(int("1"))
except KeyError as e:
exception = e
print('açar səhvi')
except ValueError as e:
exception = e
print('dəyər səhvi')
print(exception)
good()
9.4 __del__
metodu ilə yanlış istifadə
Interpreter bir obyekt silərkən, yoxlayır — bu obyektdə __del__
funksiyası varmı və əgər varsa, obyekti silməzdən əvvəl onu çağırır. Bu çox rahatdır, əgər siz obyektinizin xarici resursları və ya keşi təmizləməsini istəyirsinizsə.
Tutaq ki, sizdə belə bir mod.py
faylı var:
import foo
class Bar(object):
...
def __del__(self):
foo.cleanup(self.myhandle)
Və siz başqa bir another_mod.py
faylından bunu etmək istəyirsiniz:
import mod
mybar = mod.Bar()
Və dəhşətli bir AttributeError
alırsınız.
Niyə? Çünki, burda deyildiyi kimi burada, interpreter işini tamamlayanda, modulun bütün qlobal dəyişənlərinin dəyəri None
olur. Nəticədə, yuxarıda göstərilən misalda, __del__
çağrıldıqda foo
adı artıq None
səviyyəsinə gətirilmiş olar.
Bu "ulduzlu məsələnin" həlli üçün xüsusi bir metod olan atexit.register()
istifadə edilə bilər. Beləliklə, proqramınızın işi tamamlandıqda (yəni normal bir çıxış zamanı), sizin handle'larınız
interpreter tamamlamadan əvvəl silinir.
Bu nəzərə alınaraq, yuxarıda göstərilən kod mod.py
üçün düzəliş təxminən belə ola bilər:
import foo
import atexit
def cleanup(handle):
foo.cleanup(handle)
class Bar(object):
def __init__(self):
...
atexit.register(cleanup, self.myhandle)
Bu cür reallaşdırma, proqramın normal tamamlama prosesindən sonra hər hansı lazım olan təmizləmənin çağırılması üçün sadə və etibarlı bir üsul təmin edir. Aydındır ki, self.myhandle
adı ilə bağlı obyektlə necə davranacağına foo.cleanup
qərar verir, amma düşünürəm ki, ideyanı anladınız.
GO TO FULL VERSION