Execução de código Python com opção -m ou não

111

O interpretador python tem a opção de -m módulo "Executa o módulo do módulo da biblioteca como um script".

Com este código python a.py:

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

Eu testei python -m apara conseguir

"" <-- Empty String
__main__

Considerando que python a.pyretorna

None <-- None
__main__

Para mim, essas duas chamadas parecem ser iguais, exceto que __package__ não é nenhum quando chamado com a opção -m.

Curiosamente, com python -m runpy a, eu obtenho o mesmo que python -m acom o módulo python compilado para obter a.pyc.

Qual é a diferença (prática) entre essas invocações? Quaisquer prós e contras entre eles?

Além disso, David Beazley Python Essential Reference explica como " A opção -m executa um módulo de biblioteca como um script que é executado dentro do módulo __main__ antes da execução do script principal ". O que isso significa?

prosseek
fonte

Respostas:

169

Quando você usa o -msinalizador de linha de comando , o Python importa um módulo ou pacote para você e o executa como um script. Quando você não usa o -msinalizador, o arquivo nomeado é executado apenas como um script .

A distinção é importante quando você tenta executar um pacote. Existe uma grande diferença entre:

python foo/bar/baz.py

e

python -m foo.bar.baz

como no último caso, foo.baré importado e as importações relativas funcionarão corretamente foo.barcomo ponto de partida.

Demo:

$ 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__

Como resultado, o Python precisa realmente se preocupar com os pacotes ao usar a -mopção. Um script normal nunca pode ser um pacote, portanto, __package__é definido como None.

Mas execute um pacote ou módulo dentro de um pacote com -me agora há pelo menos a possibilidade de um pacote, então a __package__variável é definida como um valor de string; na demonstração acima, ele é definido como foo.bar, para módulos simples que não estão dentro de um pacote, é definido como uma string vazia.

Quanto ao __main__ módulo ; Python importa scripts que estão sendo executados como se fossem um módulo normal. Um novo objeto de módulo é criado para conter o namespace global, armazenado em sys.modules['__main__']. É a isso que a __name__variável se refere, é a chave dessa estrutura.

Para pacotes, você pode criar um __main__.pymódulo e executá-lo durante a execução python -m package_name; na verdade, essa é a única maneira que você pode executar um pacote como um script:

$ 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__

Portanto, ao nomear um pacote para execução -m, o Python procura um __main__módulo contido nesse pacote e o executa como um script. Seu nome ainda é definido como __main__e o objeto de módulo ainda está armazenado em sys.modules['__main__'].

Martijn Pieters
fonte
1
O que realmente significa comando PYTHONPATH=test python -m foo.bar? Você poderia explicar em detalhes, por favor?
Andriy
3
@Andriy: PYTHONPATHdefine uma variável de ambiente; ele expande a série de diretórios onde o Python irá procurar por módulos ao importar; aqui ele adiciona o testdiretório a essa série. Ao colocá-lo na mesma linha de comando, ele se aplica apenas a esse único pythoncomando. -mdiz ao Python para importar um módulo específico, como se você o tivesse executado import foo.bar. No entanto, Python executará automaticamente um __main__módulo dentro de um pacote como um script quando você usar essa opção.
Martijn Pieters
1
having to use -m always is not that user-.friendly.Acho que misturar usando e não usando -mé menos amigável.
Simin Jie
1
@SiminJie: os scripts podem ser abertos em qualquer caminho arbitrário e, em seguida, seu diretório pai é adicionado ao caminho de pesquisa do módulo. -msó funciona para o diretório atual ou diretórios já registrados no caminho de pesquisa. Esse foi o meu ponto. -mnão é algo que você dá aos usuários finais exatamente por causa desse problema de usabilidade.
Martijn Pieters
1
@ flow2k: Quero dizer, from Photos import ...vou reclamar. Então seria import Photos.<something>. import Photossó funciona porque o Python suporta pacotes com namespaces (onde duas distribuições separadas fornecem Photos.fooe Photos.barseparadamente e podem ser gerenciados de forma independente).
Martijn Pieters
25

Execução de código Python com opção -m ou não

Use a -mbandeira.

Os resultados são praticamente os mesmos quando você tem um script, mas quando você desenvolve um pacote, sem o -msinalizador, não há como fazer as importações funcionarem corretamente se você deseja executar um subpacote ou módulo no pacote como a entrada principal aponte para o seu programa (e acredite, eu tentei.)

Os docs

Como os documentos na sinalização -m dizem:

Pesquise sys.path para o módulo nomeado e execute seu conteúdo como o __main__módulo.

e

Tal como acontece com a opção -c, o diretório atual será adicionado ao início de sys.path.

tão

python -m pdb

é aproximadamente equivalente a

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

(presumindo que você não tenha um pacote ou script em seu diretório atual chamado pdb.py)

Explicação:

O comportamento é tornado "deliberadamente semelhante a" scripts.

Muitos módulos de biblioteca padrão contêm código que é chamado em sua execução como um script. Um exemplo é o módulo timeit:

Algum código Python deve ser executado como um módulo: (acho que este exemplo é melhor do que o exemplo de documento de opção de linha de comando)

$ 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

E a partir dos destaques da nota de lançamento para Python 2.4 :

A opção de linha de comando -m - python -m modulename encontrará um módulo na biblioteca padrão e o invocará. Por exemplo, python -m pdb é equivalente apython /usr/lib/python2.4/pdb.py

Questão a seguir

Além disso, David Beazley Python Essential Reference explica como "A opção -m executa um módulo de biblioteca como um script que é executado dentro do __main__módulo antes da execução do script principal".

Isso significa que qualquer módulo que você pode consultar com uma instrução de importação pode ser executado como o ponto de entrada do programa - se ele tiver um bloco de código, geralmente próximo ao final, com if __name__ == '__main__':.

-m sem adicionar o diretório atual ao caminho:

Um comentário aqui em outro lugar diz:

O fato da opção -m também adicionar o diretório atual ao sys.path é obviamente um problema de segurança (consulte: ataque de pré-carregamento). Esse comportamento é semelhante à ordem de pesquisa da biblioteca no Windows (antes de ter sido reforçada recentemente). É uma pena que o Python não siga a tendência e não ofereça uma maneira simples de desativar a adição. para sys.path

Bem, isso demonstra o possível problema - (no Windows, remova as aspas):

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

Use a -Isinalização para bloquear isso para ambientes de produção (novo na versão 3.4):

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

dos documentos :

-I

Execute o Python no modo isolado. Isso também implica -E e -s. No modo isolado, sys.path não contém o diretório do script nem o diretório de pacotes do site do usuário. Todas as variáveis ​​de ambiente PYTHON * também são ignoradas. Outras restrições podem ser impostas para evitar que o usuário injete código malicioso.

O que __package__fazer?

Ele permite importações relativas explícitas, embora não particularmente pertinentes a essa pergunta - veja esta resposta aqui: Qual é o propósito do atributo "__package__" em Python?

Aaron Hall
fonte
Qual caminho é adicionado ao sys.path quando a opção -m é usada?
variável de
Já mencionei isso, "Como com a opção -c, o diretório atual será adicionado ao início de sys.path." mas eu esclareci a que a citação se refere.
Aaron Hall
Quero dizer que - suponha que no diretório D: \ test, eu execute o comando - python -m foo.bar.boo, então isso adicionará a pasta de instalação do python ou o diretório D: \ test a sys.path? Meu entendimento é que ele adicionará d: \ test a sys.path, importará foo.bar e executará boo script
variável de
@variable - sim, experimente.
Aaron Hall
1

O principal motivo para executar um módulo (ou pacote) como um script com -m é para simplificar a implantação, especialmente no Windows. Você pode instalar scripts no mesmo local na biblioteca Python onde os módulos normalmente vão - em vez de poluir o PATH ou diretórios executáveis ​​globais como ~ / .local (o diretório de scripts por usuário é ridiculamente difícil de encontrar no Windows).

Então você apenas digita -m e o Python encontra o script automaticamente. Por exemplo, python -m pipencontrará o pip correto para a mesma instância do interpretador Python que o executa. Sem -m, se o usuário tiver várias versões do Python instaladas, qual seria o pip "global"?

Se o usuário preferir pontos de entrada "clássicos" para scripts de linha de comando, eles podem ser facilmente adicionados como pequenos scripts em algum lugar no PATH, ou pip pode criá-los no momento da instalação com o parâmetro entry_points em setup.py.

Portanto, apenas verifique __name__ == '__main__'e ignore outros detalhes de implementação não confiáveis.

ddbug
fonte
O fato da opção -m também adicionar o diretório atual ao sys.path é obviamente um problema de segurança (consulte: ataque de pré - carregamento ). Esse comportamento é semelhante à ordem de pesquisa da biblioteca no Windows (antes de ter sido reforçada recentemente). É uma pena que o Python não siga a tendência e não ofereça uma maneira simples de desativar a adição. para sys.path.
ddbug