Czy w Pythonie mogę wywołać metodę main () zaimportowanego modułu?

86

W Pythonie mam moduł myModule.py, w którym definiuję kilka funkcji oraz funkcję main () , która pobiera kilka argumentów wiersza poleceń.

Zwykle nazywam to main () ze skryptu bash. Teraz chciałbym umieścić wszystko w małym pakiecie , więc pomyślałem, że może mógłbym zamienić mój prosty skrypt bash na skrypt Pythona i umieścić go w pakiecie.

Jak więc właściwie wywołać funkcję main () myModule.py z funkcji main () MyFormerBashScript.py? Czy mogę to zrobić? Jak przekazać do niego jakiekolwiek argumenty ?

Ricky Robinson
źródło
Jeśli zaimportowałeś myModule, powinieneś być w stanie zadzwonić myModule.main(). Czego spróbowałeś do tej pory?
Marius,
Martwią mnie argumenty wejściowe, które zwykle przekazuję ze skryptu powłoki.
Ricky Robinson
Czy ma sens nazywanie tego z subprocessmodułem?
BenDundee,
Myślę, że byłoby łatwiej, tak.
Ricky Robinson

Odpowiedzi:

114

To tylko funkcja. Zaimportuj i nazwij to:

import myModule

myModule.main()

Jeśli potrzebujesz analizować argumenty, masz dwie możliwości:

  • Przetwórz je main(), ale przekaż sys.argvjako parametr (cały poniższy kod w tym samym module myModule):

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

    Teraz możesz importować i dzwonić myModule.main(['arg1', 'arg2', 'arg3'])z innego innego modułu.

  • Mają main()akceptować parametry, które są już przeanalizowane (ponownie cały kod w myModulemodule):

    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)
    

    importuj i wywołuj myModule.main(foovalue, barvalue, baz='ham')gdzie indziej i przekazuj argumenty Pythona w razie potrzeby.

Sztuczka polega na tym, aby wykryć, kiedy twój moduł jest używany jako skrypt; kiedy uruchamiasz plik pythona jako główny skrypt ( python filename.py), żadna importinstrukcja nie jest używana, więc python wywołuje ten moduł "__main__". Ale jeśli ten sam filename.pykod jest traktowany jako module ( import filename), to python używa go jako nazwy modułu. W obu przypadkach zmienna __name__jest ustawiona, a testowanie na jej podstawie powie Ci, jak Twój kod został uruchomiony.

Martijn Pieters
źródło
3
Pewnie. Ale co z argumentami wejściowymi? Używam argparse, więc kiedy wywołuję skrypt z terminala, robię $ python myModule -a input_a -b input_b --parameterC input_c. Jak by to działało z poziomu kodu Pythona? Tego nie mogłem znaleźć w prostym wyszukiwaniu.
Ricky Robinson
@RickyRobinson: rozszerzone, aby pokazać, że możesz mieć to w obie strony; po prostu przekaż argumenty przeanalizowane lub do przeanalizowania.
Martijn Pieters
1
Dzięki. Czy mógłbyś również określić, który fragment kodu należy do którego modułu lub skryptu? Wygląda to o wiele bardziej uciążliwie niż myślałem na początku.
Ricky Robinson
@RickyRobinson: Oba fragmenty należą do tego samego modułu; Wyraźnie to wyraziłem.
Martijn Pieters
42

Odpowiedź Martijena ma sens, ale brakowało w niej czegoś kluczowego, co może wydawać się oczywiste dla innych, ale było dla mnie trudne do zrozumienia.

W wersji, w której używasz argparse, musisz mieć tę linię w głównej części.

args = parser.parse_args(args)

Zwykle, gdy używasz argparse tylko w skrypcie, po prostu piszesz

args = parser.parse_args()

i parse_args znajdź argumenty z wiersza poleceń. Ale w tym przypadku główna funkcja nie ma dostępu do argumentów wiersza poleceń, więc musisz powiedzieć argparse, jakie to argumenty.

Oto przykład

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

Zakładając, że nazwałeś ten mytest.py Aby go uruchomić, możesz wykonać dowolną z tych czynności z wiersza poleceń

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

który zwraca odpowiednio

X center: 8.0
Y center: 4

lub

X center: 8.0
Y center: 2.0

lub

X center: 2
Y center: 4

Lub jeśli chcesz uruchomić z innego skryptu Pythona, możesz to zrobić

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

który powraca

X center: 7.0
Y center: 6.0
Barry Solomon
źródło
4
Właśnie tego potrzebowałem - bardzo dziękuję za pomocny dodatek
HFBrowning
To mogło działać z Pythonem 2, ale w Pythonie 3 już nie działa, nie można wywołać funkcji main () modułu:AttributeError: module 'sqlacodegen' has no attribute 'main'
NaturalBornCamper
24

To zależy. Jeśli główny kod jest chroniony przez ifjak w:

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

to nie, nie możesz zmusić Pythona do wykonania tego, ponieważ nie możesz wpłynąć na zmienną automatyczną __name__.

Ale jeśli cały kod znajduje się w funkcji, może to być możliwe. Próbować

import myModule

myModule.main()

Działa to nawet wtedy, gdy moduł chroni się za pomocą pliku __all__.

from myModule import *może nie być mainwidoczny dla Ciebie, więc naprawdę musisz zaimportować sam moduł.

Aaron Digulla
źródło
O ok, dzięki za wyjaśnienie. Umieściłem wszystko w funkcji main (), więc powinno być ok. Bardziej interesuje mnie sposób przekazywania argumentów wejściowych do tego „drugiego” głównego. Czy jest jakiś łatwy sposób?
Ricky Robinson
Jasne:import sys; module.main(sys.argv);
Aaron Digulla
To świetne alternatywne wyjaśnienie dostępu, __main__które pomogło mi, dzięki @AaronDigulla
Charlie G
Oto sztuczka, aby wywołać main z innej funkcji: stackoverflow.com/a/20158605/3244382
PatriceG
3

Miałem tę samą potrzebę argparse. Chodzi o to, że parse_argsfunkcja argparse.ArgumentParserinstancji obiektu domyślnie pobiera swoje argumenty z sys.args. Obejście, zgodnie z linią Martijna, polega na wyraźnym wyrażeniu tego, abyś mógł zmienić argumenty, które przekazujesz parse_argsjako pożądanie.

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

Kluczową kwestią jest przekazywanie argumentów do parse_argsfunkcji. Później, aby skorzystać z głównego, po prostu postępuj zgodnie z instrukcjami Martijna.

eguaio
źródło
3

Odpowiedź, której szukałem, znalazła się tutaj: Jak używać argumentu Python z argumentami innymi niż sys.argv?

Jeśli main.pyi parse_args()jest napisane w ten sposób, to parsowanie można wykonać ładnie

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

Następnie możesz wywoływać main()i analizować argumenty parser.parse_args(['--input', 'foobar.txt'])w innym skrypcie Pythona:

# 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
źródło
0

Zakładając, że próbujesz przekazać również argumenty wiersza poleceń.

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
źródło
Dzięki. Właściwie używam argparsezamiast sys.argv. Jak by się to zmieniło w tym przypadku? Poza tym ze skryptu zewnętrznego chcę tylko przekazać kilka argumentów wejściowych, które wpisuje użytkownik, podczas gdy inne argumenty wejściowe dla skryptu wewnętrznego (myModule.py) są przeze mnie zakodowane na stałe.
Ricky Robinson
Nie widząc samego kodu, trudno odpowiedzieć na szczegóły. Ogólnie rzecz biorąc, po prostu przekazujesz wszelkie argumenty. W *rozpakowuje tablicą f(a) => f([1,2,3])vs f(*a) => f(1,2,3) można było tak łatwo zrobić, myModule.main(sys.argv[1], "other value", 39)czy cokolwiek innego.
agoebel