En Python, ¿puedo llamar al main () de un módulo importado?

86

En Python tengo un módulo myModule.py donde defino algunas funciones y un main () , que toma algunos argumentos de línea de comando.

Normalmente llamo a esto main () desde un script bash. Ahora, me gustaría poner todo en un paquete pequeño , así que pensé que tal vez podría convertir mi simple script bash en un script Python y ponerlo en el paquete.

Entonces, ¿cómo llamo realmente a la función main () de myModule.py desde la función main () de MyFormerBashScript.py? ¿Puedo siquiera hacer eso? ¿Cómo le paso algún argumento ?

Ricky Robinson
fuente
Si ha importado myModule, debería poder llamar myModule.main(). ¿Qué has intentado hasta ahora?
Marius
Me preocupan los argumentos de entrada, que normalmente paso desde un script de shell.
Ricky Robinson
¿Tiene sentido llamarlo con el subprocessmódulo?
BenDundee
Supongo que sería más fácil, sí.
Ricky Robinson

Respuestas:

114

Es solo una función. Importarlo y llamarlo:

import myModule

myModule.main()

Si necesita analizar argumentos, tiene dos opciones:

  • Analizarlos main(), pero pasarlos sys.argvcomo un parámetro (todo el código a continuación en el mismo 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:])
    

    Ahora puede importar y llamar myModule.main(['arg1', 'arg2', 'arg3'])desde otro módulo.

  • Tener main()aceptar parámetros que ya son analizadas (de nuevo todo el código en el 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 importar y llamar a myModule.main(foovalue, barvalue, baz='ham')otro lugar y pasar argumentos de Python según sea necesario.

El truco aquí es detectar cuándo se está utilizando su módulo como script; cuando ejecuta un archivo de Python como el script principal ( python filename.py) no importse usa ninguna declaración, por lo que Python llama a ese módulo "__main__". Pero si ese mismo filename.pycódigo se trata como un módulo ( import filename), Python lo usa como el nombre del módulo. En ambos casos, la variable __name__está configurada y la prueba con ella le indica cómo se ejecutó su código.

Martijn Pieters
fuente
3
Por supuesto. Pero, ¿qué pasa con los argumentos de entrada? Yo uso argparse, así que cuando llamo al script desde una terminal, lo hago $ python myModule -a input_a -b input_b --parameterC input_c. ¿Cómo funcionaría desde dentro del código Python? Eso es lo que no pude encontrar con una simple búsqueda.
Ricky Robinson
@RickyRobinson: ampliado para mostrar que puede tenerlo en ambos sentidos; simplemente pase los argumentos analizados o por analizar.
Martijn Pieters
1
Gracias. ¿Podría especificar también qué fragmento de código pertenece a qué módulo o script? Parece mucho más engorroso de lo que pensaba al principio.
Ricky Robinson
@RickyRobinson: Ambos extractos pertenecen juntos en el mismo módulo; Lo he hecho explícito.
Martijn Pieters
42

La respuesta de Martijen tiene sentido, pero faltaba algo crucial que puede parecer obvio para los demás, pero que fue difícil de entender para mí.

En la versión donde usa argparse, necesita tener esta línea en el cuerpo principal.

args = parser.parse_args(args)

Normalmente, cuando usa argparse solo en un script, solo escribe

args = parser.parse_args()

y parse_args encuentran los argumentos de la línea de comando. Pero en este caso, la función principal no tiene acceso a los argumentos de la línea de comandos, por lo que debe decirle a argparse cuáles son los argumentos.

Aquí hay un ejemplo

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:])

Suponiendo que nombró a esto mytest.py Para ejecutarlo, puede hacer cualquiera de estos desde la línea de comando

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

que regresa respectivamente

X center: 8.0
Y center: 4

o

X center: 8.0
Y center: 2.0

o

X center: 2
Y center: 4

O si desea ejecutar desde otro script de Python, puede hacerlo

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

que regresa

X center: 7.0
Y center: 6.0
barry solomon
fuente
4
Esto es exactamente lo que necesitaba - muchas gracias por el útil apéndice
HFBrowning
Esto podría haber funcionado con Python 2, pero en Python 3 ya no funciona, no se puede llamar a la función main () de un módulo:AttributeError: module 'sqlacodegen' has no attribute 'main'
NaturalBornCamper
24

Depende. Si el código principal está protegido por un ifcomo en:

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

entonces no, no puede hacer que Python ejecute eso porque no puede influir en la variable automática __name__.

Pero cuando todo el código está en una función, es posible que pueda hacerlo. Tratar

import myModule

myModule.main()

Esto funciona incluso cuando el módulo se protege con un __all__.

from myModule import *puede que no sea mainvisible para usted, por lo que realmente necesita importar el módulo.

Aaron Digulla
fuente
Oh ok, gracias por la aclaración. Puse todo en una función main (), por lo que debería estar bien. Me preocupa más cómo pasar argumentos de entrada a este "segundo" principal. ¿Alguna forma fácil de hacerlo?
Ricky Robinson
Seguro:import sys; module.main(sys.argv);
Aaron Digulla
Esta es una gran explicación alternativa sobre el acceso __main__que me ayudó, gracias @AaronDigulla
Charlie G
Aquí hay un truco para llamar a una main desde otra función: stackoverflow.com/a/20158605/3244382
PatriceG
3

Yo también tenía la misma necesidad de usar argparse. La cosa es parse_argsfunción de una argparse.ArgumentParserinstancia de objeto implícitamente toma sus argumentos por defecto de sys.args. La solución, siguiendo la línea de Martijn, consiste en hacer eso explícito, para que pueda cambiar los argumentos a los que pasa parse_argscomo deseo.

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:])

El punto clave es pasar argumentos para parse_argsfuncionar. Más tarde, para usar el main, haz lo que te diga Martijn.

eguaio
fuente
3

La respuesta que estaba buscando fue respondida aquí: ¿Cómo usar python argparse con argumentos distintos de sys.argv?

Si main.pyy parse_args()está escrito de esta manera, entonces el análisis se puede hacer muy bien

# 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)

Luego puede llamar main()y analizar argumentos con parser.parse_args(['--input', 'foobar.txt'])él en otro script de 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
fuente
0

Suponiendo que también está intentando pasar los argumentos de la línea de comandos.

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
fuente
Gracias. De hecho, estoy usando en argparselugar de sys.argv. ¿Cómo cambiaría en este caso? Además, desde el script externo solo quiero pasar algunos argumentos de entrada que el usuario escribe, mientras que otros argumentos de entrada para el script interno (myModule.py) están codificados por mí.
Ricky Robinson
Sin ver el código en sí, es difícil responder a los detalles. En general, solo pasaría los argumentos. El *desempaqueta una matriz f(a) => f([1,2,3])frente a lo f(*a) => f(1,2,3) que podría hacer con la misma facilidad myModule.main(sys.argv[1], "other value", 39)o lo que sea.
agoebel