O que acontecerá se dois módulos se importarem?
Para generalizar o problema, e as importações cíclicas no Python?
O que acontecerá se dois módulos se importarem?
Para generalizar o problema, e as importações cíclicas no Python?
Respostas:
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.
Se você fizer por import foo
dentro bar
e por import bar
dentro 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 abc
e 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.
from foo import *
e from bar import *
também irá funcionar bem.
from x import y
, e ainda assim recebe o erro circular de importação
import
instrução for executada. Portanto, não haverá erros, mas você pode não obter todas as variáveis que espera.
from foo import *
e from bar import *
, tudo o que foo
é executado no está na fase de inicialização do bar
e as funções reais no bar
ainda não foram definidas ...
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 b
novamente, porque já existe no dict do módulo.
Se você tentar acessar b.x
a partir a
durante 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.x
são acessados, a linha x = 3
ainda não foi executada, o que acontecerá somente depois b out
.
__name__
vez de 'a'
. No começo, fiquei totalmente confuso por que um arquivo seria executado duas vezes.
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 ImportError
erros, devido à maneira como import()
avalia as instruções de nível superior de todo o arquivo quando chamado.
Isso ImportErrors
quase sempre pode ser evitado se você deseja positivamente suas importações no topo :
Considere esta importação circular:
# profiles/serializers.py
from images.serializers import SimplifiedImageSerializer
class SimplifiedProfileSerializer(serializers.Serializer):
name = serializers.CharField()
class ProfileSerializer(SimplifiedProfileSerializer):
recent_images = SimplifiedImageSerializer(many=True)
# 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:00
aqui 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 SimplifiedImageSerializer
e, se ImportError
for gerado, porque já foi importado, ele será puxado do importcache.
PS: Você precisa ler este post inteiro na voz de David Beazley.
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
import bar
em foo.py
ao fim
bar
e foo
ambos devem usar gX
, a solução 'mais limpa' é colocar gX
outro módulo e ter ambos foo
e bar
importar esse módulo. (mais limpa no sentido de que não há são escondidos dependências semânticas.)
bar
nem consegue encontrar gX
no foo. a importação circular é boa por si só, mas não gX
é definida quando é importada.
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:
import b
. então vai visitar o módulo bimport a
. então ele vai visitar o móduloimport b
mas 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"
."This is from module b"
"This is from module a"
e o programa será concluído.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!
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_result
mé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 staticmethod
decorador 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.
As importações circulares podem ser confusas porque a importação faz duas coisas:
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
Resolvi o problema da seguinte maneira e funciona bem sem nenhum erro. Considere dois arquivos a.py
e b.py
.
Eu adicionei isso a.py
e funcionou.
if __name__ == "__main__":
main ()
import b
y = 2
def main():
print ("a out")
print (b.x)
if __name__ == "__main__":
main ()
import a
print ("b out")
x = 3 + a.y
A saída que recebo é
>>> b out
>>> a out
>>> 5
Ok, acho que tenho uma solução bem legal. Digamos que você tenha arquivo a
e arquivo b
. Você tem um def
ou class
no arquivo b
que você deseja usar no módulo a
, mas você tem algo mais, ou um def
, class
ou variável do arquivo a
que 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 a
que é necessário no arquivo b
, mas antes de chamar a função ou classe do arquivo b
que 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 b
que precisam do arquivo def
ou class
doa
(vamos chamá-lo CLASS
), você dizfrom a import CLASS
Isso funciona porque você pode importar o arquivo b
sem o Python executar qualquer uma das instruções de importação no arquivo b
e, assim, evitar as importações circulares.
Por exemplo:
class A(object):
def __init__(self, name):
self.name = name
CLASS = A("me")
import b
go = B(6)
go.dostuff
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 CLASS
na 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 a
qualquer lugar do b.py.