Avançar para a área de conteúdo

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Todas as informações enviadas são seguras.

  • Fechar [x]

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Todas as informações enviadas são seguras.

  • Fechar [x]

Instruções do Python 3, Parte 2: Tópicos Avançados

Metaclasses, decoradores e outras criaturas estranhas

Cesar Otero, Consultant, Freelance Consultant
Cesar Otero
Cesar Otero é consultor freelancer de Java e Python. É formado em engenharia elétrica com ênfase em matemática.

Resumo:  Python 3 é a versão mais recente da poderosa linguagem de programação com propósitos gerais de Guido van Rossum. Ele rompe com questões de compatibilidade anteriores à versão 2x, eliminando alguns problemas de sintaxe. Este segundo artigo foi criado a partir do artigo anterior. Na Parte 2, desta série de duas partes, descubra novos recursos do Python e detalhes sobre tópicos mais avançados como alterações nas classes abstratas de base , metaclasses e decoradores.

Visualizar mais conteúdo nesta série

Data:  30/Jan/2009
Nível:  Intermediário
Atividade:  16012 visualizações
Comentários:  


O artigo anterior artigo anterior sobre o Python versão 3—também conhecido como Python 3000ou Py3K— abrange algumas mudanças simples no Python, que romperão com as questões de compatibilidade com versões anteriores, como a nova função print() no tipo de dados bytes e alterações no tipo de string. Esse artigo, Parte 2, explora alguns dos tópicos mais avançados, como classes abstratas de base (ABCs), metaclasses, decoradores e anotações de funções, suporte a número inteiro literal, a hierarquia de tipo numérico e alterações em exceções lançadas e capturadas, a maioria também rompendo com as questões de compatibilidade à versão 2x anterior.

Decoradores de Classe

Nas versões anteriores do Python, as transformações em um método tinham de ser feitas após a definição de tal método. Nos métodos maiores, esse requisito mantinha um importante componente de definição longe da definição da interface externa fornecida no Python Enhancement Proposal (PEP) 318 (consulte Recursos para obter um link). Este trecho mostra um exemplo desse requisito de transformação:


Lista 1. Transformações de Métodos no Python Antes da Versão 3

def myMethod(self):
    # efetuar alguma ação

myMethod = transformMethod(myMethod)

Para tornar situações como esta mais legíveis e evitar a reutilização do mesmo nome de método várias vezes, os decoradores de métodos foram introduzidos na versão 2.4 do Python.

Decoradores são métodos que modificam outros métodos e retornam um método ou outro objeto que pode ser chamado. Eles são denotados com um símbolo "arroba" (@) antes do nome do decorador—uma sintaxe semelhante às anotações Java™. A Lista 2 mostra decoradores em ação.


Lista 2. Um Método do Decorador

@transformMethod
def myMethod(self):
    # efetuar alguma ação

Decoradores são açúcares sintáticos puros—ou (de acordo com a Wikipedia) "inclusões na sintaxe de uma linguagem de computador que não afetam sua funcionalidade, mas a torna 'mais doce' para uso." Um uso comum dos decoradores é a anotação dos métodos estáticos. As Listagens 1 e 2 são equivalentes, mas a Lista 2 é mais fácil de ler.

Você define um decorador como qualquer outro método:

def mod(method):
    method.__name__ = "John"
    return method

@mod
def modMe():
    pass

print(modMe.__name__)

Além disso, o Python 3 agora suporta decoradores não apenas para métodos, mas também para classes. Dessa forma, em vez de usar:

class myClass:
    pass

myClass = doSomethingOrNotWithClass(myClass)

é possível usar o seguinte:

@doSomethingOrNotWithClass
class myClass:
    pass


Metaclasses

Metaclasses são classes cujas instâncias são outras classes. O Python 3 manteve o tipo de metaclasse, usado para criar outras metaclasses ou criar dinamicamente classes em tempo de execução. Ainda é válido usar a sintaxe:

>>>aClass = type('className',
   (object,),
   {'magicMethod': lambda cls : print("blah blah")})

que aceita como seu argumento uma cadeia como o nome da classe, um conjunto de objetos herdados (que pode ser um conjunto vazio) e um dicionário (que também pode ser vazio) contendo métodos que podem ser inclusos. É claro que também é possível herdar de tipos e criar sua própria metaclasses:

class meta(type):
    def __new__(cls, className, baseClasses, dictOfMethods):
        return type.__new__(cls, className, baseClasses, dictOfMethods)

Argumentos apenas com Palavras-chave

O Python 3 mudou o modo como os argumentos de função são designados para "slots de parâmetros." É possível usar um asterisco (* ) nos parâmetros transmitidos para não aceitar argumentos com comprimentos variáveis. Os argumentos nomeados devem aparecer após um espaço *—por exemplo, def meth(*, arg1): pass. Consulte a documentação do Python ou PEP 3102 para obter informações adicionais (consulte Recursos para obter links).

Nota: Se os dois últimos exemplos ainda não fazem sentido, recomendamos que você leia a série sobre metaclasses de David Mertz e Michele Simionato. Consulte Recursos para obter um link.

Observe que agora, em uma definição de classe, argumentos com palavra-chave são permitidos após a lista de classes base—no geral, class Foo(*bases, **kwds): pass. A metaclasse é transmitida na definição de classe, usando, de forma conveniente, o argumento com palavra-chave metaclass. Por exemplo:

>>>class aClass(baseClass1, baseClass2, metaclass = aMetaClass): pass

A antiga sintaxe das metaclasses era designar a metaclasse para o atributo__metaclass__:

class Test(object):
    __metaclass__ = type

Além disso, um novo atributo—__prepare__—foi incluído. Use tal atributo para criar o dicionário para o novo namespaceda classe. Ele é chamado antes da avaliação do corpo da classe, como mostra a Lista 3.


Lista 3. Uma Metaclasse Simples que Usa o Atributo __prepare__

def meth():
    print("Calling method")

class MyMeta(type):
    @classmethod
    def __prepare__(cls, name, baseClasses):
        return {'meth':meth}

    def __new__(cls, name, baseClasses, classdict):
        return type.__new__(cls, name, baseClasses, classdict)

class Test(metaclass = MyMeta):
    def __init__(self):
        pass

    attr = 'an attribute'

t = Test()
print(t.attr)

Um exemplo mais interessante, retirado literalmente do PEP 3115 e mostrado na Lista 4, cria uma metaclasse com uma lista de nomes de seus métodos, enquanto mantém a ordem na qual os métodos de classes foram declarados.


Lista 4. Uma Metaclasse que Preserva a Ordem dos Membros da Classe

# O dicionário customizado
class member_table(dict):
    def __init__(self):
        self.member_names = []

    def __setitem__(self, key, value):
        # se a chave ainda não estiver definida, inclua-a na
        # lista de chaves.
        se a chave não for própria:
            self.member_names.append(key)

        # Chame a superclasse
        dict.__setitem__(self, key, value)

# A metaclasse
class OrderedClass(type):
    # A função de preparação
    @classmethod
    def __prepare__(metacls, name, bases): # Nenhuma palavra-chave neste caso
        return member_table()

    # A inovação da metaclasse
    def __new__(cls, name, bases, classdict):
        # Observe que substituímos classdict por um
        # dict comum antes de transmiti-lo para a superclasse, assim
        # o registro de nomes dos membros não continua depois que a classe
        # for criada.
        result = type.__new__(cls, name, bases, dict(classdict))
        result.member_names = classdict.member_names
        return result

Há alguns motivos para as alterações nas metaclasses. Os objetos armazenam seus métodos em um dicionário, sem nenhuma ordem. No entanto, há casos nos quais a preservação da ordem dos membros de classe declarados seria útil. Isso é feito "envolvendo" a metaclasse antes da criação da classe, quando as informações ainda estão disponíveis— útil, por exemplo, na criação de estruturas C. Esse novo mecanismo permite que outras possibilidades interessantes sejam implementadas no futuro, como símbolos de inserção no corpo do namespacede classe criado durante a criação da classe e mais referências de símbolos. O PEP 3115 também menciona que há motivos estéticos para a alteração da sintaxe, mas este é um debate que não pode ser resolvido objetivamente. (Consulte Recursos para obter um link para o PEP 3115).


Classes Abstratas de Base

Como foi mencionado no artigo anterior desta série, ABCs são classes que não podem ser instanciadas. Os programadores de linguagens Java ou C++ devem se familiarizar com este conceito. O Python 3 inclui uma nova estrutura—abc—que fornece suporte para trabalhar com ABCs.

O módulo abc tem uma metaclasse (ABCMeta) e decoradores (@abstractmethod e @abstractproperty). Se um ABC tiver um @abstractmethod ou um @abstractproperty, ele não poderá ser instanciado, mas deverá ser substituído em uma subclasse. Por exemplo, o código:

>>>from abc import *
>>>class C(metaclass = ABCMeta): pass
>>>c = C()

está correto, mas não faça o seguinte:

>>>from abc import *
>>>class C(metaclass = ABCMeta):
...    @abstractmethod
...    def absMethod(self):
...        pass
>>>c = C()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class C with abstract methods absMethod

Ou melhor, use o código:

>>>class B(C):
...    def absMethod(self):
...        print("Now a concrete method")
>>>b = B()
>>>b.absMethod()
Now a concrete method

A clase ABCMeta sobrescreve os atributos __instancecheck__ e __subclasscheck__, providenciando uma maneira de sobrecarregar a função integrada isinstance() e issubclass(). Para incluir uma subclasse virtual em seu ABC, use o método register() que ABCMeta fornece. O exemplo simples:

>>>class TestABC(metaclass=ABCMeta): pass
>>>TestABC.register(list)
>>>TestABC.__instancecheck__([])
True

equivale ao uso de isinstance(list, TestABC). Você deve ter notado que o Python 3 usa __instancecheck__ em vez de __issubclass__ e __subclasscheck__ no lugar de __issubclass__, o que parece mais natural. O motivo é que a reversão dos argumentos isinstance(subclass, superclass), por exemplo, para superclass.__isinstance__(subclass) pode causar confusão. Assim, a sintaxe superclass.__instancecheck__(subclass) vence.

No módulo collections, é possível usar vários ABCs para testar se uma classe fornece uma interface específica:

>>>from collections import Iterable
>>>issubclass(list, Iterable)
True

A Tabela 1 mostra os ABCs do framework de coletas.


Tabela 1. Os ABCs do Framework de Coletas
ABCHerda
Contêiner
Hashable
Iterable
Agente Iterativo Iterable
Tamanho
Pode ser chamado
Sequência Tamanho, Iterable, Contêiner
Sequencia_mutável Sequência
Configurar Tamanho, Iterable, Contêiner
Configuração_mutável Configurar
Mapeamento Tamanho, Iterable, Contêiner
Mapeamento_mutável Mapeamento
Visualização_de_mapeamento Tamanho
Visualização_de_chaves Visualização_de_mapeamento, Configurar
Visualização_de_itens Visualização_de_mapeamento, Configurar
Visualização_de_valores Visualização_de_mapeamento

Coletas

O framework de coletas consiste de tipos de dados de contêineres e filas duplas (conhecidas como deques) e um dicionário padrão (conhecido como defaultdict ). Uma deque suporta anexos e inserções no início ou fim. O contêiner defaultdict, uma subclasse do dicionário embarcado (de acordo com a documentação do Python 3) "substitui um método e inclui uma variável de instância gravável." Ele também atua como um dicionário. O framework de coletas também fornece uma função de fábrica de tipo de dados, namedtuple().

A Hierarquia de Tipo ABC

Agora o Python 3 suporta uma hierarquia de tipo de ABCs que representa classes numéricas. Esses ABCs residem nos módulos numbers e incluem Number, Complex, Real, Rational e Integral. A Figura 1 mostra a hierarquia de números. É possível, obviamente, usá-la para implementar seu próprio tipo numérico ou outro ABC numérico.


Figura 1. A Hierarquia Numérica
A Hierarquia Numérica

A torre numérica

A hierarquia numérica do Python foi inspirada pela torre numérica da linguagem de esquema.

Um novo módulo, fractions, implementa o ABC numéricoRational. Esse módulo fornece suporte para números aritméticos racionais. Se você usa dir(fractions.Fraction), notará que ele tem atributos como imag, real e __complex__. Seguindo a torre numérica, isso ocorre porque Rationals são herdados de Reals, que são herdados de Complex.


Lançando e capturando exceções

No Python 3, as cláusulas de except foram alteradas para lidarem com um problema de ambiguidade sintática. Anteriormente, no Python versão 2.5, uma construção try . . . except como:

>>>try:
...    x = float('not a number')
... except (ValueError, NameError):
...    print "can't convert type"

podia ser escrita incorretamente como:

>>> try:
...    x = float('not a number')
... except ValueError, NameError:
...    print "can't convert type"

O problema com a forma anterior é que a exceção ValueError nunca será capturada porque o intérprete irá capturar ValueError e ligar o objeto de exceção ao nome NameError. Isso pode ser observado no próximo exemplo:

>>> try:
...    x = float('blah')
... except ValueError, NameError:
...    print "NameError is ", NameError
...
NameError is invalid literal for float(): not a number

Assim, para lidar com as ambiguidades, a vírgula (,) é substituída pela palavra-chave as quando você deseja ligar o objeto de exceção a outro nome. Se deseja obter várias exceções, os parênteses (()) são necessários. O código na Lista 5 mostra dois exemplos legítimos no Python 3.


Lista 5. Manipulação de Exceções no Python 3

# ligar o objeto ValueError à exceção de nome local
try:
    x = float('blah')
except ValueError as ex:
    print("value exception occurred ", ex)

# obter duas exceções diferentes simultaneamente
try:
    x = float('blah')
except (ValueError, NameError):
    print("caught both types of exceptions")

Outra alteração na manipulação de exceção é o encadeamento de exceções—implícito ou explícito. A Lista 6 mostra um exemplo de uma cadeia de exceção implícita.


Lista 6. Uma Cadeia de Exceção Implícita no Python 3

def divide(a, b):
    try:
        print(a/b)
    except Exception as exc:
        def log(exc):
        fid = open('logfile.txt') # missing 'w'
        print(exc, file=fid)
        fid.close()

        log(exc)

divide(1,0)

O método divide() tenta executar uma divisão por zero e levanta uma exceção: ZeroDivisionError. Mas, na cláusula de exceções, dentro do método log() aninhado, print(exc, file=fid) tenta gravar em um arquivo que não está aberto para gravação. O Python 3 levanta as exceções mostradas na Lista 7.


Lista 7. A Análise Retrospectiva do Exemplo de Exceção em Cadeia

Traceback (most recent call last):
  File "chainExceptionExample1.py", line 3, in divide
  print(a/b)
ZeroDivisionError: int division or modulo by zero

Durante a manipulação da exceção acima, outra exceção ocorreu:

Traceback (most recent call last):
  File "chainExceptionExample1.py", line 12, in <module>
    divide(1,0)
  File "chainExceptionExample1.py", line 10, in divide
    log(exc)
  File "chainExceptionExample1.py", line 7, in log
    print(exc, file=fid)
  File "/opt/python3.0/lib/python3.0/io.py", line 1492, in write
    self.buffer.write(b)
  File "/opt/python3.0/lib/python3.0/io.py", line 696, in write
    self._unsupported("write")
  File "/opt/python3.0/lib/python3.0/io.py", line 322, in _unsupported
    (self.__class__.__name__, name))
io.UnsupportedOperation: BufferedReader.write() not supported

Observe que as duas exceções foram manipuladas. Nas versões anteriores do Python, ZeroDivisionError seria perdido. Como isso acontecia? Um atributo __context__ como ZeroDivisionError agora faz parte de todos os objetos de exceção. Neste caso, o atributo __context__ de IOError levantou a "retenção" de ZeroDivisionError no atributo __context__.

Além do atributo __context__, objetos de exceção têm um atributo __cause__, que sempre é inicializado como None. O propósito desse atributo é fornecer uma forma explícita de registrar a causa de uma exceção. O atributo __cause__ é definido com a seguinte sintaxe:

>>> raise EXCEPTION from CAUSE

Isso é exatamente igual à codificação:

>>>exception = EXCEPTION
>>>exception.__cause__ = CAUSE
>>>raise exception

mas muito mais elegante. O exemplo na Lista 8 mostra o encadeamento de exceções explícito.


Lista 8. Encadeamento de Exceções Explícitos no Python 3

class CustomError(Exception):
    pass

try:
    fid = open("aFile.txt") # 'w' ausente novamente
    print("blah blah blah", file=fid)
except IOError as exc:
    raise CustomError('something went wrong') from exc

Como no exemplo anterior, a função print() levanta uma exceção, pois o arquivo não estava aberto para gravação. A Lista 9 mostra o traceback


Lista 9. A Análise Retrospectiva da Exceção

Traceback (most recent call last):
  File "chainExceptionExample2.py", line 5, in <module>
    fid = open("aFile.txt")
  File "/opt/python3.0/lib/python3.0/io.py", line 278, in __new__
    return open(*args, **kwargs)
  File "/opt/python3.0/lib/python3.0/io.py", line 222, in open
    closefd)
 File "/opt/python3.0/lib/python3.0/io.py", line 615, in __init__
    _fileio._FileIO.__init__(self, name, mode, closefd)
IOError: [Errno 2] No such file or directory: 'aFile.txt'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "chainExceptionExample2.py", line 8, in <modulei>
  raise CustomError('something went wrong') from exc
__main__.CustomError: something went wrong

Observe que, no traceback, a linha "The above exception was the direct cause of the following exception", que é seguida por outra análise retrospectiva, resulta em CustomError, "something went wrong."

Finalmente, outro atributo ainda incluído nos objetos de exceção é o atributo __traceback__. Se uma exceção obtida não tiver seu atributo __traceback__, a nova análise retrospectiva será definida. Este é um exemplo simples:

from traceback import format_tb

try:
    1/0
except Exception as exc:
    print(format_tb(exc.__traceback__)[0])

Observe que format_tb retorna uma lista e há apenas uma exceção nesta lista.

A Instrução with

Como na versão 2.6 do Python, com agora é uma palavra-chave e esse recurso não precisa mais ser importado usando __future__.


Suporte a número inteiro literal e sintaxe

O Python suporta strings de números inteiros de diferentes bases—octal, decimal e hexadecimal—e agora o suporte a binários foi incluído. A representação dos literais octais mudou: Literais octais agora são representados com um 0o ou 0O prefixado (ou seja, um zero seguido da letra o maiúscula ou minúscula) e o literal que será avaliado. Por exemplo, um octal 13 ou decimal 11 é representado como:

>>>0o13
11

Os novos literais binários são representados com um 0b ou 0B prefixado (ou seja, um zero seguido da letra b maiúscula ou minúscula). A representação do decimal 21 em binários é então:

>>>0b010101
21

Os métodos oct() e hex() foram removidos.


Anotações de Funções

Anotações de Funções associam expressões com partes de uma função, como parâmetros, no momento da compilação. As anotações de funções, por si só, são insignificantes—ou seja, não são processadas, a menos que uma biblioteca de terceiros o faça. O propósito das anotações de funções é padronizar a forma como parâmetros ou valores de retorno de uma função são anotados. A sintaxe para as anotações de funções é:

def methodName(param1: expression1, ..., paramN: expressionN)->ExpressionForReturnType:
    ...

Por exemplo, aqui estão anotações para os parâmetros de uma função:

def execute(program:"name of program to be executed", error:"if something goes wrong"):
    ...

O seguinte exemplo anota o valor de retorno de uma função. Isso é útil para a verificação do tipo de retorno de uma função:

def getName() -> "isString":
     ...

A gramática completa das anotações de funções pode ser encontrada no PEP 3107 (consulte Recursos para obter um link).


Conclusão

Um Ovo de Páscoa

Na minha opinião, o melhor módulo incluído no Python 3 é antigravity. Inicie o Python e digite import antigravity na linha de comandos. Você não ficará desapontado.

O release final do Python 3 foi publicado em dezembro de 2008. Desde lá, eu tive a chance de verificar alguns blogs e como as pessoas estão reagindo em relação a problemas de compatibilidade. Embora não seja possível ter um consenso oficial, os blogs que eu li certamente parecem polarizados. Algumas pessoas na comunidade de desenvolvimento Linux® realmente não parecem aprovar a transição para a versão 3 devido à quantidade massiva de código que precisa ser transmitida. Por outro lado, muitos desenvolvedores da Web farão a transição devido às alterações no suporte unicode.

Eu o aconselho a, no mínimo, antes de qualquer opinião, consultar os PEPs e as listas de correspondências de desenvolvimento para decidir se passará ou não para a nova versão. Os PEPs explicam em termos racionais uma determinada alteração e os benefícios obtidos com a implementação. Essas alterações foram realmente boas e profundamente discutidas. Os tópicos abrangidos nesta série foram apresentados para que o programador Python comum possa obter um rápido entendimento quanto às alterações sem precisar percorrer todos os PEPs.


Recursos

Aprender

Obter produtos e tecnologias

Sobre o autor

Cesar Otero

Cesar Otero é consultor freelancer de Java e Python. É formado em engenharia elétrica com ênfase em matemática.

Ajuda para Relatar Abuso

Relatar abuso

Obrigado. Esta entrada foi sinalizada para atenção do moderador.


Ajuda para Relatar Abuso

Relatar abuso

Falha no envio do Relatório de abuso. Tente novamente mais tarde.


developerWorks: Registre-se


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Selecione seu nome de exibição

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

(Deve possuir de 3 a 31 caracteres.)


Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Classificar este artigo

Comentários

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Linux, Software livre
ArticleID=382564
ArticleTitle=Instruções do Python 3, Parte 2: Tópicos Avançados
publish-date=01302009
author1-email=hfrequency@users.sourceforge.net
author1-email-cc=

Conheça a IBM da sua cidade

Virtual Branch Office Brasil

A IBM está mais perto do que você imagina!


Tags

Help
Use o campo de pesquisa para encontrar todos os tipos de conteúdo no My developerWorks com essa tag.

Use a barra de rolagem para ver mais ou menos tags.

Tags populares mostra as principais tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Minhas tags mostra suas tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Use o campo de pesquisa para localizar todos os tipos de conteúdo no Meu developerWorks com essa tag. Tags populares mostra as tags principais para essa zona de conteúdo particular (por exemplo, tecnologia Java, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere). Minhas tags mostra as suas tags para essa zona de conteúdo em particular (por exemplo, tecnologia Java, Linux, WebSphere).