È pitonico importare funzioni interne?


126

PEP 8 dice:

  • Le importazioni vengono sempre messe all'inizio del file, subito dopo ogni commento del modulo e docstring e prima delle variabili globali e delle costanti del modulo.

Di tanto in tanto, violo PEP 8. A volte importa cose all'interno di functions. Come regola generale, lo faccio se c'è un'importazione che viene utilizzata solo all'interno di una singola funzione.

Qualche opinione?

MODIFICA (il motivo per cui mi sento di importare in functions può essere una buona idea):

Motivo principale: può rendere il codice più chiaro.

  • Quando guardo il codice di una funzione potrei chiedermi: "Cos'è la funzione / classe xxx?" (xxx utilizzato all'interno della funzione). Se ho tutte le mie importazioni nella parte superiore del modulo, devo andare lì per determinare cosa sia xxx. Questo è più un problema quando si utilizza from m import xxx. Vedere m.xxxnella funzione probabilmente mi dice di più. A seconda di cosa si mtratta: è un noto modulo / pacchetto di primo livello ( import m)? O è un sottomodulo / pacchetto ( from a.b.c import m)?
  • In alcuni casi, avere queste informazioni extra ("Che cos'è xxx?") Vicino a dove viene utilizzato xxx può rendere la funzione più facile da capire.

2
e lo fai per le prestazioni?
Macarse

4
Penso che in alcuni casi renda il codice più chiaro. Immagino che le prestazioni grezze diminuiscano durante l'importazione in una funzione (poiché l'istruzione import verrà eseguita ogni volta che viene chiamata la funzione).
codeape

Puoi rispondere "Cos'è la funzione / classe xxx?" utilizzando la sintassi import xyz anziché la sintassi from xyz import abc
Tom Leys,

1
Se la chiarezza è l'unico fattore, U potrebbe anche includere un commento pertinente, in tal senso. ;)
Lakshman Prasad

5
@becomingGuru: Certo, ma i commenti possono non essere sincronizzati con la realtà ...
codeape

Risposte:


88

A lungo termine penso che apprezzerai avere la maggior parte delle tue importazioni all'inizio del file, in questo modo puoi dire a colpo d'occhio quanto sia complicato il tuo modulo da ciò che deve importare.

Se sto aggiungendo nuovo codice a un file esistente, di solito faccio l'importazione dove è necessario e quindi se il codice rimane, renderò le cose più permanenti spostando la riga di importazione all'inizio del file.

Un altro punto, preferisco ottenere ImportErrorun'eccezione prima che venga eseguito qualsiasi codice, come controllo dell'integrità, quindi questo è un altro motivo per importare in alto.

Uso pyCheckerper verificare la presenza di moduli inutilizzati.


47

Ci sono due occasioni in cui violo PEP 8 a questo proposito:

  • Importazioni circolari: il modulo A importa il modulo B, ma qualcosa nel modulo B necessita del modulo A (anche se questo è spesso un segno che ho bisogno di rifattorizzare i moduli per eliminare la dipendenza circolare)
  • Inserimento di un punto di interruzione pdb: import pdb; pdb.set_trace()questo è utile b / c che non voglio mettere import pdball'inizio di ogni modulo che potrei voler eseguire il debug, ed è facile ricordare di rimuovere l'importazione quando rimuovo il punto di interruzione.

Al di fuori di questi due casi, è una buona idea mettere tutto al primo posto. Rende più chiare le dipendenze.


7
Sono d'accordo che rende le dipendenze più chiare per quanto riguarda il modulo nel suo insieme. Ma credo che possa rendere il codice meno chiaro a livello di funzione per importare tutto in alto. Quando guardi il codice di una funzione potresti chiederti: "Cos'è la funzione / classe xxx?" (xxx è utilizzato all'interno della funzione). E devi guardare all'inizio del file per vedere da dove proviene xxx. Questo è più un problema quando si utilizza from m import xxx. Vedere m.xxx ti dice di più, almeno se non ci sono dubbi su cosa sia m.
codeape

20

Ecco i quattro casi d'uso di importazione che utilizziamo

  1. import(e from x import ye import x as y) in alto

  2. Scelte per l'importazione. In cima.

    import settings
    if setting.something:
        import this as foo
    else:
        import that as foo
  3. Importazione condizionale. Utilizzato con JSON, librerie XML e simili. In cima.

    try:
        import this as foo
    except ImportError:
        import that as foo
  4. Importazione dinamica. Finora, abbiamo solo un esempio di questo.

    import settings
    module_stuff = {}
    module= __import__( settings.some_module, module_stuff )
    x = module_stuff['x']

    Nota che questa importazione dinamica non porta codice, ma porta complesse strutture di dati scritte in Python. È un po 'come un dato in salamoia, tranne per il fatto che l'abbiamo messo in salamoia a mano.

    Questo è anche, più o meno, all'inizio di un modulo


Ecco cosa facciamo per rendere il codice più chiaro:

  • Mantieni i moduli brevi.

  • Se ho tutte le mie importazioni nella parte superiore del modulo, devo andare a cercare lì per determinare cosa sia un nome. Se il modulo è corto, è facile.

  • In alcuni casi, avere queste informazioni aggiuntive vicino a dove viene utilizzato un nome può rendere la funzione più facile da capire. Se il modulo è corto, è facile.


Mantenere i moduli brevi è ovviamente un'ottima idea. Ma per ottenere il vantaggio di avere sempre "informazioni di importazione" per le funzioni disponibili, la lunghezza massima del modulo dovrebbe essere una schermata (probabilmente 100 righe al massimo). E questo sarebbe probabilmente troppo breve per essere pratico nella maggior parte dei casi.
codeape

Suppongo che potresti portare questo a un estremo logico. Penso che potrebbe esserci un punto di equilibrio in cui il tuo modulo è "abbastanza piccolo" da non aver bisogno di tecniche di importazione fantasiose per gestire la complessità. La dimensione media del nostro modulo è - guarda caso - circa 100 righe.
S.Lott

8

Una cosa da tenere a mente: le importazioni inutili possono causare problemi di prestazioni. Quindi, se questa è una funzione che verrà chiamata frequentemente, è meglio mettere semplicemente l'importazione in alto. Ovviamente questa è un'ottimizzazione, quindi se c'è un motivo valido da dimostrare che l'importazione all'interno di una funzione è più chiara dell'importazione all'inizio di un file, ciò supera le prestazioni nella maggior parte dei casi.

Se stai facendo IronPython, mi è stato detto che è meglio importare all'interno delle funzioni (poiché la compilazione del codice in IronPython può essere lenta). Quindi, potresti essere in grado di ottenere un modo con l'importazione di funzioni interne. Ma a parte questo, direi che non vale la pena combattere le convenzioni.

Come regola generale, lo faccio se c'è un'importazione che viene utilizzata solo all'interno di una singola funzione.

Un altro punto che vorrei sottolineare è che questo potrebbe essere un potenziale problema di manutenzione. Cosa succede se aggiungi una funzione che utilizza un modulo precedentemente utilizzato da una sola funzione? Ti ricorderai di aggiungere l'importazione all'inizio del file? O scansionerai ogni singola funzione per le importazioni?

FWIW, ci sono casi in cui ha senso importare all'interno di una funzione. Ad esempio, se si desidera impostare la lingua in cx_Oracle, è necessario impostare una _variabile di ambiente NLS LANG prima che venga importata. Quindi, potresti vedere un codice come questo:

import os

oracle = None

def InitializeOracle(lang):
    global oracle
    os.environ['NLS_LANG'] = lang
    import cx_Oracle
    oracle = cx_Oracle

2
Sono d'accordo con il tuo problema di manutenzione. Il refactoring del codice può essere un po 'problematico. Se aggiungo una seconda funzione che utilizza un modulo precedentemente utilizzato solo da una funzione, sposto l'importazione in alto oppure infrango la mia regola generale importando anche il modulo nella seconda funzione.
codeape

2
Penso che anche l'argomento della performance possa andare dall'altra parte. L'importazione di un modulo può richiedere molto tempo. Su file system distribuiti come quelli sui supercomputer, l'importazione di un grande modulo come numpy può richiedere diversi secondi. Se un modulo è necessario solo per una singola funzione usata raramente, l'importazione nella funzione velocizzerà notevolmente il caso comune.
amaurea

6

Ho infranto questa regola in precedenza per i moduli che si autotestano. Cioè, sono normalmente usati solo per il supporto, ma definisco un main per loro in modo che se li esegui da soli puoi testare la loro funzionalità. In tal caso a volte importa getopte cmdsolo in main, perché voglio che sia chiaro a qualcuno che legge il codice che questi moduli non hanno nulla a che fare con il normale funzionamento del modulo e vengono inclusi solo per i test.


5

Proveniente dalla domanda sul caricamento del modulo due volte - Perché non entrambi?

Un'importazione nella parte superiore dello script indicherà le dipendenze e un'altra importazione nella funzione renderà questa funzione più atomica, sebbene apparentemente non causi alcuno svantaggio di prestazioni, poiché un'importazione consecutiva è economica.


3

Finché è importe non from x import *, dovresti metterli in cima. Aggiunge un solo nome allo spazio dei nomi globale e ti attieni a PEP 8. Inoltre, se in seguito ne avrai bisogno da qualche altra parte, non devi spostare nulla.

Non è un grosso problema, ma poiché non c'è quasi nessuna differenza, suggerirei di fare ciò che dice PEP 8.


3
In realtà, l'inserimento di from x import *una funzione genererà un SyntaxWarning, almeno in 2.5.
Rick Copeland,

3

Dai un'occhiata all'approccio alternativo utilizzato in sqlalchemy: dependency injection:

@util.dependencies("sqlalchemy.orm.query")
def merge_result(query, *args):
    #...
    query.Query(...)

Notate come la libreria importata viene dichiarata in un decoratore e passata come argomento alla funzione !

Questo approccio rende il codice più pulito e funziona anche 4,5 volte più velocemente di importun'istruzione!

Riferimento: https://gist.github.com/kolypto/589e84fbcfb6312532658df2fabdb796


2

Nei moduli che sono entrambi moduli "normali" e possono essere eseguiti (cioè hanno una if __name__ == '__main__':sezione-), di solito importa i moduli che vengono utilizzati solo durante l'esecuzione del modulo all'interno della sezione principale.

Esempio:

def really_useful_function(data):
    ...


def main():
    from pathlib import Path
    from argparse import ArgumentParser
    from dataloader import load_data_from_directory

    parser = ArgumentParser()
    parser.add_argument('directory')
    args = parser.parse_args()
    data = load_data_from_directory(Path(args.directory))
    print(really_useful_function(data)


if __name__ == '__main__':
    main()

1

C'è un altro caso (probabilmente "angolare") in cui può essere utile importall'interno di funzioni usate raramente: abbreviare il tempo di avvio.

Ho colpito quel muro una volta con un programma piuttosto complesso in esecuzione su un piccolo server IoT che accetta comandi da una linea seriale ed esegue operazioni, possibilmente operazioni molto complesse.

Posizionare le importistruzioni all'inizio dei file significava che tutte le importazioni fossero elaborate prima dell'avvio del server; dal momento che importla lista includeva jinja2, lxml, signxmle altri "pesi pesanti" (e SoC non era molto potente) Questo significava minuti prima della prima istruzione è stato effettivamente eseguiti.

OTOH inserendo la maggior parte delle importazioni nelle funzioni sono stato in grado di avere il server "vivo" sulla linea seriale in pochi secondi. Ovviamente quando i moduli erano effettivamente necessari, dovevo pagare il prezzo (Nota: anche questo può essere mitigato generando un'attività in background che esegue imports in tempo di inattività).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.