Posts Tagged ‘ exec ’

Python 3 – exec e outras mudanças

Já tem um tempo que tento usar somente Python 3 para meus projetos em Python, mas, como muita gente ainda está presa em versões antigas (como o pessoal do CentOS que há um mês mudou do Python 2.4 para o ainda defasado 2.6), sou obrigado a trabalhar com os dois ambientes.

Portar pequenos programas de uma versão para outra pode ser bastante simples (como no Txt2Tags que precisei de umas 2 ou 3hs para fazer funcionar) quando já se conhece os possíveis problemas e suas soluções, mas nem tudo está bem documentado.

Claro que tudo isso seria muito mais difícil se não houvesse script chamado 2to3.py que vem junto com as versões mais novas do interpretador para automatizar o processo. O problema que existem coisas que ele é incapaz de traduzir ou que precisam de mudanças mais bruscas no código para que funcionem.

Por algum motivo, o site “Dive into Python 3” que tinha uma versão online do livro homônimo foi removido do ar e perdi a melhor referência para o tópico que existia, ** Encontrei um novo mirror do Dive into Python 3 **

Também existem outros bons textos por ai.

Algumas das mudanças mais importantes na nova versão são os iteradores usados em lugares que antes retornavam listas, o uso de unicode por padrão (que na minha opinião já vale por todo o trabalho de portar código) e a troca de algumas palavras-chaves (statements) por funções e vice-versa.

Sobre essa última mudança, as palavras-chave tem o poder de mudar o local em que se encontram enquanto outros nomes podem significar qualquer coisa que o programador queira e só podem ser verificados em tempo de execução, não podendo transformar o ambiente.

Por exemplo, o comando yield faz com que o a função que o contenha seja transformada num gerador mesmo que ele nunca seja executado ou tenha bytecode para isso!

def gerador():
    if 0:
        yield

Neste código acima, o interpretador vai ver que o if nunca será executado e não vai gerar bytecode para nada dentro dele (no caso o yield), mas a função não vai retornar o implícito None quando for chamada. Ela vai retornar um gerador vazio que pode ser usado num loop for.

Usando o módulo dis para desmontar bytecode vemos que a função só retorna None, mas ela vai ter um flag para mostrar sua utilidade:

>>> dis.dis(gerador)
  2           0 LOAD_CONST               0 (None)
              3 RETURN_VALUE

Ainda sobre isso, True, False e None agora são palavras reservadas e que não podem ser modificadas, fazendo com que, se o exemplo acima fosse feito com False ao invés de 0, ele geraria bytecode no Python 2, mas não no Python 3!

No sentido contrário, as palavras-chave print e exec do Python 2 agora são funções e não podem mais alterar seu ambiente.

No caso do print, não tem problema algum e a sintaxe ficou bem melhor, mas o exec (que normalmente seu uso não é uma boa prática de programação) pode ter um comportamento bem diferente nas duas versões do interpretador.

Essa informação não vai ser encontrada na documentação do Python (apenas alguns avisos que não explicam bem a situação) e geralmente a pessoa vai procurar saber disso quando bater de cara com esse problema e nada funcionar.

Olhe o código abaixo (Não tome como exemplo de boa programação):

a = 1
def f():
    exec("a = 2")
    print(a)

f()
print(a)

No Python 2, a função vai imprimir “2” e o outro print vai imprimir “1”, como esperado. No Python 3, o número “1” vai ser impresso nos dois casos.

O que aconteceu?

O meu amigo dis mostra como a variável “a” é chamada no bytecode para impressão dentro da função:

Python 2:

3            8 LOAD_NAME                0 (a)

Python 3:

3           13 LOAD_GLOBAL              2 (a)

Como o Python 3 não consegue ver nenhum a dentro da função, ele assume que é uma palavra definida globalmente e vai embora. No Python 2, é percebida a existência da palavra exec e transforma a busca por um nome sendo primeiro no namespace local e, se não encontrar, no namespace global.

O que, por um lado traz o resultado “desejado”, também faz todos os acessos a funções e variáveis serem mais lento.

Soluções:
1 – Não usar exec.

Praticamente sempre é possível fazer uma solução sem exec que seja simples, mas se for necessário, é preciso tomar cuidado com essa mudança ou passar um segundo parâmetro para a função dizendo em que namespace você quer se o resultado seja avaliado e usar a informação nele.

Normalmente, ninguém usa exec para situações como a que eu mostrei, mas para gerar conteúdo (funções, classes, subclasses, etc) em tempo de execução e muitas vezes existem ferramentas melhores, como as metaclasses e os decoradores.