Выполнение кода Python с параметром -m или без

111

У интерпретатора python есть опция -m модуля, которая «Запускает модуль модуля библиотеки как скрипт».

С помощью этого кода Python a.py:

if __name__ == "__main__":
    print __package__
    print __name__

Я тестировал, python -m aчтобы получить

"" <-- Empty String
__main__

тогда как python a.pyвозвращается

None <-- None
__main__

Мне эти два вызова кажутся одинаковыми, за исключением того, что __package__ не является None при вызове с параметром -m.

Интересно, что с помощью python -m runpy aя получаю то же самое, что и python -m aс модулем python, скомпилированным для получения a.pyc.

В чем (практическая) разница между этими вызовами? Есть ли между ними плюсы и минусы?

Кроме того , Python Essential Reference Дэвид Бизли объясняет его как « Опция -m запускает библиотеку модуль как скрипт , который выполняется в области __main__ модуля до выполнения основного сценария ». Что это означает?

просить
источник

Ответы:

169

Когда вы используете -mфлаг командной строки , Python импортирует для вас модуль или пакет , а затем запускает его как сценарий. Когда вы не используете -mфлаг, названный вами файл запускается как скрипт .

Это различие важно, когда вы пытаетесь запустить пакет. Есть большая разница между:

python foo/bar/baz.py

и

python -m foo.bar.baz

как и в последнем случае, foo.barимпортируется, и относительный импорт будет правильно работать с foo.barотправной точкой.

Демо:

$ mkdir -p test/foo/bar
$ touch test/foo/__init__.py
$ touch test/foo/bar/__init__.py
$ cat << EOF > test/foo/bar/baz.py 
> if __name__ == "__main__":
>     print __package__
>     print __name__
> 
> EOF
$ PYTHONPATH=test python test/foo/bar/baz.py 
None
__main__
$ PYTHONPATH=test python -m foo.bar.baz 
foo.bar
__main__

В результате Python должен действительно заботиться о пакетах при использовании -mпереключателя. Обычный скрипт никогда не может быть пакетом, поэтому для __package__него установлено значение None.

Но запустите пакет или модуль внутри пакета с помощью, -mи теперь есть, по крайней мере, возможность пакета, поэтому для __package__переменной установлено строковое значение; в приведенной выше демонстрации он foo.barустановлен в пустую строку для простых модулей, не находящихся внутри пакета.

Что касается __main__ модуля ; Python импортирует выполняемые скрипты, как обычный модуль. Создается новый объект модуля для хранения глобального пространства имен, хранящегося в sys.modules['__main__']. Это то, к чему __name__относится переменная, это ключ в этой структуре.

Для пакетов вы можете создать __main__.pyмодуль и запустить его при запуске python -m package_name; в том , что это единственный способ , которым Вы можете запустить пакет как сценарий:

$ PYTHONPATH=test python -m foo.bar
python: No module named foo.bar.__main__; 'foo.bar' is a package and cannot be directly executed
$ cp test/foo/bar/baz.py test/foo/bar/__main__.py
$ PYTHONPATH=test python -m foo.bar
foo.bar
__main__

Итак, при присвоении имени пакету для работы -mPython ищет __main__модуль, содержащийся в этом пакете, и выполняет его как скрипт. Тогда его имя все еще будет установлено __main__, и объект модуля все еще будет сохранен в sys.modules['__main__'].

Мартейн Питерс
источник
1
Что на самом деле означает команда PYTHONPATH=test python -m foo.bar? Не могли бы вы объяснить это подробнее?
Андрей
3
@Andriy: PYTHONPATHустанавливает переменную окружения; расширяет серию каталогов, в которых Python будет искать модули при импорте; здесь он добавляет testкаталог к ​​этой серии. Помещая его в ту же командную строку, он применяется только к этой единственной pythonкоманде. -mсообщает Python, что нужно импортировать определенный модуль, как если бы вы его запустили import foo.bar. Однако __main__при использовании этого переключателя Python автоматически запускает модуль внутри пакета как сценарий.
Мартин Питерс
1
having to use -m always is not that user-.friendly.Я думаю, что использование и неиспользование смеси -mменее удобно для пользователя.
Simin Jie
1
@SiminJie: скрипты можно открывать по любому произвольному пути, а затем их родительский каталог добавляется в путь поиска модуля. -mработает только для текущего каталога или каталогов, уже зарегистрированных в пути поиска. Это была моя точка зрения. -mэто не то, что вы даете конечным пользователям по той самой проблеме удобства использования.
Мартин Питерс
1
@flow2k: Я имею в виду, что from Photos import ...буду жаловаться. Так бы import Photos.<something>. import Photosработает только потому, что Python поддерживает пакеты с пространством имен (где два отдельных дистрибутива предоставляют Photos.fooи Photos.barотдельно, и ими можно независимо управлять).
Мартейн Питерс
25

Выполнение кода Python с параметром -m или без

Используйте -mфлаг.

Результаты почти такие же, когда у вас есть скрипт, но когда вы разрабатываете пакет без -mфлага, нет никакого способа заставить импорт работать правильно, если вы хотите запустить подпакет или модуль в пакете в качестве основной записи. укажите на вашу программу (и поверьте, я пробовал.)

Документы

Как и в документации по флагу -m :

Найдите в sys.path названный модуль и выполните его содержимое как __main__модуль.

и

Как и в случае с параметром -c, текущий каталог будет добавлен в начало sys.path.

так

python -m pdb

примерно эквивалентен

python /usr/lib/python3.5/pdb.py

(при условии, что в вашем текущем каталоге нет пакета или скрипта с именем pdb.py)

Пояснение:

Поведение сделано «намеренно подобным» сценариям.

Многие стандартные библиотечные модули содержат код, который вызывается при их выполнении как сценарий. Примером может служить модуль timeit:

Некоторый код Python предназначен для запуска как модуля: (я думаю, что этот пример лучше, чем пример документа с параметром командной строки)

$ python -m timeit '"-".join(str(n) for n in range(100))'
10000 loops, best of 3: 40.3 usec per loop
$ python -m timeit '"-".join([str(n) for n in range(100)])'
10000 loops, best of 3: 33.4 usec per loop
$ python -m timeit '"-".join(map(str, range(100)))'
10000 loops, best of 3: 25.2 usec per loop

И из примечаний к выпуску для Python 2.4 :

Параметр командной строки -m - python -m modulename найдет модуль в стандартной библиотеке и вызовет его. Например, python -m pdb эквивалентноpython /usr/lib/python2.4/pdb.py

Дополнительный вопрос

Кроме того, в Essential Reference Дэвида Бизли это объясняется следующим образом: «Параметр -m запускает библиотечный модуль как сценарий, который выполняется внутри __main__модуля до выполнения основного сценария».

Это означает, что любой модуль, который вы можете найти с помощью оператора импорта, может быть запущен как точка входа в программу - если у него есть блок кода, обычно ближе к концу, с if __name__ == '__main__':.

-m без добавления текущего каталога в путь:

Комментарий здесь в другом месте гласит:

То, что опция -m также добавляет текущий каталог в sys.path, очевидно, является проблемой безопасности (см. Атака с предварительной загрузкой). Это поведение аналогично порядку поиска в библиотеках в Windows (до недавнего времени, когда он был усилен). Жалко, что Python не следует тенденции и не предлагает простого способа отключить добавление. к sys.path

Что ж, это демонстрирует возможную проблему - (в Windows удалите кавычки):

echo "import sys; print(sys.version)" > pdb.py

python -m pdb
3.5.2 |Anaconda 4.1.1 (64-bit)| (default, Jul  5 2016, 11:41:13) [MSC v.1900 64 bit (AMD64)]

Используйте -Iфлаг, чтобы заблокировать это для производственных сред (новое в версии 3.4):

python -Im pdb
usage: pdb.py [-c command] ... pyfile [arg] ...
etc...

из документов :

-I

Запустите Python в изолированном режиме. Это также подразумевает -E и -s. В изолированном режиме sys.path не содержит ни каталога сценария, ни каталога пакетов сайта пользователя. Все переменные среды PYTHON * также игнорируются. Могут быть наложены дополнительные ограничения, чтобы предотвратить внедрение вредоносного кода пользователем.

Что __package__делать?

Это позволяет явный относительный импорт, что не особенно важно для этого вопроса - см. Этот ответ здесь: Какова цель атрибута «__package__» в Python?

Аарон Холл
источник
Какой путь добавляется к sys.path при использовании ключа -m?
переменная
У меня уже есть цитата: «Как и в случае с параметром -c, текущий каталог будет добавлен в начало sys.path». но я пояснил, о чем идет речь.
Аарон Холл
Я имею в виду, что - предположим, что в каталоге D: \ test я запускаю команду - python -m foo.bar.boo, тогда это добавит папку установки python или каталог D: \ test в sys.path? Насколько я понимаю, он добавит d: \ test в sys.path, импортирует foo.bar и запустит сценарий boo
переменная
@variable - да, попробуйте.
Аарон Холл
1

Основная причина запускать модуль (или пакет) как сценарий с параметром -m - упростить развертывание, особенно в Windows. Вы можете установить сценарии в то же место в библиотеке Python, где обычно размещаются модули - вместо того, чтобы загрязнять PATH или глобальные каталоги исполняемых файлов, такие как ~ / .local (каталог сценариев для каждого пользователя смехотворно трудно найти в Windows).

Затем вы просто набираете -m, и Python автоматически находит сценарий. Например, python -m pipнайдет правильный пункт для того же экземпляра интерпретатора Python, который его выполняет. Без -m, если у пользователя установлено несколько версий Python, какая из них будет «глобальной» точкой?

Если пользователь предпочитает «классические» точки входа для сценариев командной строки, их можно легко добавить как небольшие сценарии где-нибудь в PATH, или pip может создать их во время установки с параметром entry_points в setup.py.

Поэтому просто проверьте __name__ == '__main__'и игнорируйте другие ненадежные детали реализации.

ddbug
источник
То, что опция -m также добавляет текущий каталог в sys.path, очевидно, является проблемой безопасности (см. Атака с предварительной загрузкой ). Это поведение аналогично порядку поиска в библиотеках в Windows (до недавнего времени, когда он был усилен). Жалко, что Python не следует тенденции и не предлагает простого способа отключить добавление. к sys.path.
ddbug