Modo preferito per espandere uno script da riga di comando da utilizzare come libreria in Python

4

Ho un utile script Python che sto invocando dalla riga di comando. Ha un numero decente di opzioni, forse 20, e non è insolito eseguire lo script con sei o sette flag. Quindi il resto dell'input arriva tramite stdin.

Ora ho un altro codice Python dal quale mi piacerebbe chiamare questa utile piccola utilità. Due opzioni che posso pensare sono:

  1. Posso usare subprocess.call e invocare il mio piccolo script
  2. (Un po 'meglio) Posso mettere insieme una riga di comando e poi passarla come un elenco di stringhe a argparse
  3. Riesco a rifocalizzare completamente il programma in modo che il punto di ingresso della mia utilità sia la chiamata alla funzione Python e poi la mia utilità della riga di comando chiama questa funzione. In linea di principio, questa sembra la cosa responsabile da fare, ma lascia la mia gestione di due interfacce separate alla mia funzione. Ad esempio, devo decidere se voglio che argparse conosca i valori predefiniti per le mie opzioni o che la funzione conosca i valori predefiniti (o che abbia due set di valori predefiniti). Qualsiasi convalida che utilizzo, ad esempio, ArgumentParser.add_mutually_exclusive_group non si applica quando il mio strumento viene eseguito come libreria invece di uno script da riga di comando.

Esiste un paradigma standard per la creazione di una singola interfaccia in Python che sia adatta per essere invocata sia da Python che dalla riga di comando?

    
posta kuzzooroo 18.03.2015 - 20:07
fonte

3 risposte

6

Se non c'è una ragione buona per non farlo, sosterrei sicuramente un giro sull'opzione 3. Come cita @jonsharp, suddividere la tua utilità in unità pulite di funzionalità è un buon modo per garantire la testabilità. Persino i più piccoli script possono eventualmente trasformarsi in un programma molto più ampio e assicurarti di avere un'API estendibile prima e più tardi, per alleviare il mal di testa.

Il modo in cui mi avvicinerei a questo è:

  1. Suddividi il tuo codice in metodi logici con I / O chiaro e chiaro
  2. Aggiungi test unitari. Averli non è mai una brutta cosa.
  3. Invece di usare if __name__ == '__main__' , crea un metodo main() (o simile) contenente il tuo punto di ingresso
  4. Usa la funzione setup() di setuptool per definire il punto di ingresso dello script nel file setup.py.

Ad esempio:

from setuptools import setup
setup(
    name='mypackage',
    version='0.1',
    entry_points={
        'console_scripts': [ 'myscript = mypackage.mymodule:main' ],
    }
)

Ora, non solo tutto il tuo codice (incluso main ()) è facilmente testabile, ma puoi ancora avere il punto di ingresso della tua console una volta fatto un python setup.py install|develop .

Any validation I do using, say, ArgumentParser.add_mutually_exclusive_group will not apply when my tool is run as a library instead of a command line script.

A seconda di come è progettata la tua API, potrebbe essere necessario aggiungere qualche convalida in più per inserire i parametri, ma è probabile che sia lì per evitare input imprevisti.

Modifica: l'unica volta che userei in genere utilizzare il sottoprocesso è quando sto chiamando in un'applicazione non Python o in un altro script Python che non possiedo o non ho il tempo di refactoring, ma quest'ultimo è solo come ultima risorsa. Le utility Python più ben scritte espongono sia le utilità della riga di comando che l'API interna.

    
risposta data 18.03.2015 - 21:32
fonte
2

La mia ipotesi è che puoi perseguire # 3 per mezzo di # 2 e che non richiederà nulla vicino a un refactoring totale. Spesso in queste situazioni, è sufficiente apportare alcune modifiche ai punti di ingresso e (a volte) ai punti di uscita.

  • Se necessario, avvolgi lo script corrente in una funzione main() .

    def main(args, stdin = None):
        if stdin is None:
            stdin = sys.stdin
    
        # full script here
    
    if __name__ == '__main__':
        main(sys.argv[1:])
    
  • Quella funzione dovrebbe richiedere un elenco di stringhe. Quando analizzi le opzioni della riga di comando, utilizza args , non sys.argv .

  • Allo stesso modo, aggiusta altre parti del tuo script per evitare operazioni dirette su sys.stdin e flussi correlati. Invece, usa gli argomenti passati alla tua funzione main() .

Gli utenti programmatici (al contrario degli utenti della riga di comando) passeranno in un elenco di stringhe e qualsiasi handle di file aperti (o iterabili) che desiderano che il codice utilizzi. Successivamente, se necessario, puoi rendere più naturale l'uso programmatico scrivendo una funzione che sappia convertire gli argomenti tipici di posizione e di parole chiave nell'elenco delle stringhe di cui ha bisogno il parser di opzioni.

    
risposta data 19.03.2015 - 13:37
fonte
1

Chiamare uno script Python da un altro script Python attraverso subprocess sembra strano per me. Non sono sicuro che l'impatto sulle prestazioni sarà importante, ma avere una dipendenza da A in B e poter chiamare direttamente i metodi pubblici sembra più potente e conveniente, specialmente se sia A sia B utilizzano pip .

Probabilmente userò subprocess in uno di questi casi:

  • Se non usi pip . La gestione delle dipendenze e il controllo delle versioni possono diventare rapidamente troppo complicati; chiamare A attraverso la linea di comando sarà quindi una scelta migliore fornendo una migliore astrazione.

  • Se lo script A non è scritto da te o dalla tua azienda e deve essere usato attraverso la linea di comando.

    Ad esempio, vmbuilder - lo strumento che crea macchine virtuali in alcune distribuzioni di Linux, è destinato ad essere usato dalla riga di comando, e sembra naturale (e facile) usarlo in questo modo invece di invocare i metodi Python direttamente .

  • Se esiste il rischio che lo script A venga riscritto in un'altra lingua. Usando la riga di comando, non importa quale lingua A usa sotto il cofano. Può essere Java, C o Haskell, o potrebbe anche essere uno script Bash ordinario: il modo in cui lo chiami sarà sempre lo stesso.

risposta data 18.03.2015 - 20:29
fonte

Leggi altre domande sui tag