Este documento discute estratégias para tornar código Python 2 e 3 compatíveis. Primeiro, explica como lidar com nomes e localizações de módulos que mudaram entre as versões, usando try/except, verificação de versão ou getattr. Segundo, destaca a importância de testes automatizados para garantir a compatibilidade e acompanhar dependências. Por fim, aborda metaclasses, uma diferença importante entre Python 2 e 3.
(2013-07-05) [fisl] Semáforo Gráfico dose para TDD em dojos
(2013-07-05) [fisl] Compatibilidade entre Python 2 e 3
1. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Compatibilidade entreCompatibilidade entre
Python 2 e 3Python 2 e 3
Como portar seu código
em Python 2.x
para o Python 3.x
sem torná-lo incompatível
com o Python 2.x?
Com base na história do pacote AudioLazy
2. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Por quê?Por quê?
● Versões futuras do Python
● Ampliar o público-alvo de seu projeto
– AudioLazy
● https://pypi.python.org/pypi/audiolazy/
● Pressão social e tecnofílicos!
– Software para uso científico
● NumPy
● MatPlotLib
● SciPy
– Notícias recentes
● Flask!
TODOS já são compatíveis
com o Python 3.x!
E outros 2300+ pacotes no PyPI...
Python 2.x is the status
quo, Python 3.x is the
present and future of the
language.
www.python.org
3. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Alguns númerosAlguns números
Valores coletados dia 2013-07-02
Compatibilidade entre Python 2.5 e
outras versões
Compatibilidade entre Python 3 e
outras versões
1459
2392
4. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Por onde começarPor onde começar
● Suíte de testes
● Conhecimento sobre
as diferenças entre
versões
– Ler, estudar
– Fuçar, brincar,
remoer, torturar a
linguagem
● Outras ferramentas e
pacotes
– Já solucionaram o mesmo
problema? Como?
– Pacotes de auxílio à
compatibilização
– Dependências funcionam
em quais versões do
Python?
● Diminuir restrições
5. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Parte 1Parte 1
Tipos de diferençasTipos de diferenças
6. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Nomes e localizaçõesNomes e localizações
import Tkinter
master = Tkinter.Tk()
master.mainloop()
import Tkinter
master = Tkinter.Tk()
master.mainloop()
import tkinter
master = tkinter.Tk()
master.mainloop()
import tkinter
master = tkinter.Tk()
master.mainloop()
Apenas Python 2 Apenas Python 3
Traceback (most recent call last):
[...]
ImportError: No module named Tkinter
Traceback (most recent call last):
[...]
ImportError: No module named Tkinter
Rodando no Python 3
Rodando no Python 2
Traceback (most recent call last):
[...]
ImportError: No module named tkinter
Traceback (most recent call last):
[...]
ImportError: No module named tkinter
7. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Nomes e localizaçõesNomes e localizações
com try...exceptcom try...except
try:
import tkinter
except ImportError:
import Tkinter as tkinter
master = tkinter.Tk()
master.mainloop()
try:
import tkinter
except ImportError:
import Tkinter as tkinter
master = tkinter.Tk()
master.mainloop()
● Nome único
– “as” no import
– Atribuição
● Há critérios para uso do
nome?
– PEP8
– Nome no Python 3
● Versões futuras
– Nome no Python 2
● Atual hábito
● Ausente no Python 3
Quais nomes foram “trocados”?
Documentação para
desenvolvedores!
8. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Nomes e localizaçõesNomes e localizações
com verificação préviacom verificação prévia
● sys.version_info
● sys.modules
import sys
PYTHON2 = sys.version_info.major == 2
if PYTHON2:
builtins = sys.modules["__builtin__"]
else:
import builtins
import sys
PYTHON2 = sys.version_info.major == 2
if PYTHON2:
builtins = sys.modules["__builtin__"]
else:
import builtins
9. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Módulo sysMódulo sys
In [1]: import sys
In [2]: sys.version_info
Out[2]: sys.version_info(major=3, minor=2,
micro=4, releaselevel='final', serial=0)
In [3]: sys.version_info >= (3, 2)
Out[3]: True
In [4]: sys.version_info >= (3, 3)
Out[4]: False
In [5]: sys.version
Out[5]: '3.2.4 (default, May 8 2013,
20:55:18) n[GCC 4.7.3]'
In [1]: import sys
In [2]: sys.version_info
Out[2]: sys.version_info(major=3, minor=2,
micro=4, releaselevel='final', serial=0)
In [3]: sys.version_info >= (3, 2)
Out[3]: True
In [4]: sys.version_info >= (3, 3)
Out[4]: False
In [5]: sys.version
Out[5]: '3.2.4 (default, May 8 2013,
20:55:18) n[GCC 4.7.3]'
10. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Nomes e localizaçõesNomes e localizações
com getattrcom getattr
● Funções são objetos
● Análogo ao método “get” de dicionários
import itertools
xzip = getattr(itertools, "izip", zip)
xmap = getattr(itertools, "imap", map)
xfilter = getattr(itertools, "ifilter", filter)
# Usando o builtins visto anteriormente
xrange = getattr(builtins, "xrange", range)
import itertools
xzip = getattr(itertools, "izip", zip)
xmap = getattr(itertools, "imap", map)
xfilter = getattr(itertools, "ifilter", filter)
# Usando o builtins visto anteriormente
xrange = getattr(builtins, "xrange", range)
11. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Monkeypatch / MockMonkeypatch / Mock
● Usando getattr ou atribuições (no try...except)
● Compatibilizar código de terceiros sem mudá-los
● Nem sempre é possível (tipos básicos)
– e.g. Método to_bytes do int (apenas Python 3)
import operator
operator.div = getattr(operator,
"div",
operator.truediv)
import operator
operator.div = getattr(operator,
"div",
operator.truediv)
In [1]: (317215).to_bytes(5, "big")
Out[1]: b'x00x00x04xd7x1f'
In [1]: (317215).to_bytes(5, "big")
Out[1]: b'x00x00x04xd7x1f'
12. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Funções e outros objetosFunções e outros objetos
ausentesausentes
● Reconstruir
● Criar pela primeira vez
– e.g. itertools.accumulate
● audiolazy.accumulate
from functools import wraps
@wraps(range)
def orange(*args, **kwargs):
return list(range(*args, **kwargs))
from functools import wraps
@wraps(range)
def orange(*args, **kwargs):
return list(range(*args, **kwargs))
13. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Métodos ausentesMétodos ausentes
● Iteração sobre dicionários
● Iterável VS Iterador
– Gotcha!
def iteritems(dictionary):
try:
return getattr(dictionary, "iteritems")()
except AttributeError:
return iter(getattr(dictionary, "items")())
def iteritems(dictionary):
try:
return getattr(dictionary, "iteritems")()
except AttributeError:
return iter(getattr(dictionary, "items")())
14. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
GeneralizaçõesGeneralizações
● String?
– str no Python 3
– (unicode, str) no Python 2
● builtins.basestring
● Inteiro?
– int no Python 3
– (long, int) no Python 2
INT_TYPES = (int, getattr(builtins, "long", None))
if PYTHON2 else (int,)
print(isinstance(something_here, INT_TYPES))
INT_TYPES = (int, getattr(builtins, "long", None))
if PYTHON2 else (int,)
print(isinstance(something_here, INT_TYPES))
Utilização comisinstance
15. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
InternalidadesInternalidades
● Iteradores (e geradores)
– Método next no Python 2
– Método __next__ no Python 3
● Avaliação “if obj:” segue um método de obj
– __nonzero__ no Python 2
– __bool__ no Python 3
● Função do método (“unbound”)
– Python 2
● classe.metodo.im_func
● objeto.metodo.im_func
– Python 3
● classe.metodo
● objeto.metodo.__func__
16. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Parte 2Parte 2
TestesTestes
17. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
TestesTestes
● Automatizados
– py.test
– nose
– unittest
– doctest
● Apenas para
documentação
● Cobertura de código
– Confiabilidade
– Dependências
● skip
● xfail
● Acompanhar
dependência
Testes passando
Testes falhando
18. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Skip automático comSkip automático com
py.testpy.test
import pytest
def skipper(msg="There's something not supported "
"in this environment"):
def skip(*args, **kwargs):
pytest.skip(msg.format(*args, **kwargs))
return skip
operator.div = getattr(operator, "div",
skipper("There's no "
"operator.div"))
import pytest
def skipper(msg="There's something not supported "
"in this environment"):
def skip(*args, **kwargs):
pytest.skip(msg.format(*args, **kwargs))
return skip
operator.div = getattr(operator, "div",
skipper("There's no "
"operator.div"))
19. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
toxtox
[tox]
envlist = py26,py27
[testenv]
deps=pytest
commands=py.test
[tox]
envlist = py26,py27
[testenv]
deps=pytest
commands=py.test
● “Standardize testing in Python”
● tox.ini
20. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Parte 3Parte 3
Diferenças importantesDiferenças importantes
!
21. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
MetaclassesMetaclasses
● Classe cujas instâncias são classes
● Sintaxe diferenciada no Python 2 e 3
# Python 3
bases = (object,)
MyMeta = type
class A(*bases, metaclass=MyMeta):
pass
# Python 2
class A(*bases):
__metaclass__ = MyMeta
# Python 3
bases = (object,)
MyMeta = type
class A(*bases, metaclass=MyMeta):
pass
# Python 2
class A(*bases):
__metaclass__ = MyMeta
22. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
MetaclassesMetaclasses
● Solução padrão:
– Criar uma classe
vazia usando a
metaclasse, e colocá-
la junto às bases
● Problemas:
– Construtor da classe
pode falhar
● Solução alternativa
– Metaclasse falsa
● Única base da nova
classe
– Construtor da
metaclasse com 2
comportamentos
● Antes da obtenção do
dicionário da classe
● Depois (real
instanciação)
– Função audiolazy.meta
23. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04>>> class BadMeta(type):
... def __new__(mcls, name, bases, namespace):
... if "bad" not in namespace:
... raise Exception("Oops, not bad enough")
... value = len(name)
... def really_bad(self):
... return self.bad() * value
... namespace["really_bad"] = really_bad
... return super(BadMeta, mcls).__new__(mcls, name, bases,
... namespace)
...
>>> class Bady(meta(object, metaclass=BadMeta)):
... def bad(self):
... return "HUA "
...
>>> class BadGuy(Bady):
... def bad(self):
... return "R"
...
>>> issubclass(BadGuy, Bady)
True
>>> Bady().really_bad() # value = 4
'HUA HUA HUA HUA '
>>> BadGuy().really_bad() # value = 6
'RRRRRR'
>>> class BadMeta(type):
... def __new__(mcls, name, bases, namespace):
... if "bad" not in namespace:
... raise Exception("Oops, not bad enough")
... value = len(name)
... def really_bad(self):
... return self.bad() * value
... namespace["really_bad"] = really_bad
... return super(BadMeta, mcls).__new__(mcls, name, bases,
... namespace)
...
>>> class Bady(meta(object, metaclass=BadMeta)):
... def bad(self):
... return "HUA "
...
>>> class BadGuy(Bady):
... def bad(self):
... return "R"
...
>>> issubclass(BadGuy, Bady)
True
>>> Bady().really_bad() # value = 4
'HUA HUA HUA HUA '
>>> BadGuy().really_bad() # value = 6
'RRRRRR'
25. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Unicode!!!Unicode!!!
● Nomes de variável em unicode
● *.py em UTF-8 (Python 3)
– No Python 2, em uma das 2 primeiras linhas:
# coding: utf-8
● Pensar no unicode (str do Python 3) como um objeto.
– Encode: codifica o unicode para uma string de bytes
– Decode: dos bytes, obtém o unicode
● The Absolute Minimum Every Software Developer
Absolutely, Positively Must Know About Unicode
and Character Sets (No Excuses!)
http://www.joelonsoftware.com/articles/Unicode.html
26. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Unicode!!!Unicode!!!
● u“texto” (Python 3.3 e 2.x)
● from __future__ import unicode_literals
– Funciona no Python 3.2
● Conversão manual (testar tipo)
● Provavelmente o aspecto mais difícil durante a
compatibilização
– os.urandom no simplekv (flask-kvsession)
27. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Itertools e functoolsItertools e functools
● reduce
– from functools import reduce
● zip, map, filter (e zip_longest)
– Python 2: Listas
– Python 3: Comportamento tardio (lazy), similar ao
izip, imap, ifilter do itertools do Python 2
● Itertools
– Não possui mais izip, imap, ifilter, izip_longest
– Novo accumulate
28. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
DivisãoDivisão
● 1 / 2
– 0 no Python 2 (int)
– 0.5 no Python 3 (float)
● 1 // 2
– 0 no Python 2 (int)
– 0 no Python 3 (int)
● Solução imediata
– from __future__ import division
29. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Parte 4Parte 4
Diferenças inusitadasDiferenças inusitadas
30. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Arredondamento de pontoArredondamento de ponto
flutuanteflutuante
In [1]: round(.5)
Out[1]: 1.0
In [2]: round(-.5)
Out[2]: -1.0
In [1]: round(.5)
Out[1]: 1.0
In [2]: round(-.5)
Out[2]: -1.0
● Python 2 ● Python 3
● Solução? Depende do comportamento desejado
– audiolazy.rint
In [1]: round(.5)
Out[1]: 0
In [2]: round(-.5)
Out[2]: 0
In [1]: round(.5)
Out[1]: 0
In [2]: round(-.5)
Out[2]: 0
31. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Namespace da classeNamespace da classe
● Python 2
– Funciona ok
● Python 3
– NameError: global
name 'data' is not
defined
class A(object):
data = [1, 2, 3]
data_powers = [[x ** n for x in data]
for n in range(3)]
class A(object):
data = [1, 2, 3]
data_powers = [[x ** n for x in data]
for n in range(3)]
● Compatibilizar
– data_powers = (lambda data: […])(data)
– Deixar fora da classe
– Colocar no __init__ ou no __new__ da metaclasse
32. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Parte 4Parte 4
FinalizaçãoFinalização
33. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Python 2.6 e 2.7Python 2.6 e 2.7
● Apenas no Python 2.7
– Dict comprehension
● dict((k, v) for k, v in my_iterable)
– Set comprehension
● set(el for el in my_iterable)
– collections.OrderedDict
● pip install ordereddict
● Outros features do Python 3.1 (backported)
http://docs.python.org/dev/whatsnew/2.7.html
Comprehension com {}:
apenas Python 2.7, 3.1 e
mais recentes
34. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
sixsix
● Pacote de compatibilização
– Funções para iterar em dicionários
– Constantes com tipos para uso com isinstance
– callable (Ausente no Python 3 até o 3.1)
– Preocupação com Python 2.4 e 2.5
● Avaliação tardia
– Não importa nada à toa
– Engana análise para auto-complete
● Metaclasse (mantém um nível hierárquico adicional)
– class A(with_metaclass(Meta, Base)) # Apenas uma base
– Neste caso, a audiolazy.meta é mais geral
Utilizado peloMatPlotLib
35. Danilo J. S. Bellini – fisl 14 – 2013-07-04Danilo J. S. Bellini – fisl 14 – 2013-07-04
Alternativas à compatibilizaçãoAlternativas à compatibilização
● Conversão automática
– Distribute com 2to3 ou 3to2
● Conversão manual
– Branches para cada versão
● Incompatibilidade
– Código restrito a versões específicas