Могу ли я вызвать main () импортированного модуля в Python?

87

В Python у меня есть модуль myModule.py, в котором я определяю несколько функций и main () , который принимает несколько аргументов командной строки.

Обычно я вызываю эту функцию main () из сценария bash. Теперь я хотел бы поместить все в небольшой пакет , поэтому я подумал, что, может быть, я смогу превратить свой простой сценарий bash в сценарий Python и поместить его в пакет.

Итак, как мне на самом деле вызвать функцию main () myModule.py из функции main () MyFormerBashScript.py? Могу я даже это сделать? Как мне передать ему какие-либо аргументы ?

Рики Робинсон
источник
Если вы импортировали myModule, вы сможете позвонить myModule.main(). что ты уже испробовал?
Мариус
Меня беспокоят входные аргументы, которые я обычно передаю из сценария оболочки.
Рики Робинсон
Есть ли смысл вызывать его с помощью subprocessмодуля?
BenDundee
Думаю, было бы проще, да.
Рики Робинсон

Ответы:

116

Это просто функция. Импортируйте его и назовите:

import myModule

myModule.main()

Если вам нужно проанализировать аргументы, у вас есть два варианта:

  • Разберите их main(), но передайте sys.argvкак параметр (весь код ниже в том же модуле myModule):

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

    Теперь вы можете импортировать и вызывать myModule.main(['arg1', 'arg2', 'arg3'])из другого другого модуля.

  • Есть main()принимать параметры, которые уже разобранные (опять весь код в myModuleмодуле):

    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)
    

    и импортировать и вызывать в myModule.main(foovalue, barvalue, baz='ham')другом месте и передавать аргументы Python по мере необходимости.

Уловка здесь состоит в том, чтобы определить, когда ваш модуль используется как скрипт; когда вы запускаете файл python в качестве основного скрипта ( python filename.py), никакой importоператор не используется, поэтому python вызывает этот модуль "__main__". Но если тот же filename.pyкод обрабатывается как module ( import filename), тогда python вместо этого использует его в качестве имени модуля. В обоих случаях переменная __name__установлена, и тестирование с ее помощью сообщает вам, как выполнялся ваш код.

Мартейн Питерс
источник
3
Конечно. Но как насчет входных аргументов? Я использую argparse, поэтому, когда я вызываю скрипт с терминала, я использую $ python myModule -a input_a -b input_b --parameterC input_c. Как это будет работать из кода Python? Вот чего я не смог найти простым поиском.
Рики Робинсон
@RickyRobinson: расширен, чтобы показать, что это можно делать обоими способами; просто передайте анализируемые или подлежащие синтаксическому анализу аргументы.
Мартейн Питерс
1
Благодарю. Не могли бы вы также указать, какой фрагмент кода принадлежит какому модулю или скрипту? Это выглядит более громоздко, чем я думал вначале.
Рики Робинсон
@RickyRobinson: Оба отрывка принадлежат одному и тому же модулю; Я сделал это ясно.
Мартейн Питерс
42

Ответ Мартиджена имеет смысл, но в нем упускается что-то важное, что может показаться очевидным для других, но мне было трудно понять.

В версии, где вы используете argparse, вам необходимо, чтобы эта строка была в основном теле.

args = parser.parse_args(args)

Обычно, когда вы используете argparse только в скрипте, вы просто пишете

args = parser.parse_args()

и parse_args находят аргументы из командной строки. Но в этом случае основная функция не имеет доступа к аргументам командной строки, поэтому вы должны сообщить argparse, каковы аргументы.

Вот пример

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

Предполагая, что вы назвали этот mytest.py Чтобы запустить его, вы можете выполнить любое из этих действий из командной строки.

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

который возвращается соответственно

X center: 8.0
Y center: 4

или же

X center: 8.0
Y center: 2.0

или же

X center: 2
Y center: 4

Или, если вы хотите запустить из другого скрипта Python, вы можете сделать

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

который возвращается

X center: 7.0
Y center: 6.0
Барри Соломон
источник
4
Это именно то, что мне было нужно - большое спасибо за полезное приложение
HFBrowning
Это могло сработать с Python 2, но в Python 3 он больше не работает, не может вызывать функцию main () модуля:AttributeError: module 'sqlacodegen' has no attribute 'main'
NaturalBornCamper
26

Это зависит. Если основной код защищен ifкак в:

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

тогда нет, вы не можете заставить Python выполнить это, потому что вы не можете повлиять на автоматическую переменную __name__.

Но когда весь код находится в функции, возможно, это удастся. Пытаться

import myModule

myModule.main()

Это работает, даже если модуль защищает себя с помощью файла __all__.

from myModule import *может не mainотображаться для вас, поэтому вам действительно нужно импортировать сам модуль.

Аарон Дигулла
источник
Хорошо, спасибо за разъяснения. Я поместил все в функцию main (), так что все должно быть в порядке. Меня больше беспокоит то, как передать входные аргументы этому «второму» основному элементу. Есть какой-нибудь простой способ сделать это?
Рики Робинсон
Конечно:import sys; module.main(sys.argv);
Аарон Дигулла
Это отличное альтернативное объяснение доступа, __main__которое мне помогло, спасибо @AaronDigulla
Charlie G
Вот трюк для вызова функции main из другой функции: stackoverflow.com/a/20158605/3244382
PatriceG
3

У меня была такая же потребность в использовании argparse. Дело в том, что parse_argsфункция argparse.ArgumentParserэкземпляра объекта неявно принимает свои аргументы по умолчанию из sys.args. Обход, следующий за строкой Martijn, состоит в том, чтобы сделать это явным, чтобы вы могли изменить аргументы, которые вы передаете parse_argsкак желаемое.

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

Ключевым моментом является передача аргументов parse_argsфункции. Позже, чтобы использовать главное, вы просто делаете, как говорит Мартин.

эгуайо
источник
3

Ответ, который я искал, был дан здесь: Как использовать python argparse с другими аргументами, кроме sys.argv?

Если main.pyи parse_args()написано таким образом, то синтаксический анализ может быть выполнен красиво

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

Затем вы можете вызвать main()и проанализировать аргументы parser.parse_args(['--input', 'foobar.txt'])для него в другом скрипте 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)
Сида Чжоу
источник
0

Предполагая, что вы также пытаетесь передать аргументы командной строки.

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
источник
Благодарю. На самом деле я использую argparseвместо sys.argv. Как бы это изменилось в этом случае? Кроме того, из внешнего скрипта я просто хочу передать несколько входных аргументов, которые вводит пользователь, в то время как другие входные аргументы для внутреннего скрипта (myModule.py) жестко запрограммированы мной.
Рики Робинсон
Не видя самого кода, сложно ответить на конкретику. В общем, вы просто передаете любые аргументы. В* распаковывается массив f(a) => f([1,2,3])против f(*a) => f(1,2,3) вас может так же легко сделать myModule.main(sys.argv[1], "other value", 39)или любой другой .
agoebel