Python 3 já existe há sete anos, mas alguns ainda preferem usar Python 2 em vez da versão mais recente. Este é um problema especialmente para os neófitos que estão se aproximando do Python pela primeira vez. Percebi isso em meu trabalho anterior com colegas na mesma situação. Eles não apenas desconheciam as diferenças entre as duas versões, como nem mesmo sabiam a versão que haviam instalado.
Inevitavelmente, diferentes colegas instalaram diferentes versões do intérprete. Essa seria uma receita para o desastre se eles tivessem tentado compartilhar cegamente os scripts entre eles.
Isso não foi exatamente culpa deles, pelo contrário. Um esforço maior para documentar e aumentar a conscientização é necessário para dissipar o véu de FUD (medo, incerteza e dúvida) que às vezes afeta nossas escolhas. Este post é, portanto, pensado para eles, ou para aqueles que já usam Python 2, mas não têm certeza de passar para a próxima versão, talvez porque tentaram a versão 3 apenas no início, quando era menos refinada e o suporte para bibliotecas era pior.
Em primeiro lugar, é verdade que Python 2 e Python 3 são linguagens diferentes? Esta não é uma questão trivial. Mesmo que algumas pessoas resolvam a questão com: “Não, não é um novo idioma” , de fato várias propostas que teriam quebrado a compatibilidade sem produzir vantagens importantes foram rejeitadas .
Python 3 é uma nova versão do Python, mas não é necessariamente compatível com as versões anteriores do código escrito para Python 2. Ao mesmo tempo, é possível escrever código compatível com ambas as versões, e isso não é por acaso, mas um claro compromisso do Desenvolvedores Python que elaborou os diversos PEP (Proposta de Extensão Python). Nos poucos casos em que a sintaxe é incompatível, graças ao fato de Python ser uma linguagem com a qual podemos modificar o código dinamicamente em tempo de execução, nós podemos resolver o problema sem depender de pré-processador com uma sintaxe completamente alheia ao resto da linguagem.
A sintaxe, portanto, não é um problema (especialmente ignorando as versões do Python 3 anteriores à 3.3). A outra grande diferença é o comportamento do código, sua semântica e a presença / ausência de grandes bibliotecas apenas para uma das duas versões. Este é realmente um problema significativo, mas não é completamente único ou novo para aqueles que já têm experiência com outras linguagens de programação. Provavelmente, você já obteve uma base de código / biblioteca antiga que falha em construir com versões recentes do mesmo compilador usado originalmente. Nestes casos, é o próprio compilador que o ajudará (em Python, em vez disso, a ajuda virá de seu próprio conjunto de testes).
Por que fazer a nova versão diferente então? Que vantagens essas mudanças nos trarão?
Vamos supor que queremos escrever um programa para ler o proprietário de arquivos / diretórios (em um sistema Unix) em nosso diretório atual e imprimi-los na tela.
# encoding: utf-8 from os import listdir, stat # to keep this example simple, we won't use the `pwd` module names = {1000: 'dario', 1001: u'олга'} for node in listdir(b'.'): owner = names[stat(node).st_uid] print(owner + ': ' + node)
Tudo funciona corretamente? Aparentemente, sim. Especificamos a codificação para o arquivo que contém o código-fonte, se tivermos um arquivo criado por олга (uid 1001) em nosso diretório seu nome será impresso corretamente, e mesmo se tivermos arquivos com nomes não ASCII, eles serão impressos corretamente .
Ainda há um caso que ainda não cobrimos: um arquivo criado por олга E com caracteres não ASCII no nome ...
su олга -c 'touch é'
Vamos tentar lançar novamente nosso pequeno script e obteremos um:
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: ordinal not in range(128)
Se você pensar sobre isso, uma situação semelhante pode ser desagradável: você escreveu seu programa (milhares de linhas ao invés das poucas 4 deste exemplo), você começa a reunir alguns usuários, alguns deles até mesmo de países que não falam inglês com nomes mais exóticos. Tudo está bem, até que um desses usuários decida criar um arquivo que usuários com nomes mais prosaicos possam criar sem nenhum problema. Agora, seu código gerará um erro, o servidor pode responder a todas as solicitações desse usuário com um erro 500 e você precisará cavar a base de código para entender por que esses erros estão aparecendo de repente.
Como o Python 3 nos ajuda com isso? Se você tentar executar o mesmo script, descobrirá que o Python é capaz de detectar imediatamente quando você está prestes a executar uma operação perigosa. Mesmo sem arquivos com nomes peculiares e / ou criados por usuários peculiares, você receberá imediatamente uma exceção como:
`TypeError: Can't convert 'bytes' object to str implicitly`
Relacionado à linha:
print(owner + ': ' + node)
A mensagem de erro é ainda mais fácil de entender, na minha opinião. O objeto str é owner
e node
é um objeto de bytes. Sabendo disso, é óbvio que o problema se deve ao fato de listdir
está nos retornando uma lista de objetos de bytes.
Um detalhe que nem todos sabem é que listdir
retorna uma lista de objetos de bytes ou strings Unicode, dependendo do tipo de objeto que foi usado como entrada. Evitei usar listdir('.')
exatamente para obter o mesmo comportamento no Python 2 e no Python 3, caso contrário, no Python 3 isso seria uma string unicode que faria o bug desaparecer.
Se tentarmos alterar um único caractere, de listdir(b'.')
para listdir(u'.')
poderemos ver como o código agora funciona no Python 3 e no Python 2. Para completar, também devemos alterar 'dario'
para u'dario'
.
Essa diferença no comportamento entre Python 2 e Python 3 é, entretanto, suportada por uma diferença radical em como as duas versões lidam com tipos de string, uma diferença que é percebida principalmente ao portar de uma versão para outra.
Em minha opinião, esta situação é emblemática da máxima: “os divisores podem ser agrupados mais facilmente do que os lumpers podem ser divididos”. O que foi agrupado no Python 2 (strings Unicode e strings padrão de byte, que podem ser livremente coagidas juntas) foi dividido no Python 3.
Por esta razão, ferramentas como 2 a 3 , mesmo que bem escrito e extremamente útil para automatizar a conversão de todas as outras diferenças, tem algumas limitações. Com a divisão bytes / unicode, a diferença nas superfícies de comportamento em tempo de execução, e uma ferramenta que só pode fazer análise / análise estática, portanto, não será capaz de salvá-lo se você tiver uma enorme base de código Python 2 que mistura esses dois tipos. Você terá que arregaçar as mangas e projetar adequadamente sua API para decidir se as funções que até agora aceitavam indiscriminadamente qualquer tipo de string agora devem funcionar apenas com algumas delas (e quais). Por outro lado, embora sejam muito menos utilizadas, as ferramentas de conversão do Python 3 para o Python 2 têm uma vida muito mais fácil. Vejamos um exemplo:
Algum tempo atrás, eu escrevi um servidor HTTP de brinquedo (apenas dependência: python-magic ), e esta é a versão para Python 2 (convertido automaticamente do Python 3 sem a necessidade de alterações manuais): https://gist.github.com/berdario/8abfd9020894e72b310a
Agora, se você quiser, pode dar uma olhada diretamente no código convertido para Python 3 com 2to3, ou você pode convertê-lo diretamente em seu sistema. Ao tentar executá-lo, você perceberá como cada erro que pode tentar corrigir manualmente está relacionado à divisão bytes / unicode.
Você pode aplicar manualmente alterações como estas: https://gist.github.com/berdario/34370a8bc39895cae139/revisions
E assim, você faz com que seu programa funcione novamente no Python 3. Essas não são mudanças complexas, mas, mesmo assim, exigem raciocinar sobre os tipos de dados nos quais suas funções estão trabalhando e sobre o fluxo de controle. São 13 linhas de mudanças em 120, uma proporção não muito fácil de manusear: com milhares de linhas de código para portar, você pode facilmente acabar com centenas para modificar.
Se você estiver curioso, pode tentar converter esse código que acabou de trazer para o Python 3 de volta para o Python 2. Usando 3to2 você obteria isto: https://gist.github.com/berdario/cbccaf7f36d61840e0ed . Em que a única alteração que teve de ser aplicada manualmente é .encode('utf-8')
na linha 55.
Começando com Python 3 (se você precisar convertê-lo de volta para Python 2), é muito mais fácil. Mas se você precisa ter seu código funcionando em outra versão, uma conversão completa como essa não é a melhor escolha. É muito melhor manter a compatibilidade com ambas as versões do Pitão . Para fazer isso, você pode contar com ferramentas como futurizar .
Mesmo se você não tiver a chance de usar Python 3 na produção (talvez uma das bibliotecas que você está usando seja volumosa e compatível apenas com Python 2), sugiro que você mantenha seu código compatível com Python 3 . Você poderia até esboço / simulação as bibliotecas incompatíveis, apenas para que você pudesse executar continuamente seus testes em ambas as versões . Isso tornará mais fácil para você quando, no futuro, você finalmente estiver pronto para migrar para Python 3, sem mencionar como isso pode ajudá-lo a projetar melhor sua API ou identificar erros como no exemplo no início deste postar.
Toda essa conversa sobre portabilidade e diferença de byte / unicode, mesmo que você inicialmente estivesse cético sobre o uso / início do Python 3, provavelmente o levou a pensar nele como o mal menor em vez de lidar com a portabilidade no futuro. Mas se portar é o pau, onde está a cenoura? São os novos recursos adicionados à linguagem e à sua biblioteca padrão?
Bem, depois de 5 anos desde o lançamento da última versão secundária do Python 2, há muitos boatos interessantes que estão se acumulando. Por exemplo, eu me descobri confiando frequentemente em coisas como as novas argumentos apenas de palavra-chave .
Quando eu queria escrever uma função para mesclar um número arbitrário de dicionários (semelhante ao que dict.update
faz, mas sem modificar as entradas), achei natural adicionar um argumento de função para permitir que o chamador personalize a lógica. Dessa forma, essa função pode ser chamada da seguinte maneira para simplesmente mesclar vários dicionários, mantendo os valores nos dictos mais à direita.
merge_dicts({'a':1, 'c':3}, {'a':4, 'b':2}, {'b': -1}) # {'b': -1, 'a': 4, 'c': 3}
Da mesma forma, para mesclar adicionando os valores:
from operator import add merge_dicts({'a':1, 'c':3}, {'a':4, 'b':2}, {'b': -1}, withf=add) # {'b': 1, 'a': 5, 'c': 3}
A implementação de uma API em Python 2 teria que definir um **kwargs
entrada e procure o withf
argumento. Se o chamador digitou incorretamente o argumento como (por exemplo) withfun
o erro seria silenciosamente ignorado, no entanto. Em Python 3, em vez disso, é perfeitamente normal adicionar um argumento opcional após os argumentos variáveis (e ele será utilizável apenas com sua palavra-chave):
def second(a, b): return b def merge_dicts(*dicts, withf=second): newdict = {} for d in dicts: shared_keys = newdict.keys() & d.keys() newdict.update({k: d[k] for k in d.keys() - newdict.keys()}) newdict.update({k: withf(newdict[k], d[k]) for k in shared_keys}) return newdict
Desde Python 3.5, a fusão ingênua pode realmente ser feita com o novo operador de desempacotamento . Mas mesmo antes do 3.5, o Python teve uma forma aprimorada de desempacotamento:
a, b, *rest = [1, 2, 3, 4, 5] rest # [3, 4, 5]
Isso está disponível para nós desde 3.0. Semelhante à desestruturação, este tipo de desempacotamento é uma forma limitada / ad-hoc da correspondência de padrões comumente usada em linguagens funcionais (onde também é usada para controle de fluxo) e é um recurso comum em linguagens dinâmicas como Ruby e Javascript (onde suporte para EcmaScript 2015 está disponível).
exemplos da vida real de semelhança gestalt
No Python 2, muitas APIs que lidavam com iteráveis foram duplicadas e as padrão tinham semântica estrita. Agora, em vez disso, tudo irá gerar valores conforme necessário: zip()
, dict.items()
, map()
, range()
. Você deseja escrever sua própria versão de enumerate
? No Python 3, é tão simples quanto compor funções da biblioteca padrão juntas:
zip(itertools.count(1), 'abc')
É equivalente a enumerate('abc', 1)
.
Você não gostaria de definir APIs HTTP de maneira tão simples?
@get('/balance') def balance(user_id: int): pass from decimal import Decimal @post('/pay') def pay(user_id: int, amount: Decimal): pass
Não há mais ''
sintaxe ad-hoc e a capacidade de usar qualquer tipo / construtor (como Decimal
) dentro de suas rotas sem ter que definir seu próprio conversor.
Algo assim já foi implementado , e o que você vê é uma sintaxe Python válida, explorando as novas anotações para tornar mais conveniente escrever APIs que também são autodocumentáveis.
Esses são apenas alguns exemplos simples, mas as melhorias são de longo alcance e, em última análise, ajudam a escrever um código mais robusto. Um exemplo são os rastreamentos de cadeia de exceção habilitados por padrão, apresentados na postagem apropriadamente nomeada “O recurso mais subestimado do Python 3” por Ionel Cristian Mărieș, que também é abordado neste outro post de Aaron Maxwell, junto com a semântica de comparação mais rígida do Python 3 e o novo super
comportamento.
Isso não é tudo. tem muitas outras melhorias , estes são os que eu sinto que têm o maior impacto no dia a dia:
.pyc
arquivosUm panorama mais completo pode ser obtido com o 'O que há de novo' páginas da documentação, ou para outra visão geral das mudanças, também sugiro este outro postar por Aaron Maxwell e estes slides de Brett Cannon.
Python 2.7 será compatível até 2020 , mas não espere até 2020 para mudar para uma nova (e melhor) versão!
Relacionado: Padrões de design Python: para código elegante e moderno