Porte para o Python 3

O Django 1.5 foi a primeira versão do Django a dar suporte ao Python 3. O mesmo código roda tanto no Python 2 (≥ 2.6.5) quanto no Python 3 (≥ 3.2), graças a camada de compatibilidade do six.

Este documento visa principalmente os autores de aplicações plugáveis que desejem dar suporte ao Python 2 e 3. Ele também descreve regras aplicadas no código do Django.

Filosofia

Este documento assume que você já está familiarizado com as mudanças entre o Python 2 e o Python 3. Se você não está, leia primeiro o Python’s official porting guide. Revisar o seu conhecimento sobre manipulação de unicode no Python 2 e 3 irá ajudar; a apresentação Pragmatic Unicode é uma boa referência.

O Django utiliza a estratégia Python 2/3 Compatible Source. Claro, se você não precisar se manter compatível com o Python 2, você está livre para escolher outra estratégia para o seu código. Mas criadores de apps plugáveis são encorajados a usar a mesma estratégia de porte que o Django utiliza.

Escrever código compatível é muito mais simples se você objetivar o Python ≥ 2.6. O Django 1.5 introduziu ferramentas de compatibilidade como o django.utils.six, que é uma versão customizada do módulo six. Por conveniência, aliases compatíveis com versões futuras foram introduzidos no Django 1.4.2. Se sua aplicação tira proveito dessas ferramentas, ela irá requerer Django ≥ 1.4.2.

Evidentemente, escrever código-fonte compatível adiciona algum overhead, e isso pode causar frustração. Desenvolvedores Django descobriram que tentar escrever código em Python 3 que seja compatível com o Python 2 é muito mais recompensador que o contrário. Além de deixar o seu código melhor preparado para o futuro, as vantagens do Python 3 (como a manipulação sensata de strings) começam a aparecer rapidamente. Lidar com o Python 2 se tornou um requerimento de compatibilidade com versões anteriores, e nós desenvolvedores estamos acostumados a lidar com esse tipo de limitação.

As ferramentas de porte fornecidas pelo Python são inspiradas por essa filosofia, e refletidas em todo esse guia.

Dicas de portabilidade

Literais Unicode

Estes passos consistem em:

  • Adicionar from __future__ import unicode_literals no topo dos seus módulos Python – o ideal é colocar ele em todo e qualquer módulo, caso contrário você vai ficar checando o topo dos seus arquivos para ver qual modo está sendo usado.
  • Remover o prefixo u antes de strings unicode;
  • Adicionar o prefixo b antes de bytestrings.

Realizar essas mudanças sistematicamente garante compatibilidade com versões anteriores.

Entretanto, como o Django só expôe interfaces unicode para o programador, aplicações Django geralmente não precisam de bytestrings. Com exceção de dados binários ou interfaces orientadas a bytes, o Python 3 desencoraja o uso de bytestrings. O Python 2 faz bytestrings e strings unicode efetivamente intercambiáveis, desde que elas só contenham dados em ASCII. Tire vantagem disso para usar strings unicode sempre que possível e evite os prefixos b.

Nota

O prefixo u no Python 2 é um erro sintático no Python 3.2 mas ele será permitido novamente no Python 3.3 graças a PEP 414. Assim, essa transformação é opcional se o seu alvo é Python ≥ 3.3. Mas ainda é recomendado pela fisolofia de “escrever código Python 3”.

Manipulação de String

O tipo unicode do Python 2 foi renomeado para str no Python 3, str() foi renomeado para bytes, e basestring desapareceu. A biblioteca six fornece ferramentas para lidar com essas mudanças.

O Django também contém várias classes e funções relacionada a strings nos módulos django.utils.encoding e django.utils.safestring. Seus nomes utilizam as palavras str, que não significa a mesma coisa no Python 2 e Python 3, e unicode, que não existe no Python 3. Para evitar ambiguidade e confusão esses conceitos foram renomeados para bytes e text.

Essas são as mudanças de nomes no módulo django.utils.encoding:

Nome antigo Novo nome
smart_str smart_bytes
smart_unicode smart_text
force_unicode force_text

Para compatibilidade com versões anteriores, os nomes antigos ainda funcionam no Python 3. Sob o Python 3, smart_str é um alias para smart_text.

Para compatibilidade com versões anteriores, os novos nomes funcionam a partir do Django 1.4.2.

Nota

O módulo django.utils.encoding foi profundamente refatorado no Django 1.5 para fornecer uma API mais consistente. Verifique sua documentação para mais detalhes.

O módulo django.utils.safestring é mais usado pelas funções mark_safe() e mark_for_escaping(), que não mudaram. Caso você esteja usando as partes internas, essas são as mudanças de nomes:

Nome antigo Novo nome
EscapeString EscapeBytes
EscapeUnicode EscapeText
SafeString SafeBytes
SafeUnicode SafeText

Para compatibilidade com versões anteriores, os nomes antigos ainda funcionam no Python 2. Já no Python 3, EscapeString e SafeString são aliases para EscapeText e SafeText respectivamente.

Para compatibilidade com versões anteriores, os novos nomes funcionam a partir do Django 1.4.2.

Os métodos __str__() e __unicode__()

No Python 2, o modelo de objetos especifica os métodos __str__() e ` __unicode__()`_ . Se esses métodos existem, eles devem retornar str (bytes) e unicode (text) respectivamente.

O comando print e a chamada embutida do método __str__() na classe str para determinar a representação compreensível por humanos de um objeto. As chamadas embutidas unicode ` __unicode__()`_ se existir, e caso contrário reverter para o método __str__() e decodificar o resultado usando o encoding do sistema. Por outro lado, a classe base Model automaticamente deriva o método __str__() do ` __unicode__()`_ fazendo o encoding para UTF-8.

No Python 3, existe apenas o método __str__(), que deve retornar str (text).

(Também é possível definir o método __bytes__(), mas aplicações Django tem pouco uso para esse método, já que elas dificilmente precisam lidar com bytes.)

O Django fornece uma maneira mais simples para definir os métodos __str__() e ` __unicode__()`_ que funcionam no Python 2 e 3: você deve definir um método __str__() retornando texto e adicionar o decorator python_2_unicode_compatible().

No Python 3, o decorator não faz nada. No Python 2, ele define os métodos apropriados para ` __unicode__()`_ e __str__() (trocando o método __str__() original no processo). Segue um exemplo:

from __future__ import unicode_literals
from django.utils.encoding import python_2_unicode_compatible

@python_2_unicode_compatible
class MyClass(object):
    def __str__(self):
        return "Instance of my class"

Essa técnica é a que melhor se encaixa na filosofia de portabilidade do Django.

Para compatibilidade com versões futuras, esse decorator está disponível a partir do Django 1.4.2.

Finalmente, repare que o método __repr__() deve retornar um str em todas as versões do Python.

Classes dict e similares

Os métodos dict.keys(), dict.items() e dict.values() retornam listas no Python 2 e iteradores no Python 3. A classe QueryDict e classes declaradas em django.utils.datastructures similares a classe dict se comportam da mesma maneira no Python 3.

A biblioteca six fornece funções de compatibilidade para lidar com essas mudanças: iterkeys(), iteritems(), e itervalues(). Ela também contém uma função iterlists não documentada que funciona bem para a classe django.utils.datastructures.MultiValueDict e suas subclasses.

Objetos das classes HttpRequest e HttpResponse.

De acordo com a PEP 3333:

  • Cabeçalhos são sempre objetos do tipo str
  • Fluxos de entrada e saída são sempre objetos bytes.

Especificamente, o atributo HttpResponse.content contém bytes, o que pode ser um problema se você compará-lo com um str nos seus testes. A solução preferida é confiar nos métodos assertContains() e assertNotContains(). Esses métodos aceitam uma response e uma string unicode como argumentos.

Diretrizes de codificação

As diretrizes a seguir são aplicadas no código-fonte do Django. Elas são também recomendadas para aplicações de terceiros que seguem a mesma estratégia de portabilidade.

Syntax requirements

Unicode

No Python 3, todas as strings são consideradas Unicode por padrão. O tipo unicode do Python 2 é chamado de str no Python 3, e str se tornou bytes.

Você não deve usar o prefixo u antes de uma string literal unicode porque isso é um erro de síntaxe no Python 3.2. Você deve usar o prefixo b em strings do tipo byte.

Para habilitar o mesmo compartamento no Python 2, todo módulo deve importar unicode_literals from __future__:

from __future__ import unicode_literals

my_string = "This is an unicode literal"
my_bytestring = b"This is a bytestring"

Se você precisar de uma string do tipo byte no Python 2 e uma string unicode no Python 3, use a classe interna str:

str('my string')

No Python 3, não existem conversões automáticas entre str e bytes, e o módulo codecs se tornou mais rigoroso. O método str.encode() sempre retorna bytes, e bytes.decode sempre retorna str. Como consequência, o padrão a seguir algumas vezes é necessário:

value = value.encode('ascii', 'ignore').decode('ascii')

Seja cauteloso se você tiver que lidar com index bytestrings.

Exceções

Quando você capturar exceções, use a keyword as:

try:
    ...
except MyException as exc:
    ...

Essa sintaxe antiga foi removida no Python 3:

try:
    ...
except MyException, exc:    # Don't do that!
    ...

A sintaxe para relançar uma exceção com um traceback diferente também mudou. Utilize a função six.reraise().

Métodos mágicos

Utilize os padrões abaixo para manipular métodos mágicos renomeados no Python 3.

Iteradores

class MyIterator(six.Iterator):
    def __iter__(self):
        return self             # implement some logic here

    def __next__(self):
        raise StopIteration     # implement some logic here

Interpretação de booleanos

class MyBoolean(object):

    def __bool__(self):
        return True             # implement some logic here

    def __nonzero__(self):      # Python 2 compatibility
        return type(self).__bool__(self)

Divisão

class MyDivisible(object):

    def __truediv__(self, other):
        return self / other     # implement some logic here

    def __div__(self, other):   # Python 2 compatibility
        return type(self).__truediv__(self, other)

    def __itruediv__(self, other):
        return self // other    # implement some logic here

    def __idiv__(self, other):  # Python 2 compatibility
        return type(self).__itruediv__(self, other)

Métodos especiais são procurados na classe a não na instância para refletir o comportamento do interpretador Python.

Escrevendo código compatível com six

A biblioteca six é a biblioteca de compatibilidade canônica para suportar o Python 2 e 3 em uma única base. Leia a sua documentação!

A versão customizada da biblioteca é distribuída com o Django a partir da versão 1.4.2. Você pode importá-la como django.utils.six.

Essas são as mudanças necessárias mais comuns para se escrever código compatível.

Manipulação de String

Os tipos foram removidos no Python 3, e o significado de str mudou. Para testar esses tipos, use as expressões a seguir:

isinstance(myvalue, six.string_types)       # replacement for basestring
isinstance(myvalue, six.text_type)          # replacement for unicode
isinstance(myvalue, bytes)                  # replacement for str

O Python ≥ 2.6 fornece bytes como um alias para str, de modo que você não precisa usar o six.binary_type.

long

O tipo long não existe mais no Python 3. 1L é um erro sintático. Utilize a verificação six.integer_types se o valor for um integer ou um long:

isinstance(myvalue, six.integer_types)      # replacement for (int, long)

xrange

Se você usa xrange no Python 2, faça o import do six.moves.range e use ele no lugar. Você pode também importar six.moves.xrange (ele é equivalente ao six.moves.range) mas a primeira técnica permite que você simplesmente remova o import ao deixar de suportar o Python 2.

Módulos movidos

Alguns módulos foram renomeados no Python 3. O módulo django.utils.six.moves (baseado no módulo six.moves) fornece uma localização compatível para importá-los.

PY2

Se você precisar de código diferente no Python 2 e Python 3, verifique six.PY2:

if six.PY2:
    # compatibility code for Python 2

Esse é o último recurso para quando o módulo six não fornecer uma função apropriada.

A versão six customizada do Django

A versão do six disponibilizada com o Django (django.utils.six) inclui algumas customizações somente para uso interno.