Pergunta

PEP 08 afirma:

As importações são sempre colocados no topo do arquivo, logo após os comentários do módulo e docstrings, e antes globals módulos e constantes.

No entanto, se o método / classe / função que eu estou importando só é usado em casos raros, certamente é mais eficiente para fazer a importação quando é necessário?

Não é esta:

class SomeClass(object):

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

mais eficiente do que isso?

from datetime import datetime

class SomeClass(object):

    def not_often_called(self)
        self.datetime = datetime.now()
Foi útil?

Solução

Módulo de importação é bastante rápido, 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 dentro de uma função fará com que as chamadas para essa função para levar mais tempo.

Então, se você se preocupa com a eficiência, colocar as importações no topo. Só movê-los para uma função, se seus programas de perfil que ajudaria (você fez perfil para ver onde melhor para melhorar o desempenho, certo ??)


As melhores razões que eu vi para realizar importações preguiçosos são:

  • Suporte biblioteca opcional. Se o seu código tem vários caminhos que usam diferentes bibliotecas, não quebrar se uma biblioteca opcional não está instalado.
  • No __init__.py de um plug-in, que pode ser importado, mas não realmente utilizados. Exemplos são plugins bazar, quadro carregamento lento que o uso de bzrlib.

Outras dicas

Colocar a declaração de importação dentro de uma função pode evitar dependências circulares. Por exemplo, se você tem 2 módulos, X.py e Y.py, e ambos necessidade de importar uns aos outros, isso vai causar uma dependência circular quando você importa um dos módulos causando um loop infinito. Se você mover a declaração de importação em um dos módulos, então não vai tentar importar o outro módulo até que a função é chamada, e que módulo já serão importados, laço, de forma nenhuma infinito. Leia aqui para mais - effbot.org/zone/import-confusion.htm

Eu adotaram a prática de colocar todas as importações nas funções que usá-los, em vez de na parte superior do módulo.

O benefício que recebo é a capacidade de refatorar mais confiável. Quando eu mover uma função de um módulo para outro, eu sei que a função vai continuar a trabalhar com toda a sua herança de testar intacta. Se eu tiver meus importações no topo do módulo, quando eu passar uma função, eu acho que eu acabar gastando muito tempo recebendo importações do novo módulo completo e mínimo. A IDE refatoração pode fazer isso irrelevante.

Existe uma penalidade de velocidade como mencionado em outro lugar. Eu medi isso no meu aplicativo e achei que era ser insignificante para os meus propósitos.

Também é bom ser capaz de ver todas as dependências do módulo na frente, sem recorrer a procurar (por exemplo grep). No entanto, a razão pela qual eu me preocupo com dependências do módulo é geralmente porque eu estou instalando, refatoração, ou mover todo um sistema compreende vários arquivos, e não apenas um único módulo. Nesse caso, eu estou indo para executar uma pesquisa global de qualquer maneira para ter certeza que tenho as dependências de nível de sistema. Então, eu não encontrei as importações globais para ajudar a minha compreensão de um sistema na prática.

Eu costumo colocar a importação de sys dentro do cheque if __name__=='__main__' e, em seguida, passar argumentos (como sys.argv[1:]) para uma função main(). Isso me permite usar main num contexto em que sys não foi importado.

Na maioria das vezes isso seria útil para a clareza e sensata a fazer, mas não é sempre o caso. Abaixo estão alguns exemplos de circunstâncias em que as importações do módulo pode viver em outro lugar.

Em primeiro lugar, você poderia ter um módulo com um teste de unidade da forma:

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

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

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

Há provavelmente outras situações onde você pode colocar as importações de outras partes no código.

A primeira variante é realmente mais eficiente do que o segundo, quando a função é chamada seja zero ou uma vez. Com a segunda e posteriores invocações, no entanto, a abordagem de "importar cada chamada" é na verdade menos eficiente. Consulte este link para uma técnica de carregamento lento que combina o melhor de ambos se aproxima, fazendo uma "importação preguiçoso".

Mas há outras que a eficiência razões pelas quais você pode preferir um sobre o outro. Uma abordagem é torna muito mais claro para alguém que lê o código como para as dependências que este módulo tem. Eles também têm características muito diferentes de falha - o primeiro falhará em tempo de carregamento se não há "data e hora" módulo enquanto o segundo não irá falhar até que o método é chamado

.

Adicionado Nota:. No IronPython, as importações podem ser um pouco mais caro do que em CPython porque o código é basicamente a ser compilado como está sendo importado

Curt faz um ponto bom:. A segunda versão é mais clara e irá falhar em tempo de carregamento ou mais tarde, e inesperadamente

Normalmente eu não me preocupo com a eficiência dos módulos de carga, uma vez que do (a) bastante rápido, e (b) na maior parte só acontece na inicialização.

Se você tiver que carregar módulos pesados ??em momentos inesperados, provavelmente faz mais sentido para carregá-los dinamicamente com a função __import__, e ser certeza para exceções captura ImportError, e tratá-los de forma razoável .

Eu não me preocuparia com a eficiência de carregar o módulo na frente muito. A memória ocupada pelo módulo não será muito grande (assumindo que é bastante modular) e os custos de arranque será insignificante.

Na maioria dos casos você deseja carregar os módulos no topo do arquivo de origem. Para alguém ler o seu código, ele torna muito mais fácil de dizer o que função ou objeto veio do que módulo.

Uma razão boa para importar um módulo em outras partes do código é se ele é usado em uma declaraçã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)

Claro, a outra razão para módulos de importação em outras partes do código é se você precisa importá-los de forma dinâmica. Isso é porque você praticamente não têm qualquer escolha.

Eu não me preocuparia com a eficiência de carregar o módulo na frente muito. A memória ocupada pelo módulo não será muito grande (assumindo que é bastante modular) e os custos de arranque será insignificante.

É uma troca, que só o programador pode decidir fazer.

Caso 1 economiza um pouco de memória e tempo de inicialização não importando o módulo datetime (e fazer o que a inicialização pode-se exigir) até ser necessário. Note-se que fazer a importação 'apenas quando chamado' também significa fazê-lo 'cada vez quando chamado', então cada chamada após o primeiro ainda é incorrer a sobrecarga adicional de fazer a importação.

Caso 2 poupar algum tempo de execução e latência por meio da importação de data e hora com antecedência para que not_often_called () irá retornar mais rapidamente quando é chamado, e também por não incorrer a sobrecarga de uma importação em cada chamada.

Além de eficiência, é mais fácil ver dependências de módulo na frente, se as instruções de importação são ... na frente. Escondendo-los no código pode torná-lo mais difícil de encontrar facilmente o que módulos algo depende.

Pessoalmente, eu geralmente seguem o PEP, exceto para coisas como testes de unidade e de tal forma que eu não quero sempre carregado porque eu sei que eles não vão ser usados, exceto para o código de teste.

Aqui está um exemplo onde todos os importações estão no topo (esta é a única vez que eu precisava fazer isso). Eu quero ser capaz de terminar um subprocesso em ambos Un * x e 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 John Millikin disse .)

Isto é como muitas outras otimizações - você sacrificar alguma a legibilidade para a velocidade. Como John mencionado, se você tiver feito sua lição de casa perfis e encontrei este para ser uma mudança bastante significativa útil e que você precisa a velocidade extra, então vá para ele. Provavelmente seria bom para colocar uma nota com todas as outras importações:

from foo import bar
from baz import qux
# Note: datetime is imported in SomeClass below

Módulo de inicialização ocorre apenas uma vez - na primeira importação. Se o módulo em questão é da biblioteca padrão, então você provavelmente vai importar de outros módulos no seu programa também. Para um módulo tão prevalente como data e hora, é também provável uma dependência para uma série de outras bibliotecas padrão. A declaração de importação custaria muito pouco, em seguida, uma vez que o módulo intialization já teria acontecido. Tudo o que está a fazer neste momento é obrigatório o objeto módulo existente ao âmbito local.

Casal essas informações com o argumento para facilitar a leitura e eu diria que é melhor ter a instrução de importação no âmbito do módulo.

Apenas para completar resposta de Moe e a pergunta original:

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

  1. Podemos mover um dos from imports na parte inferior do módulo.
  2. Podemos mover um dos from imports dentro da função ou método que é, na verdade, exigindo a importação (isso nem sempre é possível, como você pode usá-lo a partir de vários lugares).
  3. Podemos alterar um dos dois from imports ser uma importação que se parece com: import a

Assim, para concluir. Se você não está lidando com dependências circulares e fazendo algum tipo de truque para evitá-los, então é melhor colocar todas as suas importações no topo por causa das razões já explicadas em outras respostas para essa pergunta. E, por favor, ao fazer isso "truques" incluir um comentário, ele sempre é bem-vindo! :)

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

Esta questão muitas vezes surge na API Python do Apache Spark, onde você precisa inicializar o SparkContext antes de importar os pacotes pyspark ou módulos. É melhor importações lugar pyspark em um âmbito onde o SparkContext é garantido para estar disponível.

Eu não aspiro para fornecer resposta completa, porque outros já fizeram isso muito bem. Eu só quero mencionar um caso de uso quando eu encontrar especialmente útil para módulos de importação dentro de funções. Meu aplicativo usa pacotes Python e módulos armazenados em determinado local como plugins. Durante a inicialização do aplicativo, o aplicativo anda através de todos os módulos no local e as importações deles, então ele olha dentro dos módulos e, se encontrar alguns pontos de montagem para os plugins (no meu caso, é uma subclasse de uma certa classe de base tendo uma única ID) registra-los. O número de plugins é grande (agora dezenas, mas talvez centenas no futuro) e cada um deles é usado muito raramente. Tendo importações de bibliotecas de terceiros no topo dos meus módulos plugin foi uma penalidade pouco durante a inicialização do aplicativo. Especialmente alguns de terceiros bibliotecas são pesados ??para importação (por exemplo, importação de plotly ainda tenta se conectar a internet e baixar algo que foi adicionando cerca de um segundo para inicialização). Ao otimizar as importações (chamando-os apenas nas funções onde são usados) nos plugins eu consegui encolher a inicialização de 10 segundos a cerca de 2 segundos. Isso é uma grande diferença para meus usuários.

Assim, a minha resposta é não, não sempre colocar as importações no topo de seus módulos.

Eu fiquei surpreso de não ver os números de custo real para os repetidos de carga-cheques já publicado, embora existam muitos bons explicações sobre o que esperar.

Se você importar no topo, você toma a carga atingiu não importa o que. Isso é muito pequeno, mas comumente nos milissegundos, não nanossegundos.

Se você importar dentro de uma função (s), então você só pode tomar o hit para o carregamento se e quando uma dessas funções é chamado pela primeira vez. Como muitos assinalaram, se isso não acontecer nada, 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 tem foi carregado; não para realmente re-loading). Por outro lado, como @aaronasterling apontou você também economiza um pouco, porque a importação de dentro de uma função permite o uso função ligeiramente mais rápido variável local pesquisas para identificar o nome mais tarde (http://stackoverflow.com/questions/477096/python-import-coding-style/4789963#4789963 ).

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

 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

É interessante que não uma única resposta mencionado processamento paralelo até agora, onde ele pode ser necessário que as importações estão na função, quando o código de função serializado é o que está sendo empurrado para outros núcleos, por exemplo, como no caso de ipyparallel.

Não pode haver um ganho de desempenho através da importação de variáveis ??locais / escopo dentro de uma função. Isso depende do uso da coisa importada dentro da função. Se você está looping muitas vezes e acessar um objeto global módulo, importá-lo como pode ajudar local.

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. user é o tempo em programa. sys é hora de chamadas do sistema.

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

Gostaria de mencionar um usecase meu, muito semelhantes aos mencionados por @ John Millikin e @ V.K. :

Opcional Importações

Eu faço análise de dados com Jupyter Notebook, e eu uso o mesmo notebook IPython como um modelo para todas as análises. Em algumas ocasiões, eu preciso importar Tensorflow fazer algumas modelo rápidas corridas, mas às vezes eu trabalho em locais onde tensorflow não está configurado / é lento para importação. Nesses casos, eu encapsular minhas operações Tensorflow-dependentes em uma função auxiliar, tensorflow importação dentro dessa função, e vinculá-lo a um botão.

Desta forma, eu poderia fazer "restart-and-run-all" sem ter que esperar para a importação, ou ter que retomar o resto das células quando ele falhar.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top