9.1 Criando dependências circulares de módulo
Suponha que você tenha dois arquivos, a.py e b.py, cada um importando o outro da seguinte forma:
No a.py:
import b
def f():
return b.x
print(f())
No b.py:
import a
x = 1
def g():
print(a.f())
Primeiro, vamos tentar importar o a.py:
import a
# 1
Funcionou super bem. Talvez isso te surpreenda. Afinal, os módulos se importam ciclicamente, e isso provavelmente deveria ser um problema, certo?
A resposta é que a simples existência de importações circulares de módulos por si só não é problema no Python. Se um módulo já foi importado, o Python é inteligente o suficiente para não tentar importá-lo novamente. No entanto, dependendo de quando cada módulo tenta acessar funções ou variáveis definidas no outro, você pode realmente enfrentar problemas.
Então, voltando ao nosso exemplo, quando importamos o a.py, ele não teve problemas ao importar o b.py, pois o b.py não exige que nada do a.py seja definido durante sua importação. A única referência no b.py ao a é a chamada a.f(). Mas essa chamada está em g(), e nada no a.py ou b.py chama g(). Portanto, tudo funciona perfeitamente.
Mas o que acontece se tentarmos importar o b.py (sem importar previamente o a.py, ou seja):
import b
Traceback (most recent call last):
File "<stdin>", line 1, in
File "b.py", line 1, in
import a File "a.py", line 6, in
print(f()) File "a.py", line 4, in f return b.x AttributeError: 'module' object has no attribute 'x'
O problema aqui é que, no processo de importação do b.py, ele tenta importar o a.py, que, por sua vez, chama f(), que tenta acessar b.x. Mas b.x ainda não foi definido. Daí a exceção AttributeError.
Pelo menos, uma das soluções para esse problema é bastante trivial. Apenas altere o b.py para importar o a.py dentro do g():
x = 1
def g():
import a # Isso será avaliado apenas quando g() for chamado
print(a.f())
Agora, quando o importamos, tudo está bem:
import b
b.g()
# 1 Impresso pela primeira vez, já que o módulo 'a' chama 'print(f())' no final
# 1 Impresso pela segunda vez, este é o nosso chamado para 'g()'
9.2 Conflito de nomes com módulos da biblioteca padrão do Python
Uma das maravilhas do Python é a abundância de módulos que vêm "de fábrica". Mas, como resultado, se você não ficar de olho, pode acabar tendo um nome de módulo que coincide com o nome de um módulo da biblioteca padrão do Python (por exemplo, no seu código pode haver um módulo chamado email.py, que vai conflitar com um módulo da biblioteca padrão com o mesmo nome).
Isso pode causar problemas sérios. Por exemplo, se algum dos módulos tentar importar a versão do módulo da biblioteca padrão do Python, mas você tiver um módulo com o mesmo nome no seu projeto, ele por engano importará seu módulo em vez do da biblioteca padrão.
Portanto, é importante ter cuidado para não usar os mesmos nomes que os módulos da biblioteca padrão do Python. É muito mais fácil alterar o nome do módulo no seu projeto do que solicitar uma mudança de nome de um módulo na biblioteca padrão e esperar pela aprovação.
9.3 Visibilidade das exceções
Considere o seguinte arquivo main.py:
import sys
def bar(i):
if i == 1:
raise KeyError(1)
if i == 2:
raise ValueError(2)
def bad():
e = None
try:
bar(int("1"))
except KeyError as e:
print('key error')
except ValueError as e:
print('value error')
print(e)
bad()
Parece tudo certo, o código deve funcionar, vamos ver o que ele nos mostra:
$ python main.py 1
Traceback (most recent call last):
File "C:\Projects\Python\TinderBolt\main.py", line 19, in <module>
bad()
File "C:\Projects\Python\TinderBolt\main.py", line 17, in bad
print(e)
^
UnboundLocalError: cannot access local variable 'e' where it is not associated with a value
O que acabou de acontecer aqui? O problema é que no Python o objeto no bloco de exceção não está disponível fora dele. (A razão para isso é que, caso contrário, os objetos nesse bloco seriam mantidos na memória até que o coletor de lixo os removesse).
Uma maneira de evitar esse problema é manter uma referência ao objeto do bloco de exceção fora desse bloco, para que ele permaneça acessível. Aqui está uma versão do exemplo anterior que utiliza essa técnica, tornando o código funcional:
import sys
def bar(i):
if i == 1:
raise KeyError(1)
if i == 2:
raise ValueError(2)
def good():
exception = None
try:
bar(int("1"))
except KeyError as e:
exception = e
print('key error')
except ValueError as e:
exception = e
print('value error')
print(exception)
good()
9.4 Uso incorreto do método __del__
Quando o interpretador deleta um objeto, ele verifica se há uma função __del__ no objeto e, se houver, ele a chama antes de deletar o objeto. Isso é muito útil quando você quer que seu objeto limpe algum recurso externo ou cache.
Suponha que você tenha este arquivo mod.py:
import foo
class Bar(object):
...
def __del__(self):
foo.cleanup(self.myhandle)
E você está tentando fazer isso de outro arquivo another_mod.py:
import mod
mybar = mod.Bar()
E recebe um terrível AttributeError.
Por que? Porque, conforme relatado aqui, quando o interpretador termina, todas as variáveis globais do módulo têm valor None. Como resultado, no exemplo acima, no momento da chamada do __del__, o nome foo já foi configurado como None.
A solução para esse "desafio estrela" é usar o método especial atexit.register(). Desta forma, quando seu programa termina (ou seja, em uma saída normal), seus handle'os são removidos antes que o interpretador termine.
Com isso em mente, a correção para o código mod.py acima pode ser algo assim:
import foo
import atexit
def cleanup(handle):
foo.cleanup(handle)
class Bar(object):
def __init__(self):
...
atexit.register(cleanup, self.myhandle)
Essa implementação fornece um meio simples e confiável de executar qualquer limpeza necessária após a terminação normal do programa. Obviamente, a decisão de o que fazer com o objeto que está associado ao nome self.myhandle fica a cargo de foo.cleanup, mas acho que você entendeu a ideia.
GO TO FULL VERSION