É python importar funções internas?

126

O PEP 8 diz:

  • 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.

Na ocasião, violei o PEP 8. Algumas vezes, importo coisas dentro de funções. Como regra geral, faço isso se houver uma importação usada apenas em uma única função.

Alguma opinião?

EDIT (a razão pela qual sinto a importação de funções pode ser uma boa ideia):

Razão principal: pode tornar o código mais claro.

  • Ao olhar para o código de uma função, eu poderia me perguntar: "O que é função / classe xxx?" (xxx sendo usado dentro da função). Se eu tiver todas as minhas importações na parte superior do módulo, preciso procurar lá para determinar o que é xxx. Este é mais um problema ao usar from m import xxx. Ver m.xxxna função provavelmente me diz mais. Dependendo do que mé: É um módulo / pacote de nível superior bem conhecido ( import m)? Ou é um submódulo / pacote ( from a.b.c import m)?
  • Em alguns casos, ter essas informações extras ("O que é xxx?") Perto de onde xxx é usado pode facilitar a compreensão da função.
codeape
fonte
2
e você faz isso para desempenho?
Macarse
4
Eu sinto que isso torna o código mais claro em alguns casos. Eu acho que o desempenho bruto cai ao importar em uma função (uma vez que a instrução de importação será executada toda vez que a função for chamada).
Codeape 21/06/09
Você pode responder "O que é função / classe xxx?" usando a sintaxe de importação xyz em vez do de importação xyz abc sintaxe
Tom Leys
1
Se a clareza é o único fator, U também pode incluir um comentário relevante para esse efeito. ;)
Lakshman Prasad
5
@becomingGuru: Claro, mas os comentários podem ficar fora de sincronia com a realidade ...
codeape

Respostas:

88

A longo prazo, acho que você apreciará ter a maior parte de suas importações na parte superior do arquivo, para que você possa ver rapidamente o quão complicado é o seu módulo pelo que ele precisa importar.

Se eu estiver adicionando novo código a um arquivo existente, normalmente faço a importação onde for necessário e, se o código permanecer, tornarei as coisas mais permanentes, movendo a linha de importação para a parte superior do arquivo.

Outro ponto, eu prefiro obter uma ImportError exceção antes de qualquer código ser executado - como uma verificação de integridade, por isso é outro motivo para importar na parte superior.

Eu uso pyCheckerpara verificar se há módulos não utilizados.

Peter Ericson
fonte
47

Há duas ocasiões em que eu viole o PEP 8 a esse respeito:

  • Importações circulares: o módulo A importa o módulo B, mas algo no módulo B precisa do módulo A (embora isso geralmente seja um sinal de que preciso refatorar os módulos para eliminar a dependência circular)
  • Inserindo um ponto de interrupção pdb: import pdb; pdb.set_trace()Isso é útil porque eu não quero colocar import pdbno topo de todos os módulos que eu possa querer depurar e é fácil lembrar de remover a importação ao remover o ponto de interrupção.

Fora desses dois casos, é uma boa ideia colocar tudo no topo. Isso torna as dependências mais claras.

Rick Copeland
fonte
7
Concordo que torna as dependências mais claras em relação ao módulo como um todo. Mas acredito que ele pode tornar o código menos claro no nível da função para importar tudo no topo. Ao observar o código de uma função, você pode se perguntar: "O que é função / classe xxx?" (xxx é usado dentro da função). E você tem que olhar na parte superior do arquivo para ver de onde vem o xxx. Esse é mais um problema ao usar from m import xxx. Ver m.xxx informa mais - pelo menos se não houver dúvida sobre o que é m.
codeape 21/06/09
20

Aqui estão os quatro casos de uso de importação que usamos

  1. import(e from x import ye import x as y) no topo

  2. Opções para importação. No topo.

    import settings
    if setting.something:
        import this as foo
    else:
        import that as foo
  3. Importação condicional. Usado com bibliotecas JSON, XML e similares. No topo.

    try:
        import this as foo
    except ImportError:
        import that as foo
  4. Importação dinâmica. Até agora, temos apenas um exemplo disso.

    import settings
    module_stuff = {}
    module= __import__( settings.some_module, module_stuff )
    x = module_stuff['x']

    Observe que essa importação dinâmica não traz código, mas traz estruturas de dados complexas escritas em Python. É como um dado em conserva, exceto que o coletamos manualmente.

    Também está, mais ou menos, na parte superior de um módulo


Aqui está o que fazemos para tornar o código mais claro:

  • Mantenha os módulos curtos.

  • Se eu tiver todas as minhas importações na parte superior do módulo, preciso procurar lá para determinar o que é um nome. Se o módulo é curto, é fácil de fazer.

  • Em alguns casos, ter essas informações extras próximas de onde um nome é usado pode facilitar a compreensão da função. Se o módulo é curto, é fácil de fazer.

S.Lott
fonte
Manter os módulos curtos é, obviamente, uma ideia muito boa. Mas, para obter o benefício de sempre ter "informações de importação" para funções disponíveis, o comprimento máximo do módulo teria que ser uma tela (provavelmente 100 linhas no máximo). E isso provavelmente seria muito curto para ser prático na maioria dos casos.
codeape 22/06/09
Suponho que você possa levar isso a um extremo lógico. Eu acho que pode haver um ponto de equilíbrio em que seu módulo é "pequeno o suficiente" para que você não precise de técnicas de importação sofisticadas para gerenciar a complexidade. Nosso tamanho médio do módulo é - coincidentemente - cerca de 100 linhas.
22410 S.Lott
8

Um aspecto a ter em mente: importações desnecessárias podem causar problemas de desempenho. Portanto, se essa é uma função que será chamada com frequência, é melhor colocar a importação no topo. Claro que isso é uma otimização; portanto, se houver um caso válido de que importar dentro de uma função seja mais claro do que importar na parte superior de um arquivo, isso supera o desempenho na maioria dos casos.

Se você estiver usando o IronPython, me disseram que é melhor importar funções internas (já que a compilação de código no IronPython pode ser lenta). Assim, você pode conseguir uma maneira de importar funções internas. Mas, além disso, eu argumentaria que não vale a pena lutar contra convenções.

Como regra geral, faço isso se houver uma importação usada apenas em uma única função.

Outro ponto que eu gostaria de destacar é que esse pode ser um possível problema de manutenção. O que acontece se você adicionar uma função que usa um módulo que foi usado anteriormente por apenas uma função? Você vai se lembrar de adicionar a importação na parte superior do arquivo? Ou você vai verificar todas as funções em busca de importações?

FWIW, há casos em que faz sentido importar dentro de uma função. Por exemplo, se você deseja definir o idioma em cx_Oracle, é necessário definir uma _variável de ambiente NLS LANG antes de ser importada. Assim, você pode ver um código como este:

import os

oracle = None

def InitializeOracle(lang):
    global oracle
    os.environ['NLS_LANG'] = lang
    import cx_Oracle
    oracle = cx_Oracle
Jason Baker
fonte
2
Concordo com o seu problema de manutenção. O código de refatoração pode ser um pouco problemático. Se eu adicionar uma segunda função que usa um módulo usado anteriormente apenas por uma função - movo a importação para o topo ou quebro minha própria regra geral importando o módulo na segunda função também.
codeape 21/06/09
2
Eu acho que o argumento de desempenho também pode ir para o outro lado. A importação de um módulo pode ser demorada. Em sistemas de arquivos distribuídos, como os dos supercomputadores, a importação de um grande módulo como o numpy pode levar alguns segundos. Se um módulo for necessário apenas para uma única função raramente usada, a importação da função acelerará significativamente o caso comum.
amaurea
6

Eu quebrei essa regra antes para módulos que são autotestes. Ou seja, eles normalmente são usados ​​apenas para suporte, mas eu defino um main para eles, para que, se você os executar por si próprios, possa testar sua funcionalidade. Nesse caso, às vezes eu importo getopte cmdapenas no principal, porque quero que fique claro para alguém que está lendo o código que esses módulos não têm nada a ver com a operação normal do módulo e estão sendo incluídos apenas para teste.

Dan Lew
fonte
5

Vindo da pergunta sobre o carregamento do módulo duas vezes - Por que não os dois?

Uma importação na parte superior do script indicará as dependências e outra importação na função, tornando essa função mais atômica, enquanto aparentemente não causa nenhuma desvantagem no desempenho, uma vez que uma importação consecutiva é barata.

IljaBek
fonte
3

Contanto que seja importe não from x import *, você deve colocá-los no topo. Ele adiciona apenas um nome ao namespace global e você segue o PEP 8. Além disso, se precisar mais tarde em outro lugar, não precisará mudar nada.

Não é grande coisa, mas como quase não há diferença, sugiro fazer o que o PEP 8 diz.

Javier
fonte
3
Na verdade, colocar from x import *dentro de uma função gerará um SyntaxWarning, pelo menos no 2.5.
21411 Rick Copeland
3

Dê uma olhada na abordagem alternativa usada no sqlalchemy: injeção de dependência:

@util.dependencies("sqlalchemy.orm.query")
def merge_result(query, *args):
    #...
    query.Query(...)

Observe como a biblioteca importada é declarada em um decorador e passada como argumento para a função !

Essa abordagem torna o código mais limpo e também funciona 4,5 vezes mais rápido que umimport declaração!

Referência: https://gist.github.com/kolypto/589e84fbcfb6312532658df2fabdb796

Kolypto
fonte
2

Nos módulos que são 'normais' e podem ser executados (ou seja, possuem if __name__ == '__main__': seção), geralmente importo módulos que são usados ​​apenas ao executar o módulo na seção principal.

Exemplo:

def really_useful_function(data):
    ...


def main():
    from pathlib import Path
    from argparse import ArgumentParser
    from dataloader import load_data_from_directory

    parser = ArgumentParser()
    parser.add_argument('directory')
    args = parser.parse_args()
    data = load_data_from_directory(Path(args.directory))
    print(really_useful_function(data)


if __name__ == '__main__':
    main()
codeape
fonte
1

Há outro caso (provavelmente "esquina") em que pode ser benéfico import funções raramente usadas: diminuir o tempo de inicialização.

Eu bati nessa parede uma vez com um programa bastante complexo sendo executado em um pequeno servidor de IoT, aceitando comandos de uma linha serial e executando operações, possivelmente operações muito complexas.

Colocar importinstruções no topo dos arquivos para que todas as importações sejam processadas antes do início do servidor; desde importlista incluía jinja2, lxml, signxmle outros "pesos pesados" (e SoC não era muito poderoso), isso significava minutos antes da primeira instrução foi realmente executado.

OTOH colocando a maioria das importações em funções, consegui manter o servidor "ativo" na linha serial em segundos. É claro que quando os módulos foram realmente necessários, tive que pagar o preço (Nota: isso também pode ser mitigado gerando uma tarefa em segundo plano fazendo imports em tempo ocioso).

ZioByte
fonte