Le dichiarazioni di importazione devono essere sempre nella parte superiore di un modulo?

StackOverflow https://stackoverflow.com/questions/128478

Domanda

PEP 08 afferma:

  

Le importazioni vengono sempre inserite nella parte superiore del file, subito dopo i commenti e le istruzioni del modulo e prima dei globuli e delle costanti del modulo.

Tuttavia, se la classe / metodo / funzione che sto importando viene utilizzata solo in rari casi, sicuramente è più efficiente eseguire l'importazione quando è necessario?

Non è questo:

class SomeClass(object):

    def not_often_called(self)
        from datetime import datetime
        self.datetime = datetime.now()

più efficiente di così?

from datetime import datetime

class SomeClass(object):

    def not_often_called(self)
        self.datetime = datetime.now()
È stato utile?

Soluzione

L'importazione dei moduli è piuttosto veloce, ma non istantanea. Ciò significa che:

  • Mettere le importazioni nella parte superiore del modulo va bene, perché è un costo banale che viene pagato solo una volta.
  • Inserendo le importazioni all'interno di una funzione, le chiamate a tale funzione impiegheranno più tempo.

Quindi, se ti interessa l'efficienza, metti le importazioni in cima. Spostali in una funzione solo se il tuo profilo mostra che sarebbe di aiuto (hai fatto il profilo per vedere dove migliorare al meglio le prestazioni, giusto ??)


I migliori motivi che ho visto per eseguire importazioni pigre sono:

  • Supporto libreria opzionale. Se il tuo codice ha più percorsi che utilizzano librerie diverse, non interrompere se non è installata una libreria opzionale.
  • Nel __init__.py di un plugin, che potrebbe essere importato ma non effettivamente utilizzato. Esempi sono i plugin di Bazaar, che utilizzano il framework di caricamento lento di bzrlib .

Altri suggerimenti

L'inserimento dell'istruzione import all'interno di una funzione può impedire dipendenze circolari. Ad esempio, se si dispone di 2 moduli, X.py e Y.py, ed entrambi devono importarsi a vicenda, ciò causerà una dipendenza circolare quando si importa uno dei moduli causando un ciclo infinito. Se si sposta l'istruzione import in uno dei moduli, non tenterà di importare l'altro modulo fino a quando non viene chiamata la funzione e quel modulo verrà già importato, quindi nessun ciclo infinito. Leggi qui per ulteriori informazioni - effbot.org/zone/import-confusion.htm

Ho adottato la pratica di inserire tutte le importazioni nelle funzioni che le utilizzano, piuttosto che nella parte superiore del modulo.

Il vantaggio che ottengo è la capacità di riformattare in modo più affidabile. Quando sposto una funzione da un modulo a un altro, so che la funzione continuerà a funzionare con tutto il suo retaggio di test intatto. Se ho le mie importazioni nella parte superiore del modulo, quando sposto una funzione, trovo che finisco per passare molto tempo a ottenere le importazioni del nuovo modulo complete e minime. Un IDE refactoring potrebbe renderlo irrilevante.

C'è una penalità di velocità come menzionato altrove. L'ho misurato nella mia domanda e l'ho trovato insignificante per i miei scopi.

È anche bello poter vedere tutte le dipendenze dei moduli in anticipo senza ricorrere alla ricerca (ad es. grep). Tuttavia, il motivo per cui mi preoccupo delle dipendenze del modulo è generalmente perché sto installando, refactoring o spostando un intero sistema comprendente più file, non solo un singolo modulo. In tal caso, eseguirò comunque una ricerca globale per assicurarmi di avere dipendenze a livello di sistema. Quindi non ho trovato importazioni globali per aiutare la mia comprensione pratica di un sistema.

Di solito inserisco l'importazione di sys all'interno del se __name __ == '__ main__' controlla e poi passo gli argomenti (come sys.argv [1:] ) in una funzione main () . Questo mi permette di usare main in un contesto in cui sys non è stato importato.

Il più delle volte sarebbe utile per chiarezza e ragionevolezza, ma non è sempre così. Di seguito sono riportati alcuni esempi di circostanze in cui le importazioni dei moduli potrebbero risiedere altrove.

In primo luogo, potresti avere un modulo con un test unitario del modulo:

if __name__ == '__main__':
    import foo
    aa = foo.xyz()         # initiate something for the test

In secondo luogo, potrebbe essere necessario importare in modo condizionale alcuni moduli diversi in fase di esecuzione.

if [condition]:
    import foo as plugin_api
else:
    import bar as plugin_api
xx = plugin_api.Plugin()
[...]

Probabilmente ci sono altre situazioni in cui è possibile inserire importazioni in altre parti nel codice.

La prima variante è effettivamente più efficiente della seconda quando la funzione viene chiamata zero o una volta. Con la seconda e le successive invocazioni, tuttavia, l '"importazione di ogni chiamata" l'approccio è in realtà meno efficiente. Vedi questo link per una tecnica di caricamento lento che combina entrambi i vantaggi di entrambi i link si avvicina eseguendo un '"importazione lenta".

Ma ci sono ragioni diverse dall'efficienza per cui potresti preferire l'una all'altra. Un approccio è quello di rendere molto più chiaro a qualcuno che legge il codice le dipendenze che questo modulo ha. Hanno anche caratteristiche di errore molto diverse: il primo fallirà al momento del caricamento se non c'è "datetime" mentre il secondo non fallirà fino a quando non viene chiamato il metodo.

Nota aggiunta: in IronPython, le importazioni possono essere un po 'più costose rispetto a CPython perché il codice viene sostanzialmente compilato mentre viene importato.

Curt ha un buon punto: la seconda versione è più chiara e fallirà al momento del caricamento piuttosto che in seguito, e inaspettatamente.

Normalmente non mi preoccupo dell'efficienza del caricamento dei moduli, poiché è (a) piuttosto veloce e (b) si verifica principalmente all'avvio.

Se devi caricare moduli pesanti in momenti inaspettati, probabilmente ha più senso caricarli dinamicamente con la funzione __import__ ed essere sicuro per catturare ImportError e gestirle in modo ragionevole.

Non mi preoccuperei dell'efficienza di caricare troppo il modulo in anticipo. La memoria occupata dal modulo non sarà molto grande (supponendo che sia abbastanza modulare) e il costo di avvio sarà trascurabile.

Nella maggior parte dei casi si desidera caricare i moduli nella parte superiore del file di origine. Per qualcuno che legge il tuo codice, è molto più facile dire quale funzione o oggetto proviene da quale modulo.

Un buon motivo per importare un modulo altrove nel codice è se viene utilizzato in un'istruzione di debug.

Ad esempio:

do_something_with_x(x)

Potrei eseguire il debug di questo con:

from pprint import pprint
pprint(x)
do_something_with_x(x)

Naturalmente, l'altro motivo per importare moduli altrove nel codice è se è necessario importarli dinamicamente. Questo perché praticamente non hai scelta.

Non mi preoccuperei dell'efficienza di caricare troppo il modulo in anticipo. La memoria occupata dal modulo non sarà molto grande (supponendo che sia abbastanza modulare) e il costo di avvio sarà trascurabile.

È un compromesso, che solo il programmatore può decidere di fare.

Il caso 1 consente di risparmiare un po 'di memoria e tempo di avvio non importando il modulo datetime (e facendo qualsiasi inizializzazione potrebbe richiedere) fino a quando necessario. Notare che fare l'importazione 'solo quando chiamato' significa anche farlo 'ogni volta che viene chiamato', quindi ogni chiamata dopo la prima comporta ancora l'overhead aggiuntivo di fare l'importazione.

Il caso 2 consente di risparmiare un po 'di tempo di esecuzione e latenza importando in anticipo datetime in modo che not_often_called () restituisca più rapidamente quando viene chiamato e anche senza sostenere il sovraccarico di un'importazione ad ogni chiamata.

Oltre all'efficienza, è più semplice vedere le dipendenze del modulo in anticipo se le dichiarazioni di importazione sono ... in anticipo. Nasconderli nel codice può rendere più difficile trovare facilmente da quali moduli dipende qualcosa.

Personalmente generalmente seguo il PEP ad eccezione di cose come i test unitari e tali che non voglio essere sempre caricati perché so che non verranno utilizzati tranne il codice di test.

Ecco un esempio in cui tutte le importazioni sono in cima (questa è l'unica volta in cui ho bisogno di farlo). Voglio essere in grado di terminare un sottoprocesso su Un * xe Windows.

import os
# ...
try:
    kill = os.kill  # will raise AttributeError on Windows
    from signal import SIGTERM
    def terminate(process):
        kill(process.pid, SIGTERM)
except (AttributeError, ImportError):
    try:
        from win32api import TerminateProcess  # use win32api if available
        def terminate(process):
            TerminateProcess(int(process._handle), -1)
    except ImportError:
        def terminate(process):
            raise NotImplementedError  # define a dummy function

(In revisione: what John Millikin ha detto.)

Questo è come molte altre ottimizzazioni: sacrifichi un po 'di leggibilità per la velocità. Come accennato da John, se hai fatto i tuoi compiti di profilazione e hai trovato questo un cambiamento abbastanza significativamente utile e hai bisogno della velocità extra, quindi provaci. Probabilmente sarebbe bene mettere una nota su tutte le altre importazioni:

from foo import bar
from baz import qux
# Note: datetime is imported in SomeClass below

L'inizializzazione del modulo avviene una sola volta, alla prima importazione. Se il modulo in questione proviene dalla libreria standard, probabilmente lo importerai anche da altri moduli nel tuo programma. Per un modulo prevalente come il datetime, è anche probabilmente una dipendenza per una serie di altre librerie standard. La dichiarazione di importazione costerebbe molto poco allora poiché l'inizializzazione del modulo sarebbe già avvenuta. A questo punto, tutto ciò che sta facendo è associare l'oggetto modulo esistente all'ambito locale.

Associa queste informazioni all'argomento per la leggibilità e direi che è meglio avere la dichiarazione di importazione nell'ambito del modulo.

Solo per completare la risposta di Moe e la domanda originale:

Quando abbiamo a che fare con dipendenze circolari possiamo fare alcuni "trucchi". Supponendo che stiamo lavorando con i moduli a.py e b. py che contengono x () e b y () , rispettivamente. Poi:

  1. Possiamo spostare uno dei dalle importazioni nella parte inferiore del modulo.
  2. È possibile spostare uno dei dalle importazioni all'interno della funzione o del metodo che richiede effettivamente l'importazione (ciò non è sempre possibile, poiché è possibile utilizzarlo da diversi punti).
  3. Possiamo cambiare uno dei due dalle importazioni in un'importazione che assomiglia a: importa un

Quindi, per concludere. Se non hai a che fare con dipendenze circolari e fai qualche trucco per evitarle, allora è meglio mettere tutte le tue importazioni in cima a causa dei motivi già spiegati in altre risposte a questa domanda. E per favore, quando fai questo "trucchi" includi un commento, è sempre il benvenuto! :)

Oltre alle eccellenti risposte già fornite, vale la pena notare che il posizionamento delle importazioni non è solo una questione di stile. A volte un modulo ha dipendenze implicite che devono essere importate o inizializzate per prime, e un'importazione di livello superiore potrebbe portare a violazioni dell'ordine di esecuzione richiesto.

Questo problema si presenta spesso nell'API Python di Apache Spark, dove è necessario inizializzare SparkContext prima di importare pacchetti o moduli pyspark. È meglio posizionare le importazioni di pyspark in un ambito in cui è garantito che SparkContext sia disponibile.

Non aspiro a fornire una risposta completa, perché altri lo hanno già fatto molto bene. Voglio solo menzionare un caso d'uso quando trovo particolarmente utile importare moduli all'interno di funzioni. La mia applicazione utilizza pacchetti e moduli python memorizzati in determinate posizioni come plugin. Durante l'avvio dell'applicazione, l'applicazione attraversa tutti i moduli nella posizione e li importa, quindi guarda all'interno dei moduli e se trova alcuni punti di montaggio per i plugin (nel mio caso è una sottoclasse di una certa classe base con un unico ID) li registra. Il numero di plugin è grande (ora dozzine, ma forse centinaia in futuro) e ognuno di essi viene usato abbastanza raramente. Avere importazioni di librerie di terze parti nella parte superiore dei miei moduli plug-in è stato un po 'penalizzante durante l'avvio dell'applicazione. Soprattutto alcune librerie di terze parti sono pesanti da importare (ad esempio l'importazione di plotly tenta persino di connettersi a Internet e scaricare qualcosa che stava aggiungendo circa un secondo all'avvio). Ottimizzando le importazioni (chiamandole solo nelle funzioni in cui vengono utilizzate) nei plugin sono riuscito a ridurre l'avvio da 10 secondi a circa 2 secondi. Questa è una grande differenza per i miei utenti.

Quindi la mia risposta è no, non sempre mettere le importazioni in cima ai tuoi moduli.

Sono stato sorpreso di non vedere i numeri di costo effettivi per i ripetuti controlli di carico già pubblicati, anche se ci sono molte buone spiegazioni su cosa aspettarsi.

Se si importa in alto, si prende il carico hit indipendentemente da cosa. È piuttosto piccolo, ma comunemente nei millisecondi, non nei nanosecondi.

Se si importa all'interno di una o più funzioni, si prende il colpo solo per caricare if e quando viene chiamata per la prima volta una di quelle funzioni. Come molti hanno sottolineato, se ciò non accade affatto, si risparmia il tempo di caricamento. Ma se le funzioni vengono chiamate molto, si ottiene un hit ripetuto ma molto più piccolo (per verificare che sia stato caricato ; non per ricaricare effettivamente). D'altra parte, come ha sottolineato @aaronasterling, si risparmia anche un po 'perché l'importazione all'interno di una funzione consente alla funzione di utilizzare ricerche variabili locali leggermente più veloci per identificare il nome in seguito (http://stackoverflow.com/questions/477096/python-import-coding-style/4789963#4789963 ).

Ecco i risultati di un semplice test che importa alcune cose dall'interno di una funzione. I tempi riportati (in Python 2.7.14 su un Intel Core i7 a 2,3 GHz) sono mostrati di seguito (la seconda chiamata che prende più di quelle successive sembra coerente, anche se non so perché).

 0 foo:   14429.0924 µs
 1 foo:      63.8962 µs
 2 foo:      10.0136 µs
 3 foo:       7.1526 µs
 4 foo:       7.8678 µs
 0 bar:       9.0599 µs
 1 bar:       6.9141 µs
 2 bar:       7.1526 µs
 3 bar:       7.8678 µs
 4 bar:       7.1526 µs

Il codice:

from __future__ import print_function
from time import time

def foo():
    import collections
    import re
    import string
    import math
    import subprocess
    return

def bar():
    import collections
    import re
    import string
    import math
    import subprocess
    return

t0 = time()
for i in xrange(5):
    foo()
    t1 = time()
    print("    %2d foo: %12.4f \xC2\xB5s" % (i, (t1-t0)*1E6))
    t0 = t1
for i in xrange(5):
    bar()
    t1 = time()
    print("    %2d bar: %12.4f \xC2\xB5s" % (i, (t1-t0)*1E6))
    t0 = t1

È interessante notare che finora nessuna risposta ha menzionato l'elaborazione parallela, dove potrebbe essere RICHIESTO che le importazioni siano nella funzione, quando il codice funzione serializzato è ciò che viene trasferito ad altri core, ad es. come nel caso di ipyparallel.

Può esserci un miglioramento delle prestazioni importando variabili / ambito locale all'interno di una funzione. Questo dipende dall'uso della cosa importata all'interno della funzione. Se esegui il loop molte volte e accedi a un oggetto globale del modulo, può essere utile importarlo come local.

test.py

X=10
Y=11
Z=12
def add(i):
  i = i + 10

runlocal.py

from test import add, X, Y, Z

    def callme():
      x=X
      y=Y
      z=Z
      ladd=add 
      for i  in range(100000000):
        ladd(i)
        x+y+z

    callme()

run.py

from test import add, X, Y, Z

def callme():
  for i in range(100000000):
    add(i)
    X+Y+Z

callme()

Un tempo su Linux mostra un piccolo guadagno

/usr/bin/time -f "\t%E real,\t%U user,\t%S sys" python run.py 
    0:17.80 real,   17.77 user, 0.01 sys
/tmp/test$ /usr/bin/time -f "\t%E real,\t%U user,\t%S sys" python runlocal.py 
    0:14.23 real,   14.22 user, 0.01 sys

reale è l'orologio da parete. l'utente è tempo nel programma. sys è il momento delle chiamate di sistema.

https://docs.python.org/3.5 /reference/executionmodel.html#resolution-of-names

Vorrei menzionare un mio caso d'uso, molto simile a quelli menzionati da @John Millikin e @ V.K. :

Importazioni opzionali

Faccio analisi dei dati con Jupyter Notebook e utilizzo lo stesso notebook IPython come modello per tutte le analisi. In alcune occasioni, devo importare Tensorflow per eseguire alcune rapide corse di modelli, ma a volte lavoro in luoghi in cui tensorflow non è impostato / è lento da importare. In questi casi, incapsulo le mie operazioni dipendenti da Tensorflow in una funzione di supporto, importare tensorflow all'interno di quella funzione e lo associo a un pulsante.

In questo modo, potrei fare " restart-and-run-all " senza dover aspettare l'importazione o riprendere il resto delle celle quando fallisce.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top