Em Python, posso chamar o main () de um módulo importado?

87

Em Python, tenho um módulo myModule.py onde defino algumas funções e um main () , que leva alguns argumentos de linha de comando.

Normalmente chamo isso de main () de um script bash. Agora, gostaria de colocar tudo em um pequeno pacote , então pensei que talvez pudesse transformar meu script bash simples em um script Python e colocá-lo no pacote.

Então, como eu realmente chamo a função main () de myModule.py a partir da função main () de MyFormerBashScript.py? Posso fazer isso? Como posso passar argumentos para ele?

Ricky Robinson
fonte
Se você importou myModule, então você deve conseguir ligar myModule.main(). O que você tentou até agora?
Marius
Estou preocupado com os argumentos de entrada, que geralmente passo de um script de shell.
Ricky Robinson
Faz sentido chamá-lo com o subprocessmódulo?
BenDundee
Eu acho que seria mais fácil, sim.
Ricky Robinson

Respostas:

116

É apenas uma função. Importe-o e chame-o de:

import myModule

myModule.main()

Se precisar analisar argumentos, você tem duas opções:

  • Analise-os main(), mas passe-os sys.argvcomo um parâmetro (todos os códigos abaixo no mesmo módulo myModule):

    def main(args):
        # parse arguments using optparse or argparse or what have you
    
    if __name__ == '__main__':
        import sys
        main(sys.argv[1:])
    

    Agora você pode importar e chamar myModule.main(['arg1', 'arg2', 'arg3'])de outro módulo.

  • Ter main()parâmetros de aceitação que já foram analisados ​​(novamente, todo o código no myModulemódulo):

    def main(foo, bar, baz='spam'):
        # run with already parsed arguments
    
    if __name__ == '__main__':
        import sys
        # parse sys.argv[1:] using optparse or argparse or what have you
        main(foovalue, barvalue, **dictofoptions)
    

    e importe e chame em myModule.main(foovalue, barvalue, baz='ham')outro lugar e passe argumentos Python conforme necessário.

O truque aqui é detectar quando seu módulo está sendo usado como um script; quando você executa um arquivo python como o script principal ( python filename.py), nenhuma importinstrução está sendo usada, portanto, o python chama esse módulo "__main__". Mas se esse mesmo filename.pycódigo for tratado como um módulo ( import filename), então o python usa isso como o nome do módulo. Em ambos os casos, a variável __name__é definida e o teste em relação a isso informa como seu código foi executado.

Martijn Pieters
fonte
3
Certo. Mas e quanto aos argumentos de entrada? Eu uso argparse, então, quando chamo o script de um terminal, eu uso $ python myModule -a input_a -b input_b --parameterC input_c. Como funcionaria de dentro do código python? Isso é o que não consegui encontrar em uma pesquisa simples.
Ricky Robinson
@RickyRobinson: expandido para mostrar que você pode ter as duas coisas; apenas passe os argumentos analisados ​​ou a serem analisados.
Martijn Pieters
1
Obrigado. Você também poderia especificar qual trecho de código pertence a qual módulo ou script? Parece muito mais complicado do que pensei no início.
Ricky Robinson
@RickyRobinson: Os dois trechos pertencem ao mesmo módulo; Eu deixei isso explícito.
Martijn Pieters
42

A resposta de Martijen faz sentido, mas estava faltando algo crucial que pode parecer óbvio para os outros, mas era difícil para mim descobrir.

Na versão em que você usa argparse, você precisa ter esta linha no corpo principal.

args = parser.parse_args(args)

Normalmente, quando você está usando argparse apenas em um script, você acabou de escrever

args = parser.parse_args()

e parse_args encontra os argumentos da linha de comando. Mas, neste caso, a função principal não tem acesso aos argumentos da linha de comando, então você deve informar ao argparse quais são os argumentos.

Aqui está um exemplo

import argparse
import sys

def x(x_center, y_center):
    print "X center:", x_center
    print "Y center:", y_center

def main(args):
    parser = argparse.ArgumentParser(description="Do something.")
    parser.add_argument("-x", "--xcenter", type=float, default= 2, required=False)
    parser.add_argument("-y", "--ycenter", type=float, default= 4, required=False)
    args = parser.parse_args(args)
    x(args.xcenter, args.ycenter)

if __name__ == '__main__':
    main(sys.argv[1:])

Supondo que você tenha nomeado este mytest.py Para executá-lo, você pode fazer qualquer um destes na linha de comando

python ./mytest.py -x 8
python ./mytest.py -x 8 -y 2
python ./mytest.py 

que retorna respectivamente

X center: 8.0
Y center: 4

ou

X center: 8.0
Y center: 2.0

ou

X center: 2
Y center: 4

Ou se você deseja executar a partir de outro script Python, você pode fazer

import mytest
mytest.main(["-x","7","-y","6"]) 

que retorna

X center: 7.0
Y center: 6.0
Barry Solomon
fonte
4
Isso é exatamente o que eu precisava - muito obrigado pelo adendo útil
HFBrowning
Isso pode ter funcionado com Python 2, mas em Python 3 não funciona mais, não é possível chamar a função main () de um módulo:AttributeError: module 'sqlacodegen' has no attribute 'main'
NaturalBornCamper
26

Depende. Se o código principal estiver protegido por um ifas in:

if __name__ == '__main__':
    ...main code...

então não, você não pode fazer o Python executar isso porque você não pode influenciar a variável automática __name__.

Mas quando todo o código está em uma função, talvez seja possível. Experimentar

import myModule

myModule.main()

Isso funciona mesmo quando o módulo se protege com um __all__.

from myModule import *pode não ficar mainvisível para você, então você realmente precisa importar o próprio módulo.

Aaron Digulla
fonte
Oh ok, obrigado pelo esclarecimento. Coloquei tudo em uma função main (), então deve estar ok. Estou mais preocupado em como passar argumentos de entrada para este "segundo" principal. Alguma maneira fácil de fazer isso?
Ricky Robinson
Claro:import sys; module.main(sys.argv);
Aaron Digulla
Esta é uma ótima explicação alternativa sobre o acesso __main__que me ajudou, obrigado @AaronDigulla
Charlie G
Aqui está um truque para chamar um main de outra função: stackoverflow.com/a/20158605/3244382
PatriceG
3

Tive a mesma necessidade de usar argparsetambém. A coisa é parse_argsfunção de uma argparse.ArgumentParserinstância de objeto implicitamente recebe seus argumentos por padrão de sys.args. A solução, seguindo a linha de Martijn, consiste em tornar isso explícito, para que você possa alterar os argumentos que passa parse_argscomo desejo.

def main(args):
    # some stuff
    parser = argparse.ArgumentParser()
    # some other stuff
    parsed_args = parser.parse_args(args)
    # more stuff with the args

if __name__ == '__main__':
    import sys
    main(sys.argv[1:])

O ponto principal é passar argumentos para a parse_argsfunção. Depois, para usar o principal, basta fazer o que Martijn manda.

eguaio
fonte
3

A resposta que eu estava procurando foi respondida aqui: Como usar python argparse com args diferente de sys.argv?

Se main.pye parse_args()for escrito desta forma, a análise pode ser feita de forma adequada

# main.py
import argparse
def parse_args():
    parser = argparse.ArgumentParser(description="")
    parser.add_argument('--input', default='my_input.txt')
    return parser

def main(args):
    print(args.input)

if __name__ == "__main__":
    parser = parse_args()
    args = parser.parse_args()
    main(args)

Em seguida, você pode chamar main()e analisar argumentos com parser.parse_args(['--input', 'foobar.txt'])ele em outro script Python:

# temp.py
from main import main, parse_args
parser = parse_args()
args = parser.parse_args([]) # note the square bracket
# to overwrite default, use parser.parse_args(['--input', 'foobar.txt'])
print(args) # Namespace(input='my_input.txt')
main(args)
Sida Zhou
fonte
0

Supondo que você esteja tentando passar os argumentos da linha de comando também.

import sys
import myModule


def main():
    # this will just pass all of the system arguments as is
    myModule.main(*sys.argv)

    # all the argv but the script name
    myModule.main(*sys.argv[1:])
agoebel
fonte
Obrigado. Na verdade, estou usando em argparsevez de sys.argv. Como isso mudaria neste caso? Além disso, do script externo, quero apenas passar alguns argumentos de entrada que o usuário digita, enquanto outros argumentos de entrada para o script interno (myModule.py) são codificados por mim.
Ricky Robinson
Sem ver o código em si, é difícil responder a detalhes. Em geral, você apenas passaria quaisquer argumentos. O *descompacta um array f(a) => f([1,2,3])vs f(*a) => f(1,2,3) Você poderia facilmente fazer myModule.main(sys.argv[1], "other value", 39)ou qualquer outra coisa.
ago