CodeGym /Corsi /Python SELF IT /Errori standard, parte 3

Errori standard, parte 3

Python SELF IT
Livello 20 , Lezione 3
Disponibile

9.1 Creazione di dipendenze cicliche tra moduli

Supponiamo che tu abbia due file, a.py e b.py, ciascuno dei quali importa l'altro nel seguente modo:

In a.py:


import b

def f():
    return b.x

print(f())

In b.py:


import a

x = 1

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

Proviamo prima a importare a.py:


import a
# 1

Funziona perfettamente. Forse ti sorprende. Dopotutto, i moduli si importano ciclicamente l'un l'altro, e questo probabilmente dovrebbe essere un problema, giusto?

La risposta è che il semplice fatto di avere un'importazione ciclica tra moduli non è di per sé un problema in Python. Se un modulo è già stato importato, Python è abbastanza intelligente da non tentare di importarlo nuovamente. Tuttavia, a seconda del momento in cui ciascun modulo tenta di accedere a funzioni o variabili definite nell'altro, potresti effettivamente incorrere in problemi.

Quindi, tornando al nostro esempio, quando abbiamo importato a.py, non ha avuto problemi a importare b.py, poiché b.py non richiede che nulla di a.py sia definito al momento del suo import. L'unico riferimento in b.py a a è la chiamata a a.f(). Ma questo è all'interno di g(), e niente in a.py o b.py chiama g(). Quindi tutto funziona perfettamente.

Ma cosa succede se proviamo a importare b.py (senza un precedente import di a.py, cioè):


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' 
     
    
  

Il problema qui è che nel processo di importazione di b.py si tenta di importare a.py, che a sua volta chiama f(), che tenta di accedere a b.x. Ma b.x non è ancora stato definito. Da qui l'eccezione AttributeError.

Almeno una soluzione a questo problema è piuttosto banale. Basta modificare b.py per importare a.py all'interno di g():


x = 1

def g():
    import a  # Questo verrà valutato solo quando g() viene chiamato
    print(a.f())

Ora, quando lo importiamo, tutto va bene:


import b
b.g()
# 1   Stampato la prima volta poiché il modulo 'a' chiama 'print(f())' alla fine
# 1   Stampato la seconda volta, questa è la nostra chiamata a 'g()'

9.2 Conflitto di nomi con i nomi dei moduli della libreria standard di Python

Una delle bellezze di Python è l'ampia gamma di moduli che vengono forniti "out of the box". Ma di conseguenza, se non stai attento, potresti trovarti nella situazione in cui il nome del tuo modulo coincide con il nome di un modulo della libreria standard fornita con Python (ad esempio, potresti avere un modulo nel tuo codice chiamato email.py, che entrerà in conflitto con il modulo della libreria standard con lo stesso nome).

Questo può portare a gravi problemi. Ad esempio, se un modulo tenta di importare la versione del modulo dalla libreria standard di Python, e tu hai un modulo con lo stesso nome nel tuo progetto, per errore importerà il tuo modulo invece del modulo della libreria standard.

Quindi è necessario prestare attenzione a non utilizzare gli stessi nomi dei moduli della libreria standard di Python. È molto più semplice cambiare il nome del modulo nel tuo progetto piuttosto che presentare una richiesta di modifica del nome del modulo nella libreria standard e attendere la sua approvazione.

9.3 Visibilità delle eccezioni

Consideriamo il seguente file 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('errore di chiave')
    except ValueError as e:
        print('errore di valore')
    print(e)

bad()

Sembra tutto corretto, il codice dovrebbe funzionare, vediamo cosa ci restituisce:


$ 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

Cosa è appena successo qui? Il problema è che in Python l'oggetto nel blocco delle eccezioni non è disponibile al di fuori di esso. (Il motivo è che altrimenti gli oggetti in quel blocco rimarrebbero in memoria fino a quando il garbage collector non si avvia e non rimuove i riferimenti).

Un modo per evitare questo problema è mantenere un riferimento all'oggetto del blocco delle eccezioni al di fuori di quel blocco, in modo che rimanga accessibile. Ecco una versione del precedente esempio che utilizza questa tecnica, rendendo così funzionante il codice:


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('errore di chiave')
    except ValueError as e:
        exception = e
        print('errore di valore')
    print(exception)
            
good()

9.4 Uso improprio del metodo __del__

Quando l'interprete elimina un oggetto, controlla se quell'oggetto ha una funzione __del__, e se sì, la chiama prima di eliminare l'oggetto. Questo è molto utile quando vuoi che il tuo oggetto pulisca risorse esterne o cache.

Supponiamo che tu abbia un file mod.py del genere:


import foo

class Bar(object):
        ...

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

E stai cercando di fare qualcosa del genere da un altro file another_mod.py:


import mod
mybar = mod.Bar()

E ricevi un terribile AttributeError.

Perché? Perché, come riportato qui, quando l'interprete termina, tutte le variabili globali del modulo hanno il valore None. Di conseguenza, nell'esempio sopra, al momento della chiamata di __del__, il nome foo era già stato impostato su None.

La soluzione a questa "questione con l'asterisco" è l'uso del metodo speciale atexit.register(). In questo modo, quando il tuo programma termina (cioè all'uscita normale), i tuoi handle vengono eliminati prima che l'interprete finisca.

Con questo in mente, la correzione per il codice mod.py sopra potrebbe apparire così:


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

atexit.register(cleanup, self.myhandle)

Questa implementazione fornisce un modo semplice e affidabile per effettuare qualsiasi pulizia necessaria dopo la normale chiusura del programma. Ovviamente, la decisione su come procedere con l'oggetto collegato al nome self.myhandle spetta a foo.cleanup, ma penso che tu abbia capito l'idea.

1
Sondaggio/quiz
Iteratori, livello 20, lezione 3
Non disponibile
Iteratori
Iteratori
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION