Skip to content

Instantly share code, notes, and snippets.

@jsbueno
Created March 8, 2022 02:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jsbueno/7e482a67098ac0ccc9eb560a27d6f521 to your computer and use it in GitHub Desktop.
Save jsbueno/7e482a67098ac0ccc9eb560a27d6f521 to your computer and use it in GitHub Desktop.
Novidades e tecnologias na versão 2.2 do Python
Um pouco de história do #Python: a "mudadora de mundo: versão 2.2, um fio:
Há pouco mais de 20 anos, em 21 de dezembro de 2001, foi lançada a versão 2.2 do Python. Essa provavelmente foi a versão mais importante da linguagem, fora a versão "3.0" em si, com muito pouca quebra de compatibilidade.
I) A principal mudança: a chegada das "New Style Classes", com as quais as classes criadas em Python passaram a ser "cidadãs de primeira classe". Antes disso, existiam "classes" criadas em Python e os "types", os tipos nativos da linguagem, como int, list, float - as duas coisas não se conversavam -
PEP 252 -- Making Types Look More Like Classes -- https://www.python.org/dev/peps/pep-0252/
PEP 253 -- Subtyping Built-in Types -- https://www.python.org/dev/peps/pep-0253/
As new style classes além da unificação de tipos, trouxeram:
1. o "descriptor protocol" o protocolo interno que até hoje é responsável
pelo funcionamento de métodos, das "properties", possibilita
classmethod, staticmethod, e possibilita a implementação de ORMs em geral. A própria chamada built-in property também apareceu aqui, na versão 2.2.
2. Um novo algoritmo de ordem de resolução de métodos (MRO) usado em herança múltipla, que efetivamente resolvia uma falha de design em heranças múltiplas nas "old style classes". O algoritmo sofreu mais um pequeno ajuste na 2.3 para chegar na forma "final e perfeita" que é usada até hoje, mas a forma de busca "em largura primeiro" da versão 2.2 já resolvia quase todos os casos.
3. Referencias fracas (weakref) - elas apareceram no Python 2.1 com a PEP-205 (https://www.python.org/dev/peps/pep-0205/#implementation-strategy) - mas não estavam disponíveis para uso em classes em Python puro antes das new-style classes
4. a possibilidade de escolher como uma classe é criada, através do uso de metaclasses. Efetivamente Python já tinha 2
metaclasses, uma para os tipos nativos, outra para as classes criadas em Python, mas os usuários não só não podiam criar uma nova, como não tinham nenhum controle sobre a classe usada. Com as news style classes, a metaclasse pode passar a ser explicitada com o atributo de nome especial "__metaclass__", bem como customizada em Python puro. (a criação das metaclasses customizáveis está na entrelinhas da PEP 253 )
5. O builtin "super" que permite a chamada de um método na super-classe sem que tenha que se reimplementar o algoritmo de MRO. Notem que nessa versão era obrigatório passar 2 parâmetros explícitos para a chamada "super" como em `super(MinhaClasse, self).__init__`. A forma "sem parâmetros" de super só foi possível na versão 3.0 da linguagem e é um dos poucos hacks "por fora" do tempo de execução - os dois parâmetros são inseridos de forma "escondida" em tempo de compilação. (https://www.python.org/dev/peps/pep-3135)
5. Atributos com dunderscore padronizados e explícitos, como __dict__, __class__, etc...
6. Customização de acesso a atributos para (a) qualquer acesso com __getattribute__ e __setattr__ e (b) apenas para atributos não existentes, com __getattr__.
E essa das novidades das new-style classes não é exaustiva (sobre as weakrefs mesmo, só me dei conta que só entraram na linguagem efetivamente com essas classes, e portanto na versão 2.2 enquanto estava escrendo isso)
II) Escopos de função aninhados - Embora existindo desde a versão 2.1, na 2.2 que passaram a ser o comportamento padrão. Em Python 2.1 tinha que ser ativado com `from __future__ import nested_scopes`.
Isso aqui é o que permite que se leia uma variável de uma "função de fora" quando se escreve uma função dentro de outra incluindo quando se escreve um lambda. Sem essa funcionalidade, um lambda em Python não teria acesso a nenhuma variável da função circundante. Durante todo o ciclo do Python 2, esse acesso permaneceu sendo somente de leitura e somente no Python 3.0, com a palavra chave "nonlocal" que foi possível escrever nessas variáveis
PEP 227 -- Statically Nested Scopes -- https://www.python.org/dev/peps/pep-0227/
III) Mudança do operador de divisão. Em Python 2, uma divisão de operadores inteiros normalmente sempre tinha um resultado inteiro - 3/2 resultava em "1", e nunca "1.5". A PEP 238, implementada na versão 2.2 que adicionou o operador "//" para "floor division" que sempre replica esse comportamento, e o "from __future__ import division" que permite resultados float pra divisões de inteiro, resultando em um comportamento _muito_ mais natural pra linguagens de mais alto nível. Infelizmente, por questões de retrocompatibilidade, o comportamento de "preservar o tipo" permaneceu o padrão na linguagem até a versão 3.0, onde a compatibilidade poderia ser quebrada nesses casos.
PEP 238 -- Changing the Division Operator -- https://www.python.org/dev/peps/pep-0238/
IV) Unificação de inteiros longos e inteiros curtos: até a versão 2.1 esses dois tipos eram distintos, e uma operação matemática cujo resultado não coubesse em 2^32 daria um erro de overflow. Pior APIs internas das bultins de Python que aceitavam inteiros curtos quebravam se fossem chamadas com inteiros longos. A partir da versão 2.2, os dois tipos continuaram a existir, mas a linguagem criava ou convertia os tipos de forma 100% transparente para os usuários. Ao longo das versões 2.x isso ainda era visível pelo sufixo "L" na representação de números grandes. Novasmente, a partir do Python 3, ocorreu a "unificação real" e só existe _uma_ classe de inteiros exposta no core da linguagem.
PEP 237 -- Unifying Long Integers and Integers -- https://www.python.org/dev/peps/pep-0237/
V) Protocolo de Iterador - Nesta versão também, os métodos especiais "__iter__" e "next" passaram a existir junto com o "Iterator Protocol" - que permite que objetos que não necessariamente tenham seus dados acessíveis de forma aleatória possam ser usados em um loop "for", ou em outras construções da linguagem. Antes disso, apenas objetos com um __len__ definido e __getitem__ poderiam funcionar em um "for" (observe que até hoje - Python 3.11, o comando for ainda funciona das duas formas: se um objeto não implementar __iter__, __len__ e __getitem__ são usados)
PEP 255 -- Simple Generators -- https://www.python.org/dev/peps/pep-0255/
VI) Geradores, ou "Generator" e "Generator Functions" e a palavra chave "yield": como parte da mesma PEP 255, a versão 2.2 também foi a primeira a suportar as "generator functions": a mesma sintaxe de declaração de funções usada para criar objetos completamente diferentes: funções que podem ser "pausadas", seu resultado é usado no programa externamente, e "continuadas" na sequência, mantendo todos seu estado interno (variáveis locais). Elas são expostas para o usuário em Python puro como objetos que implementam "next" ("__next__" em Python 3) - mas internamente, a mudança de contexto para um generator, comparada com uma chamada de função, é muito mais eficiente (essa diferença só vai ser superada na versão 3.11 da linguagem, se for). Para se poder usar Generator Functions e a palavra chave "yield" em Python 2.2 era necessária a declaração "from __future__ import generators", afim de se evitar quebra de compatibilidade devido a nova palavra chave.
É importante notar que essa funcionalidade é a base das "co-rotinas" e todo o suporte a progamação assíncrona na linguagem. Conforme as pessoas foram entendendo e se sentindo confortáveis, mais e mais geradores eram efetivamente usados como "co-rotinas" - até criação e desenvolvimento do código da PEP 3156 que resultou no módulo "asyncio" na biblitoeca padrão, a partir da versão 3.4, e só a partir da versão 3.5 o suporte sintático com as palavtas chave "async" e "await". Houve mudanças entre a implementação inicial na versão 2.2 e a 2.6 - principalmente a possibilidade de retornar um valor de geradores, e de se passar valores para dentro da função em execução com a chamada "send". Elas foram acontecendo gradualmente em entre as versões 2.2 e 2.6.
VII) Suporte a caracteres unicode "largos" - até a versão 2.1, Python só suportava os caracteres unicode com codepoints de 16 bits, estando limitado a 65536 caracteres - isso deixaria de fora quase todos os emojis que usamos hoje, por exemplo. Na versão 2.2 foi incluído o suporte a Unicode com 4 bytes de largura abrangendo toda a extensão da especificação, mas com um "gotcha" - como todas as strings internas de unicode passavam de 2 bytes por carácter para 4 byters por carácter, foi considerado que para alguns workloads a mudança podia resultar num consumo de recursos muito grandes, então por toda a vida útil do Python 2 foi mantida a possibilidade de se criar builds "narrow" da linguagem, com suporte limitado a Unicode, mas cujas strings de texto internas usavam apenas 2 bytes por caractere. Isso implicou que na existência de dois runtimes incompatíveis da versão 2.2 até a 2.7 do Python, e qualquer um distribuindo pacotes precompilados tinha que suportar os dois tipos de build. Naturalmente aversão "wide" era a padrão e muito mais usada.
PEP 261 -- Support for "wide" Unicode characters -- https://www.python.org/dev/peps/pep-0261/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment