Come faccio a scaricare (ricaricare) un modulo?

Ho un server Python in funzione da molto tempo e vorrei essere in grado di aggiornare un servizio senza riavviare il server. Qual è il modo migliore per farlo?

if foo.py has changed:
    unimport foo  <-- How do I do this?
    import foo
    myfoo = foo.Foo()
Soluzione

Puoi ricaricare un modulo quando è già stato importato usando la funzione integrata reload:

from importlib import reload  # Python 3.4+ only.
import foo

while True:
    # Do some things.
    if is_changed(foo):
        foo = reload(foo)

In Python 3, reload è stato spostato nel modulo imp. In 3.4, imp è stato deprecato in favore di importlib, e reload è stato aggiunto a quest'ultimo. Quando si punta alla 3 o alle versioni successive, o si fa riferimento al modulo appropriato quando si chiama reload o lo si importa.

Penso che questo sia ciò che vuoi. I server web come il server di sviluppo di Django lo usano in modo da poter vedere gli effetti delle tue modifiche al codice senza riavviare il processo del server stesso.

Per citare dalla documentazione:

Il codice dei moduli Python viene ricompilato e il codice a livello di modulo viene rieseguito, definendo un nuovo insieme di oggetti che sono legati a nomi nel dizionario del modulo del modulo. La funzione init di moduli di estensione non viene chiamata una seconda volta. Come per tutti gli altri oggetti in Python i vecchi oggetti sono solo recuperati solo dopo che il loro numero di riferimenti scendono a zero. I nomi nel modulo namespace sono aggiornati per puntare a qualsiasi oggetti nuovi o modificati. Altri riferimenti ai vecchi oggetti (come nomi esterni al modulo) non sono rimbalzati per riferirsi ai nuovi oggetti e devono essere aggiornati in ogni namespace dove si verificano, se questo è desiderato.

Come hai notato nella tua domanda, dovrai ricostruire gli oggetti Foo se la classe Foo risiede nel modulo foo.

Commentari (17)

Può essere particolarmente difficile cancellare un modulo se non è Python puro.

Ecco alcune informazioni tratte da: Come faccio a cancellare veramente un modulo importato?

Potete usare sys.getrefcount() per scoprire il numero effettivo di riferimenti.

>>> import sys, empty, os
>>> sys.getrefcount(sys)
9
>>> sys.getrefcount(os)
6
>>> sys.getrefcount(empty)
3

I numeri maggiori di 3 indicano che sarà difficile sbarazzarsi del modulo. L'homegrown "empty" (che non contiene nulla) modulo dovrebbe essere garbage collected dopo

>>> del sys.modules["empty"]
>>> del empty

come il terzo riferimento è un artefatto della funzione getrefcount().

Commentari (2)

reload(module), ma solo se è completamente indipendente. Se qualcos'altro ha un riferimento al modulo (o qualsiasi oggetto appartenente al modulo), allora otterrete errori sottili e curiosi causati dal vecchio codice che rimane in giro più a lungo di quanto vi aspettavate, e cose come isinstance non funzionano tra diverse versioni dello stesso codice.

Se hai dipendenze unidirezionali, devi anche ricaricare tutti i moduli che dipendono dal modulo ricaricato per liberarti di tutti i riferimenti al vecchio codice. E poi ricaricare i moduli che dipendono dai moduli ricaricati, ricorsivamente.

Se avete dipendenze circolari, che è molto comune per esempio quando si tratta di ricaricare un pacchetto, dovete scaricare tutti i moduli del gruppo in una volta sola. Non puoi farlo con reload() perché reimporterà ogni modulo prima che le sue dipendenze siano state aggiornate, permettendo ai vecchi riferimenti di insinuarsi nei nuovi moduli.

L'unico modo per farlo in questo caso è quello di hackerare sys.modules, che non è supportato. Dovresti passare attraverso e cancellare ogni voce di sys.modules che vuoi che sia ricaricata alla prossima importazione, e anche cancellare le voci i cui valori sono None per affrontare un problema di implementazione che ha a che fare con il caching delle importazioni relative fallite. Non è terribilmente bello, ma finché si ha un insieme di dipendenze completamente autonomo che non lascia riferimenti al di fuori della sua base di codice, è fattibile.

Probabilmente è meglio riavviare il server.)

Commentari (7)