CodeGym /Kursy /Python SELF PL /Błędy standardowe, część 3

Błędy standardowe, część 3

Python SELF PL
Poziom 20 , Lekcja 3
Dostępny

9.1 Tworzenie cyklicznych zależności modułów

Załóżmy, że masz dwa pliki, a.py i b.py, każdy z nich importuje drugi w następujący sposób:

W a.py:


import b

def f():
    return b.x

print(f())

W b.py:


import a

x = 1

def g():
    print(a.f())

Najpierw spróbujemy zaimportować a.py:


import a
# 1

Działa świetnie. Może to Cię zaskakuje. W końcu, moduły importują się nawzajem cyklicznie, co prawdopodobnie powinno być problemem, prawda?

Odpowiedź brzmi tak, że samo posiadanie cyklicznego importu modułów nie jest problemem w Pythonie. Jeśli moduł został już zaimportowany, Python jest na tyle inteligentny, że nie próbuje go ponownie importować. Jednak, w zależności od tego, w którym momencie każdy moduł próbuje uzyskać dostęp do funkcji lub zmiennych zdefiniowanych w innym, możesz napotkać problemy.

Wracając do naszego przykładu, kiedy zaimportowaliśmy a.py, nie miał on problemu z importem b.py, ponieważ b.py nie wymaga, aby cokolwiek z a.py było zdefiniowane podczas jego importu. Jedynym odwołaniem w b.py do a jest wywołanie a.f(). Ale to wywołanie jest w g(), i nic w a.py czy b.py nie wywołuje g(). Wszystko więc działa doskonale.

Ale co się stanie, jeśli spróbujemy zaimportować b.py (bez wcześniejszego importu a.py, to jest):


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' 
     
    
  

Problem polega na tym, że w trakcie importu b.py próbuje on zaimportować a.py, który z kolei wywołuje f(), który próbuje uzyskać dostęp do b.x. Ale b.x nie zostało jeszcze zdefiniowane. Stąd wyjątek AttributeError.

Przynajmniej jedno z rozwiązań tego problemu jest dość trywialne. Po prostu zmień b.py, aby importował a.py w g():


x = 1

def g():
    import a  # To będzie wykonane tylko wtedy, gdy g() zostanie wywołane
    print(a.f())

Teraz, gdy to importujemy, wszystko jest w porządku:


import b
b.g()
# 1   Drukowane pierwszy raz, ponieważ moduł 'a' wywołuje 'print(f())' na końcu
# 1   Drukowane drugi raz, to jest nasze wywołanie 'g()'

9.2 Krzyżowanie nazw z nazwami modułów standardowej biblioteki Pythona

Jedną z zalet Pythona jest mnogość modułów, które są dostarczane „z pudełka”. Ale w rezultacie, jeśli nie będziesz tego świadomie unikał, możesz napotkać na problem, że nazwa Twojego modułu może pokrywać się z nazwą modułu ze standardowej biblioteki dostarczanej z Pythonem (na przykład w Twoim kodzie może być moduł o nazwie email.py, który będzie konfliktował z modułem standardowej biblioteki o tej samej nazwie).

To może prowadzić do poważnych problemów. Na przykład, jeśli którykolwiek z modułów będzie próbował zaimportować wersję modułu ze standardowej biblioteki Pythona, a w Twoim projekcie będzie moduł o tej samej nazwie, to przez pomyłkę zaimportuje Twój moduł zamiast modułu ze standardowej biblioteki.

Dlatego należy być ostrożnym, aby nie używać tych samych nazw, co w modułach standardowej biblioteki Pythona. Znacznie łatwiej jest zmienić nazwę modułu w swoim projekcie, niż składać wniosek o zmianę nazwy modułu w standardowej bibliotece i czekać na jego zatwierdzenie.

9.3 Widoczność wyjątków

Rozważmy następujący plik main.py:


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('błąd klucza')
    except ValueError as e:
        print('błąd wartości')
    print(e)

bad()

Wszystko wydaje się być w porządku, kod powinien działać, zobaczmy, co nam pokaże:


$ python main.py 1
Traceback (most recent call last):
    File "C:\Projects\Python\TinderBolt\main.py", line 19, in <module>
        bad()
    File "C:\Projects\Python\TinderBolt\main.py", line 17, in bad
        print(e)
          ^
UnboundLocalError: cannot access local variable 'e' where it is not associated with a value

Co się właśnie tutaj stało? Problem polega na tym, że w Pythonie obiekt w bloku wyjątku nie jest dostępny poza jego granicami. (Powodem tego jest to, że w przeciwnym razie obiekty w tym bloku byłyby przechowywane w pamięci aż do momentu, gdy zbieracz śmieci uruchomi się i usunie odwołania do nich).

Jednym ze sposobów uniknięcia tego problemu jest przechowanie odwołania do obiektu bloku wyjątku poza tym blokiem, aby pozostał on dostępny. Oto wersja poprzedniego przykładu, która wykorzystuje tę technikę, dzięki czemu kod działa:


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('błąd klucza')
    except ValueError as e:
        exception = e
        print('błąd wartości')
    print(exception)
            
good()

9.4 Niewłaściwe użycie metody __del__

Kiedy interpreter usuwa obiekt, sprawdza, czy ten obiekt ma funkcję __del__, a jeśli tak, to ją wywołuje przed usunięciem obiektu. To bardzo wygodne, kiedy chcesz, aby Twój obiekt posprzątał za sobą jakieś zewnętrzne zasoby czy cache.

Załóżmy, że masz taki plik mod.py:


import foo

class Bar(object):
        ...

def __del__(self):
    foo.cleanup(self.myhandle)

I próbujesz zrobić coś takiego z innego pliku another_mod.py:


import mod
mybar = mod.Bar()

I dostajesz straszny AttributeError.

Dlaczego? Ponieważ, jak podano tutaj, kiedy interpreter kończy działanie, wszystkie zmienne globalne modułu mają wartość None. W rezultacie w powyższym przykładzie, w momencie wywołania __del__, nazwa foo została już ustawiona na None.

Rozwiązaniem tego „zadania z gwiazdką” będzie użycie specjalnej metody atexit.register(). W ten sposób, kiedy Twój program kończy działanie (to znaczy przy normalnym wyjściu z niego), Twoje handle'y są usuwane zanim interpreter skończy działanie.

Po uwzględnieniu tego poprawka dla powyższego kodu mod.py może wyglądać mniej więcej tak:


import foo
import atexit
        
def cleanup(handle):
    foo.cleanup(handle)
        
class Bar(object):
    def __init__(self):
      ...

atexit.register(cleanup, self.myhandle)

Taka implementacja zapewnia prosty i niezawodny sposób wywołania jakiejkolwiek koniecznej czyszczenia po normalnym zakończeniu programu. Oczywiście decyzja o tym, co zrobić z obiektem związanym z nazwą self.myhandle, należy do foo.cleanup, ale myślę, że rozumiecie ideę.

1
Опрос
Iteratory,  20 уровень,  3 лекция
недоступен
Iteratory
Iteratory
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION