Importações circulares (ou cíclicas) em Python


352

O que acontecerá se dois módulos se importarem?

Para generalizar o problema, e as importações cíclicas no Python?



11
também apenas como referência, parece que importações circulares são permitidas no python 3.5 (e provavelmente além), mas não 3.4 (e provavelmente abaixo).
Charlie Parker

4
Estou usando python 3.7.2 e ainda estou tendo um erro de tempo de execução devido a dependências circulares.
Richard Whitehead

Respostas:


281

Houve uma discussão muito boa sobre isso no comp.lang.python no ano passado. Responde bem à sua pergunta.

As importações são bem diretas, na verdade. Lembre-se do seguinte:

'import' e 'from xxx import yyy' são instruções executáveis. Eles são executados quando o programa em execução atinge essa linha.

Se um módulo não estiver em sys.modules, uma importação criará a nova entrada do módulo em sys.modules e, em seguida, executará o código no módulo. Ele não retorna o controle ao módulo de chamada até que a execução seja concluída.

Se um módulo existe em sys.modules, uma importação simplesmente retorna esse módulo, independentemente de ter ou não concluído a execução. Essa é a razão pela qual as importações cíclicas podem retornar módulos que parecem estar parcialmente vazios.

Por fim, o script em execução é executado em um módulo chamado __main__. A importação do script com seu próprio nome criará um novo módulo não relacionado a __main__.

Reúna esse lote e você não terá surpresas ao importar módulos.


13
@meawoppl Você poderia expandir esse comentário, por favor? Quão especificamente eles mudaram?
Dan Schien

3
A partir de agora, a única referência a importações circulares no python3 "O que há de novo?" páginas está no 3.5 . Ele diz "Importações circulares envolvendo importações relativas agora são suportadas". @meawoppl você encontrou mais alguma coisa que não está listada nestas páginas?
Zezollo

4
Eles são def. não suportado em 3.0-3.4. Ou pelo menos a semântica para o sucesso é diferente. Aqui está uma sinopse que descobri que não menciona as alterações 3.5. gist.github.com/datagrok/40bf84d5870c41a77dc6
meawoppl

Por favor, você pode expandir este "Finalmente, o script de execução é executado em um módulo chamado main , importando o script com seu próprio nome, criará um novo módulo não relacionado ao main .". Então, digamos que o arquivo é a.py e quando é executado como ponto de entrada principal, ele é o principal agora se tiver código como de uma variável de importação. Então o mesmo arquivo 'a.py' será carregado na tabela sys modules? Então, isso significa que, se houver uma declaração print, ela será executada duas vezes? Uma vez para o arquivo principal e novamente quando a importação é encontrada?
variável

Essa resposta é de 10 anos de idade, e eu gostaria de um update modernizado para garantir que ele permaneça correta em várias versões do Python, 2.x ou 3.x
Fallenreaper

296

Se você fizer por import foodentro bare por import bardentro foo, funcionará bem. Quando algo realmente for executado, os dois módulos estarão totalmente carregados e terão referências um ao outro.

O problema é quando você faz from foo import abce from bar import xyz. Porque agora cada módulo exige que o outro módulo já seja importado (para que o nome que estamos importando exista) antes que ele possa ser importado.


27
Parece que from foo import *e from bar import *também irá funcionar bem.
Akavall

11
Verifique a edição da postagem acima usando a.py/b.py. Ele não usa from x import y, e ainda assim recebe o erro circular de importação
Greg Ennis

2
Isso não é inteiramente verdade. Assim como import * from, se você tentar acessar um elemento na importação circular, no nível superior, portanto, antes de o script concluir sua execução, você terá o mesmo problema. Por exemplo, se você estiver configurando um pacote global em um pacote de outro e os dois se incluírem. Eu estava fazendo isso para criar uma fábrica desleixada para um objeto na classe base, onde esse objeto poderia ser uma de várias subclasses e o código de uso não precisava estar ciente de qual estava realmente criando.
AaronM

3
@ Akavall Na verdade não. Isso importará apenas os nomes disponíveis quando a importinstrução for executada. Portanto, não haverá erros, mas você pode não obter todas as variáveis ​​que espera.
Augurar

3
Observe que, se você faz from foo import *e from bar import *, tudo o que fooé executado no está na fase de inicialização do bare as funções reais no barainda não foram definidas ...
Martian2049

100

As importações cíclicas terminam, mas você precisa ter cuidado para não usar os módulos importados ciclicamente durante a inicialização do módulo.

Considere os seguintes arquivos:

a.py:

print "a in"
import sys
print "b imported: %s" % ("b" in sys.modules, )
import b
print "a out"

b.py:

print "b in"
import a
print "b out"
x = 3

Se você executar o a.py, obterá o seguinte:

$ python a.py
a in
b imported: False
b in
a in
b imported: True
a out
b out
a out

Na segunda importação de b.py (na segunda a in), o interpretador Python não importa bnovamente, porque já existe no dict do módulo.

Se você tentar acessar b.xa partir adurante a inicialização do módulo, você vai ter uma AttributeError.

Anexe a seguinte linha a a.py:

print b.x

Então, a saída é:

$ python a.py
a in                    
b imported: False
b in
a in
b imported: True
a out
Traceback (most recent call last):
  File "a.py", line 4, in <module>
    import b
  File "/home/shlomme/tmp/x/b.py", line 2, in <module>
    import a
 File "/home/shlomme/tmp/x/a.py", line 7, in <module>
    print b.x
AttributeError: 'module' object has no attribute 'x'

Isso ocorre porque os módulos são executados na importação e, no momento em que b.xsão acessados, a linha x = 3ainda não foi executada, o que acontecerá somente depois b out.


14
isso explica muito o problema, mas e a solução? como poderíamos importar e imprimir corretamente x? a outra solução acima não funcionou para mim
Mehmet

Eu acho que essa resposta se beneficiaria muito se você usasse em __name__vez de 'a'. No começo, fiquei totalmente confuso por que um arquivo seria executado duas vezes.
Bergi 18/02

30

Como outras respostas descrevem esse padrão é aceitável em python:

def dostuff(self):
     from foo import bar
     ...

O que evitará a execução da instrução de importação quando o arquivo for importado por outros módulos. Somente se houver uma dependência circular lógica, isso falhará.

A maioria das importações circulares não é realmente importações circulares lógicas, mas gera ImportErrorerros, devido à maneira como import()avalia as instruções de nível superior de todo o arquivo quando chamado.

Isso ImportErrorsquase sempre pode ser evitado se você deseja positivamente suas importações no topo :

Considere esta importação circular:

App A

# profiles/serializers.py

from images.serializers import SimplifiedImageSerializer

class SimplifiedProfileSerializer(serializers.Serializer):
    name = serializers.CharField()

class ProfileSerializer(SimplifiedProfileSerializer):
    recent_images = SimplifiedImageSerializer(many=True)

App B

# images/serializers.py

from profiles.serializers import SimplifiedProfileSerializer

class SimplifiedImageSerializer(serializers.Serializer):
    title = serializers.CharField()

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()

De David Beazleys, excelente palestra Módulos e Pacotes: Live and Let Die! - PyCon 2015 , 1:54:00aqui está uma maneira de lidar com importações circulares em python:

try:
    from images.serializers import SimplifiedImageSerializer
except ImportError:
    import sys
    SimplifiedImageSerializer = sys.modules[__package__ + '.SimplifiedImageSerializer']

Isso tenta importar SimplifiedImageSerializere, se ImportErrorfor gerado, porque já foi importado, ele será puxado do importcache.

PS: Você precisa ler este post inteiro na voz de David Beazley.


9
ImportError não é gerado se o módulo já tiver sido importado. Os módulos podem ser importados quantas vezes você quiser, por exemplo, "importar a; importar a;" está bem
Yuras 5/05

9

Eu tenho um exemplo aqui que me impressionou!

foo.py

import bar

class gX(object):
    g = 10

bar.py

from foo import gX

o = gX()

main.py

import foo
import bar

print "all done"

Na linha de comando: $ python main.py

Traceback (most recent call last):
  File "m.py", line 1, in <module>
    import foo
  File "/home/xolve/foo.py", line 1, in <module>
    import bar
  File "/home/xolve/bar.py", line 1, in <module>
    from foo import gX
ImportError: cannot import name gX

2
Como você resolveu isso? Eu estou tentando entender importação circular para corrigir um problema do meu próprio que a aparência muito semelhante ao que você está fazendo ...
c089

12
Errm ... acho que resolvi meu problema com esse hack incrivelmente feio. {{{se não for 'foo.bar' no sys.modules: da barra de importação foo else: bar = sys.modules ['foo.bar']}}} Pessoalmente, acho que as importações circulares são um sinal de aviso ENORME em código incorreto design ...
c089

5
@ c089, ou você pode apenas mover import barem foo.pyao fim
warvariuc

5
Se bare fooambos devem usar gX, a solução 'mais limpa' é colocar gXoutro módulo e ter ambos fooe barimportar esse módulo. (mais limpa no sentido de que não há são escondidos dependências semânticas.)
Tim Wilder

2
Tim tem um bom argumento. Basicamente, é porque barnem consegue encontrar gXno foo. a importação circular é boa por si só, mas não gXé definida quando é importada.
precisa saber é o seguinte

9

Módulo a.py:

import b
print("This is from module a")

Módulo b.py

import a
print("This is from module b")

A execução do "Módulo a" produzirá:

>>> 
'This is from module a'
'This is from module b'
'This is from module a'
>>> 

Ele produziu essas 3 linhas enquanto deveria produzir infinitival por causa da importação circular. O que acontece linha por linha durante a execução do "Módulo a" está listado aqui:

  1. A primeira linha é import b. então vai visitar o módulo b
  2. A primeira linha no módulo b é import a. então ele vai visitar o módulo
  3. A primeira linha no módulo a é, import bmas observe que essa linha não será mais executada novamente , porque todo arquivo em python executa uma linha de importação apenas uma vez, não importa onde ou quando é executado. então ele passará para a próxima linha e será impresso "This is from module a".
  4. Depois de terminar de visitar o módulo inteiro a do módulo b, ainda estamos no módulo b. então a próxima linha será impressa"This is from module b"
  5. As linhas do módulo b são executadas completamente. então, voltaremos ao módulo a onde começamos o módulo b.
  6. A linha import b já foi executada e não será executada novamente. a próxima linha será impressa "This is from module a"e o programa será concluído.

4

Concordo plenamente com a resposta do pitonista aqui. Mas encontrei algum código que era defeituoso com importações circulares e causava problemas ao tentar adicionar testes de unidade. Para corrigi-lo rapidamente sem alterar tudo, você pode resolver o problema fazendo uma importação dinâmica.

# Hack to import something without circular import issue
def load_module(name):
    """Load module using imp.find_module"""
    names = name.split(".")
    path = None
    for name in names:
        f, path, info = imp.find_module(name, path)
        path = [path]
    return imp.load_module(name, f, path[0], info)
constants = load_module("app.constants")

Novamente, essa não é uma correção permanente, mas pode ajudar alguém que deseja corrigir um erro de importação sem alterar muito o código.

Felicidades!


3

Há muitas ótimas respostas aqui. Embora geralmente haja soluções rápidas para o problema, algumas das quais parecem mais pitônicas do que outras, se você tiver o luxo de refatorar, outra abordagem é analisar a organização do seu código e tentar remover a dependência circular. Você pode descobrir, por exemplo, que possui:

Arquivo a.py

from b import B

class A:
    @staticmethod
    def save_result(result):
        print('save the result')

    @staticmethod
    def do_something_a_ish(param):
        A.save_result(A.use_param_like_a_would(param))

    @staticmethod
    def do_something_related_to_b(param):
        B.do_something_b_ish(param)

Arquivo b.py

from a import A

class B:
    @staticmethod
    def do_something_b_ish(param):
        A.save_result(B.use_param_like_b_would(param))

Nesse caso, basta mover um método estático para um arquivo separado, diga c.py:

Arquivo c.py

def save_result(result):
    print('save the result')

permitirá remover o save_resultmétodo de A e, assim, remover a importação de A de a em b:

Arquivo refatorado a.py

from b import B
from c import save_result

class A:
    @staticmethod
    def do_something_a_ish(param):
        A.save_result(A.use_param_like_a_would(param))

    @staticmethod
    def do_something_related_to_b(param):
        B.do_something_b_ish(param)

Arquivo refatorado b.py

from c import save_result

class B:
    @staticmethod
    def do_something_b_ish(param):
        save_result(B.use_param_like_b_would(param))

Em resumo, se você possui uma ferramenta (por exemplo, pylint ou PyCharm) que informa sobre métodos que podem ser estáticos, apenas lançar um staticmethoddecorador sobre eles pode não ser a melhor maneira de silenciar o aviso. Embora o método pareça relacionado à classe, pode ser melhor separá-lo, especialmente se você tiver vários módulos intimamente relacionados que possam precisar da mesma funcionalidade e pretender praticar os princípios DRY.


2

As importações circulares podem ser confusas porque a importação faz duas coisas:

  1. executa código de módulo importado
  2. adiciona módulo importado à tabela de símbolos globais do módulo importador

O primeiro é feito apenas uma vez, enquanto o último em cada declaração de importação. A importação circular cria situação quando o módulo de importação usa um importado com código parcialmente executado. Em conseqüência, ele não verá objetos criados após a declaração de importação. O exemplo de código abaixo demonstra isso.

As importações circulares não são o mal final a ser evitado a todo custo. Em algumas estruturas, como o Flask, elas são bastante naturais e ajustar seu código para eliminá-las não melhora o código.

main.py

print 'import b'
import b
print 'a in globals() {}'.format('a' in globals())
print 'import a'
import a
print 'a in globals() {}'.format('a' in globals())
if __name__ == '__main__':
    print 'imports done'
    print 'b has y {}, a is b.a {}'.format(hasattr(b, 'y'), a is b.a)

b.by

print "b in, __name__ = {}".format(__name__)
x = 3
print 'b imports a'
import a
y = 5
print "b out"

a.py

print 'a in, __name__ = {}'.format(__name__)
print 'a imports b'
import b
print 'b has x {}'.format(hasattr(b, 'x'))
print 'b has y {}'.format(hasattr(b, 'y'))
print "a out"

saída main.py do python com comentários

import b
b in, __name__ = b    # b code execution started
b imports a
a in, __name__ = a    # a code execution started
a imports b           # b code execution is already in progress
b has x True
b has y False         # b defines y after a import,
a out
b out
a in globals() False  # import only adds a to main global symbol table 
import a
a in globals() True
imports done
b has y True, a is b.a True # all b objects are available

1

Resolvi o problema da seguinte maneira e funciona bem sem nenhum erro. Considere dois arquivos a.pye b.py.

Eu adicionei isso a.pye funcionou.

if __name__ == "__main__":
        main ()

a.py:

import b
y = 2
def main():
    print ("a out")
    print (b.x)

if __name__ == "__main__":
    main ()

b.py:

import a
print ("b out")
x = 3 + a.y

A saída que recebo é

>>> b out 
>>> a out 
>>> 5

0

Ok, acho que tenho uma solução bem legal. Digamos que você tenha arquivo ae arquivo b. Você tem um defou classno arquivo bque você deseja usar no módulo a, mas você tem algo mais, ou um def, classou variável do arquivo aque você precisa em sua definição ou classe no arquivo b. O que você pode fazer é, na parte inferior do arquivo a, depois de chamar a função ou classe no arquivo aque é necessário no arquivo b, mas antes de chamar a função ou classe do arquivo bque você precisa para o arquivo a, diga import b Então, e aqui está a parte principal , em todas as definições ou classes no arquivo bque precisam do arquivo defou classdoa(vamos chamá-lo CLASS), você dizfrom a import CLASS

Isso funciona porque você pode importar o arquivo bsem o Python executar qualquer uma das instruções de importação no arquivo be, assim, evitar as importações circulares.

Por exemplo:

Arquivo a:

class A(object):

     def __init__(self, name):

         self.name = name

CLASS = A("me")

import b

go = B(6)

go.dostuff

Arquivo b:

class B(object):

     def __init__(self, number):

         self.number = number

     def dostuff(self):

         from a import CLASS

         print "Hello " + CLASS.name + ", " + str(number) + " is an interesting number."

Voila.


from a import CLASSna verdade, não pula a execução de todo o código em a.py. É o que realmente acontece: (1) Todo o código no a.py é executado como um módulo especial "__main__". (2) Em import b, o código de nível superior em b.py é executado (definindo a classe B) e o controle retorna para "__main__". (3) "__main__" eventualmente passa o controle para go.dostuff(). (4) quando dostuff () chega import a, ele executa todo o código em a.py novamente , desta vez como o módulo "a"; depois importa o objeto CLASS do novo módulo "a". Então, na verdade, isso funcionaria igualmente bem se você usasse import aqualquer lugar do b.py.
Matthias Fripp
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.