As instruções de importação devem sempre estar na parte superior de um módulo?

403

O PEP 08 declara:

As importações são sempre colocadas na parte superior do arquivo, logo após qualquer comentário e documentação do módulo, e antes das globais e constantes do módulo.

No entanto, se a classe / método / função que estou importando for usada apenas em casos raros, certamente será mais eficiente fazer a importação quando for necessário?

Não é isso:

class SomeClass(object):

    def not_often_called(self)
        from datetime import datetime
        self.datetime = datetime.now()

mais eficiente que isso?

from datetime import datetime

class SomeClass(object):

    def not_often_called(self)
        self.datetime = datetime.now()
Adam J. Forster
fonte

Respostas:

283

A importação de módulos é bastante rápida, mas não instantânea. Isso significa que:

  • Colocar as importações na parte superior do módulo é bom, porque é um custo trivial que é pago apenas uma vez.
  • Colocar as importações em uma função fará com que as chamadas para essa função demorem mais tempo.

Portanto, se você se preocupa com eficiência, coloque as importações no topo. Somente mova-os para uma função se o seu perfil mostrar que isso ajudaria (você fez um perfil para ver onde melhor melhorar o desempenho, certo?)


Os melhores motivos que vi para realizar importações preguiçosas são:

  • Suporte de biblioteca opcional. Se o seu código tiver vários caminhos que usam bibliotecas diferentes, não quebre se uma biblioteca opcional não estiver instalada.
  • No __init__.pyde um plugin, que pode ser importado, mas não realmente usado. Exemplos são os plug-ins do Bazaar, que usam bzrliba estrutura de carregamento lento.
John Millikin
fonte
17
John, essa era uma questão completamente teórica, então eu não tinha nenhum código para descrever. No passado, sempre segui o PEP, mas escrevi hoje um código que me fez pensar se era a coisa certa a fazer. Obrigado pela ajuda.
Adam J. Forster
43
> Colocar as importações em uma função fará com que as chamadas para essa função demorem mais tempo. Na verdade, acho que esse custo é pago apenas uma vez. Eu li que o Python armazena em cache um módulo importado para que haja apenas um custo mínimo para importá-lo novamente.
moltenform
24
@halfhourhacks Python não vai voltar a importar o módulo, mas ainda tem que executar algumas instruções só para ver se existe o módulo / está em sys.modules / etc
John Millikin
24
-1. Colocar importações em uma função não necessariamente leva mais tempo. Por favor, veja minha resposta em outra pergunta.
aaronasterling 25/01
4
Um caso de uso é evitar importações circulares (geralmente não é sensato, mas às vezes é adequado). Às vezes, a classe A no módulo m1 chama um método na classe B no módulo m2, que constrói outra instância da classe A. Se o método na classe B, que constrói uma instância da classe A, a importação é executada apenas após a execução da função que constrói uma instância, a importação circular é evitada.
Sam Svenbjorgchristiensensen
80

Colocar a instrução de importação dentro de uma função pode impedir dependências circulares. Por exemplo, se você tiver 2 módulos, X.py e Y.py, e ambos precisarem importar um ao outro, isso causará uma dependência circular quando você importar um dos módulos, causando um loop infinito. Se você mover a instrução de importação em um dos módulos, ela não tentará importar o outro módulo até que a função seja chamada e esse módulo já seja importado, portanto, não haverá loop infinito. Leia aqui para mais informações - effbot.org/zone/import-confusion.htm

Moe
fonte
3
Sim, mas pode-se entrar no inferno da dependência.
eigenein
8
Se dois módulos precisarem se importar, algo está errado com o código.
Anna
A programação orientada a objetos geralmente me leva a dependências circulares. Uma classe de objeto vital pode ser importada para vários módulos. Para que esse objeto execute suas próprias tarefas, pode ser necessário acessar um ou mais desses módulos. Existem maneiras de evitá-lo, como enviar dados para o objeto através da função args, para permitir que ele acesse o outro módulo. Mas há momentos em que isso parece muito contra-intuitivo para o POO (o mundo exterior não precisa saber como está cumprindo a tarefa nessa função).
Robert
4
Quando X precisa de Y e Y precisa de X, elas são duas partes da mesma ideia (ou seja, devem ser definidas juntas) ou há uma abstração ausente.
GLRoman 16/01
59

Adotei a prática de colocar todas as importações nas funções que as utilizam, em vez de na parte superior do módulo.

O benefício que recebo é a capacidade de refatorar com mais confiabilidade. Quando movo uma função de um módulo para outro, sei que a função continuará funcionando com todo o seu legado de teste intacto. Se eu tenho minhas importações na parte superior do módulo, quando movo uma função, acho que acabo gastando muito tempo obtendo as importações do novo módulo completas e mínimas. Um IDE de refatoração pode tornar isso irrelevante.

Há uma penalidade de velocidade como mencionado em outro lugar. Eu medi isso na minha inscrição e achei insignificante para meus propósitos.

Também é bom poder ver todas as dependências do módulo antecipadamente, sem recorrer à pesquisa (por exemplo, grep). No entanto, o motivo pelo qual me preocupo com as dependências do módulo é geralmente porque estou instalando, refatorando ou movendo um sistema inteiro que compreende vários arquivos, não apenas um único módulo. Nesse caso, vou realizar uma pesquisa global de qualquer maneira para garantir que tenho as dependências no nível do sistema. Portanto, não encontrei importações globais para ajudar na minha compreensão de um sistema na prática.

Eu costumo colocar a importação de sysdentro da if __name__=='__main__'verificação e depois passar argumentos (como sys.argv[1:]) para uma main()função. Isso me permite usar mainem um contexto em sysque não foi importado.


fonte
4
Muitos IDEs facilitam a refatoração do código, otimizando e importando automaticamente os módulos necessários para o seu arquivo. Na maioria dos casos, PyCharm e Eclipse tomaram as decisões corretas para mim. Eu apostaria que existe uma maneira de obter o mesmo comportamento no emacs ou no vim.
precisa saber é o seguinte
3
Uma importação dentro de uma instrução if no espaço para nome global ainda é uma importação global. Isso imprimirá os argumentos (usando Python 3): def main(): print(sys.argv); if True: import sys; main();Você precisaria agrupar if __name__=='__main__'uma função para criar um novo espaço para nome.
precisa saber é
4
Isso me parece um excelente motivo para importar dentro de funções e não no escopo global. Estou surpreso que mais ninguém tenha mencionado fazê-lo pelo mesmo motivo. Existem desvantagens significativas, além de desempenho e verbosidade?
algal
@algal A desvantagem é que muitas pessoas python odeiam isso porque você viola o codex pep. Você precisa convencer os membros da sua equipe. A penalidade de desempenho é mínima. Às vezes é ainda mais rápido, consulte stackoverflow.com/a/4789963/362951
mit
Eu achei extremamente útil para a refatoração colocar as importações perto de onde eu as uso. Não é mais necessário rolar para cima e para trás tantas tims. Uso IDEs como pycharm ou wing ide e também refatoração, mas nem sempre quero confiar neles. Mover funções para outro módulo fica muito mais fácil com esse estilo de importação alternativo, como conseqüência refatoro muito mais.
mit
39

Na maioria das vezes, isso seria útil para clareza e sensibilidade, mas nem sempre é o caso. Abaixo estão alguns exemplos de circunstâncias em que as importações de módulos podem residir em outros lugares.

Primeiramente, você poderia ter um módulo com um teste de unidade do formulário:

if __name__ == '__main__':
    import foo
    aa = foo.xyz()         # initiate something for the test

Em segundo lugar, você pode ter um requisito para importar condicionalmente algum módulo diferente no tempo de execução.

if [condition]:
    import foo as plugin_api
else:
    import bar as plugin_api
xx = plugin_api.Plugin()
[...]

Provavelmente, há outras situações em que você pode colocar importações em outras partes do código.

ConcernedOfTunbridgeWells
fonte
14

A primeira variante é realmente mais eficiente que a segunda quando a função é chamada zero ou uma vez. Com a segunda e subsequentes invocações, no entanto, a abordagem "importar todas as chamadas" é realmente menos eficiente. Consulte este link para uma técnica de carregamento lento que combina o melhor de ambas as abordagens, realizando uma "importação lenta".

Mas há outras razões além da eficiência para você preferir uma à outra. Uma abordagem é tornar muito mais claro para quem lê o código as dependências que este módulo possui. Eles também têm características de falha muito diferentes - a primeira falhará no tempo de carregamento se não houver um módulo "datetime", enquanto a segunda não falhará até que o método seja chamado.

Nota adicionada: No IronPython, as importações podem ser um pouco mais caras que no CPython porque o código está basicamente sendo compilado à medida que está sendo importado.

Curt Hagenlocher
fonte
11
Não é verdade que o primeiro tenha um desempenho melhor: wiki.python.org/moin/PythonSpeed/…
Jason Baker
Ele funciona melhor se o método nunca for chamado porque a importação nunca acontece.
Curt Hagenlocher
É verdade, mas o desempenho é pior se o método for chamado mais de uma vez. E os benefícios de desempenho que você obteria ao não importar o módulo imediatamente são desprezíveis na maioria dos casos. As exceções seriam se o módulo for muito grande ou houver muitos desses tipos de funções.
Jason Baker
No mundo IronPython, as importações iniciais são muito mais caras do que no CPython;). O exemplo de "importação lenta" no seu link é provavelmente a melhor solução genérica geral.
Curt Hagenlocher
Espero que você não se importe, mas editei isso em sua postagem. Essa é uma informação útil para saber.
Jason Baker
9

Curt faz um bom argumento: a segunda versão é mais clara e falhará no tempo de carregamento, mais tarde e inesperadamente.

Normalmente, não me preocupo com a eficiência do carregamento de módulos, pois é (a) muito rápido e (b) ocorre principalmente na inicialização.

Se você precisar carregar módulos pesados ​​em momentos inesperados, provavelmente faz mais sentido carregá-los dinamicamente com a __import__função, e certifique - se de capturar ImportErrorexceções e manipulá-los de maneira razoável.

Dan Lenski
fonte
8

Eu não me preocuparia com a eficiência de carregar o módulo muito cedo. A memória ocupada pelo módulo não será muito grande (assumindo que seja modular o suficiente) e o custo de inicialização será insignificante.

Na maioria dos casos, você deseja carregar os módulos na parte superior do arquivo de origem. Para alguém lendo seu código, fica muito mais fácil dizer qual função ou objeto veio de qual módulo.

Um bom motivo para importar um módulo para outro local do código é se ele é usado em uma instrução de depuração.

Por exemplo:

do_something_with_x(x)

Eu poderia depurar isso com:

from pprint import pprint
pprint(x)
do_something_with_x(x)

Obviamente, o outro motivo para importar módulos em outras partes do código é se você precisar importá-los dinamicamente. Isso ocorre porque você praticamente não tem escolha.

Eu não me preocuparia com a eficiência de carregar o módulo muito cedo. A memória ocupada pelo módulo não será muito grande (assumindo que seja modular o suficiente) e o custo de inicialização será insignificante.

Jason Baker
fonte
Estamos falando de dezenas de milissegundos de custo de inicialização por módulo (na minha máquina). Isso nem sempre é insignificante, por exemplo, se isso afeta a capacidade de resposta de um aplicativo da Web a um clique do usuário.
Evgeni Sergeev 26/03
6

É uma troca, que apenas o programador pode decidir fazer.

O caso 1 economiza um pouco de memória e tempo de inicialização, não importando o módulo datetime (e fazendo a inicialização necessária) até ser necessário. Observe que fazer a importação 'somente quando chamado' também significa fazê-lo 'sempre que chamado'; portanto, cada chamada após a primeira ainda está incorrendo em uma sobrecarga adicional ao fazer a importação.

O caso 2 economiza tempo de execução e latência importando data e hora antecipadamente, para que not_often_called () retorne mais rapidamente quando for chamado e também não incorrendo na sobrecarga de uma importação em todas as chamadas.

Além da eficiência, é mais fácil ver as dependências do módulo antecipadamente se as instruções de importação estiverem ... antecipadamente. Escondê-los no código pode dificultar a localização fácil de quais módulos dependem os módulos.

Pessoalmente, geralmente sigo o PEP, exceto em coisas como testes de unidade e que nem sempre quero carregar porque sei que eles não serão usados, exceto pelo código de teste.

pjz
fonte
2
-1. A principal sobrecarga de importação ocorre apenas na primeira vez. O custo da sys.modulesconsulta do módulo pode ser facilmente compensado pela economia em apenas ter que procurar um nome local em vez de um nome global.
precisa saber é o seguinte
6

Aqui está um exemplo em que todas as importações estão no topo (esta é a única vez que eu preciso fazer isso). Quero poder encerrar um subprocesso no Un * x e no Windows.

import os
# ...
try:
    kill = os.kill  # will raise AttributeError on Windows
    from signal import SIGTERM
    def terminate(process):
        kill(process.pid, SIGTERM)
except (AttributeError, ImportError):
    try:
        from win32api import TerminateProcess  # use win32api if available
        def terminate(process):
            TerminateProcess(int(process._handle), -1)
    except ImportError:
        def terminate(process):
            raise NotImplementedError  # define a dummy function

(Em revisão: o que John Millikin disse.)

giltay
fonte
6

É como muitas outras otimizações - você sacrifica alguma legibilidade por velocidade. Como John mencionou, se você fez sua lição de casa de criação de perfil e achou uma alteração bastante útil o suficiente e precisa de velocidade extra, faça isso. Provavelmente seria bom anotar todas as outras importações:

from foo import bar
from baz import qux
# Note: datetime is imported in SomeClass below
Drew Stephens
fonte
4

A inicialização do módulo ocorre apenas uma vez - na primeira importação. Se o módulo em questão for da biblioteca padrão, você provavelmente o importará de outros módulos em seu programa. Para um módulo tão prevalente quanto a data e hora, também é provável que seja uma dependência para várias outras bibliotecas padrão. A declaração de importação custaria muito pouco, pois a inicialização do módulo já teria acontecido. Tudo o que está fazendo neste momento é vincular o objeto do módulo existente ao escopo local.

Associe essas informações ao argumento de legibilidade e eu diria que é melhor ter a declaração de importação no escopo do módulo.

Jeremy Brown
fonte
4

Apenas para completar a resposta de Moe e a pergunta original:

Quando temos que lidar com dependências circulares, podemos fazer alguns "truques". Supondo que estamos trabalhando com módulos a.pye b.pyque contêm x()eb y(), respectivamente. Então:

  1. Podemos mover um dos from importsna parte inferior do módulo.
  2. Podemos mover uma parte from importsinterna da função ou método que realmente exige a importação (isso nem sempre é possível, pois você pode usá-lo em vários locais).
  3. Podemos alterar um dos dois from importspara ser uma importação que se parece com:import a

Então, para concluir. Se você não está lidando com dependências circulares e está fazendo algum tipo de truque para evitá-las, é melhor colocar todas as suas importações no topo por causa dos motivos já explicados em outras respostas a esta pergunta. E, por favor, ao fazer esses "truques" incluir um comentário, é sempre bem-vindo! :)

Caumons
fonte
4

Além das excelentes respostas já dadas, vale ressaltar que a colocação das importações não é apenas uma questão de estilo. Às vezes, um módulo possui dependências implícitas que precisam ser importadas ou inicializadas primeiro, e uma importação de nível superior pode levar a violações da ordem de execução necessária.

Esse problema geralmente ocorre na API Python do Apache Spark, onde você precisa inicializar o SparkContext antes de importar qualquer pacote ou módulo pyspark. É melhor colocar as importações do pyspark em um escopo em que o SparkContext esteja garantido disponível.

Paulo
fonte
4

Fiquei surpreso ao não ver os números de custo reais das verificações de carga repetidas já publicados, embora haja muitas boas explicações sobre o que esperar.

Se você importa na parte superior, recebe o acerto de carga, não importa o quê. Isso é bem pequeno, mas geralmente em milissegundos, não em nanossegundos.

Se você importar dentro de uma (s) função (s), será possível carregar apenas se e quando uma dessas funções for chamada pela primeira vez. Como muitos salientaram, se isso não acontecer, você economiza o tempo de carregamento. Mas se a função (s) se chamado de um monte, você toma um repetida embora hit muito menor (para verificar que ele tenha sido carregado, não para realmente re-loading). Por outro lado, como @aaronasterling apontou, você também economiza um pouco porque a importação em uma função permite que a função use pesquisas de variáveis ​​locais um pouco mais rápidas para identificar o nome posteriormente ( http://stackoverflow.com/questions/477096/python- estilo de codificação de importação / 4789963 # 4789963 ).

Aqui estão os resultados de um teste simples que importa algumas coisas de dentro de uma função. Os tempos relatados (no Python 2.7.14 em um Intel Core i7 de 2,3 GHz) são mostrados abaixo (a segunda chamada, recebendo mais do que as chamadas posteriores, parece consistente, embora eu não saiba o porquê).

 0 foo:   14429.0924 µs
 1 foo:      63.8962 µs
 2 foo:      10.0136 µs
 3 foo:       7.1526 µs
 4 foo:       7.8678 µs
 0 bar:       9.0599 µs
 1 bar:       6.9141 µs
 2 bar:       7.1526 µs
 3 bar:       7.8678 µs
 4 bar:       7.1526 µs

O código:

from __future__ import print_function
from time import time

def foo():
    import collections
    import re
    import string
    import math
    import subprocess
    return

def bar():
    import collections
    import re
    import string
    import math
    import subprocess
    return

t0 = time()
for i in xrange(5):
    foo()
    t1 = time()
    print("    %2d foo: %12.4f \xC2\xB5s" % (i, (t1-t0)*1E6))
    t0 = t1
for i in xrange(5):
    bar()
    t1 = time()
    print("    %2d bar: %12.4f \xC2\xB5s" % (i, (t1-t0)*1E6))
    t0 = t1
TextGeek
fonte
As mudanças no tempo de execução provavelmente se devem ao dimensionamento da frequência da CPU em resposta à carga. É melhor iniciar os testes de velocidade com um segundo de trabalho ocupado para aumentar a velocidade do clock da CPU.
Han-Kwang Nienhuys
3

Não pretendo dar uma resposta completa, porque outros já o fizeram muito bem. Eu só quero mencionar um caso de uso quando achar especialmente útil importar módulos dentro de funções. Meu aplicativo usa pacotes e módulos python armazenados em determinado local como plugins. Durante a inicialização do aplicativo, o aplicativo percorre todos os módulos no local e os importa, depois olha dentro dos módulos e se encontra alguns pontos de montagem para os plug-ins (no meu caso, é uma subclasse de uma determinada classe base com uma classe única). ID) registra-os. O número de plugins é grande (agora dezenas, mas talvez centenas no futuro) e cada um deles é usado muito raramente. A importação de bibliotecas de terceiros na parte superior dos meus módulos de plug-in foi um pouco penosa durante a inicialização do aplicativo. Especialmente, algumas bibliotecas de terceiros são pesadas para importar (por exemplo, a importação de plotly tenta se conectar à Internet e baixar algo que estava adicionando cerca de um segundo à inicialização). Ao otimizar as importações (chamando-as apenas nas funções em que são usadas) nos plugins, consegui reduzir a inicialização de 10 segundos para cerca de 2 segundos. Essa é uma grande diferença para meus usuários.

Portanto, minha resposta é não, nem sempre coloque as importações na parte superior dos seus módulos.

VK
fonte
3

É interessante que nenhuma resposta mencionou o processamento paralelo até agora, onde pode ser NECESSÁRIO que as importações estejam na função, quando o código de função serializado é o que está sendo enviado para outros núcleos, por exemplo, como no caso do ipyparallel.

K.-Michael Aye
fonte
1

Pode haver um ganho de desempenho importando variáveis ​​/ escopo local dentro de uma função. Isso depende do uso da coisa importada dentro da função. Se você estiver repetindo várias vezes e acessando um objeto global do módulo, importá-lo como local pode ajudar.

test.py

X=10
Y=11
Z=12
def add(i):
  i = i + 10

runlocal.py

from test import add, X, Y, Z

    def callme():
      x=X
      y=Y
      z=Z
      ladd=add 
      for i  in range(100000000):
        ladd(i)
        x+y+z

    callme()

run.py

from test import add, X, Y, Z

def callme():
  for i in range(100000000):
    add(i)
    X+Y+Z

callme()

Uma vez no Linux mostra um pequeno ganho

/usr/bin/time -f "\t%E real,\t%U user,\t%S sys" python run.py 
    0:17.80 real,   17.77 user, 0.01 sys
/tmp/test$ /usr/bin/time -f "\t%E real,\t%U user,\t%S sys" python runlocal.py 
    0:14.23 real,   14.22 user, 0.01 sys

real é relógio de parede. usuário é tempo no programa. sys é hora de chamadas do sistema.

https://docs.python.org/3.5/reference/executionmodel.html#resolution-of-names

quiet_penguin
fonte
1

Legibilidade

Além do desempenho da inicialização, há um argumento de legibilidade a ser feito para localizar importinstruções. Por exemplo, considere os números de linha python 1283 a 1296 no meu primeiro projeto python atual:

listdata.append(['tk font version', font_version])
listdata.append(['Gtk version', str(Gtk.get_major_version())+"."+
                 str(Gtk.get_minor_version())+"."+
                 str(Gtk.get_micro_version())])

import xml.etree.ElementTree as ET

xmltree = ET.parse('/usr/share/gnome/gnome-version.xml')
xmlroot = xmltree.getroot()
result = []
for child in xmlroot:
    result.append(child.text)
listdata.append(['Gnome version', result[0]+"."+result[1]+"."+
                 result[2]+" "+result[3]])

Se a importdeclaração estivesse no topo do arquivo, eu teria que percorrer um longo caminho, ou pressionar Home, para descobrir o que ETera. Então eu teria que voltar para a linha 1283 para continuar lendo o código.

De fato, mesmo que a importinstrução estivesse no topo da função (ou classe), como muitos a colocariam, seria necessária uma paginação para cima e para trás.

A exibição do número da versão do Gnome raramente será feita, portanto o importinício do arquivo apresenta um atraso de inicialização desnecessário.

WinEunuuchs2Unix
fonte
0

Gostaria de mencionar um caso meu, muito parecido com o mencionado por John Millikin e @VK:

Importações opcionais

Faço análise de dados com o Jupyter Notebook e uso o mesmo notebook IPython como modelo para todas as análises. Em algumas ocasiões, preciso importar o Tensorflow para executar algumas execuções rápidas do modelo, mas às vezes trabalho em locais onde o tensorflow não está configurado / é lento para importar. Nesses casos, encapsulo minhas operações dependentes do fluxo de tensor em uma função auxiliar, importo o fluxo de tensor dentro dessa função e vinculo-o a um botão.

Dessa maneira, eu poderia fazer "reiniciar e executar tudo" sem ter que esperar pela importação ou ter que continuar o restante das células quando ela falhar.

Cedro
fonte
0

Esta é uma discussão fascinante. Como muitos outros, eu nunca havia considerado esse tópico. Eu fui obrigado a ter as importações nas funções por querer usar o Django ORM em uma das minhas bibliotecas. Eu estava tendo que ligar django.setup()antes de importar minhas classes de modelo e, como estava no topo do arquivo, ele estava sendo arrastado para um código de biblioteca completamente não-Django, devido à construção do injetor de IoC.

Eu meio que entrei um pouco e acabei colocando o django.setup()construtor singleton e a importação relevante na parte superior de cada método de classe. Agora isso funcionou bem, mas me deixou desconfortável porque as importações não estavam no topo e também comecei a me preocupar com o tempo extra atingido. Então eu vim aqui e li com grande interesse que todos entendem isso.

Eu tenho um longo histórico de C ++ e agora uso Python / Cython. Minha opinião é que, por que não colocar as importações na função, a menos que isso cause um gargalo com perfil. É como declarar espaço para variáveis ​​antes que você precise delas. O problema é que tenho milhares de linhas de código com todas as importações no topo! Então, acho que vou fazê-lo a partir de agora e alterar o arquivo ímpar aqui e ali quando estiver passando e tiver tempo.

LJHW
fonte