CodeGym /Cursos /Python SELF PT /Erros padrão, parte 3

Erros padrão, parte 3

Python SELF PT
Nível 20 , Lição 3
Disponível

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.

1
Pesquisa/teste
Iteradores, nível 20, lição 3
Indisponível
Iteradores
Iteradores
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION