Você está na página 1de 56

Produtividade e qualidade

em Python através da
metaprogramação
ou

a “visão radical” na prática
Luciano Ramalho

ramalho@python.pro.br

@ramalhoorg

Fluent Python (O’Reilly)
• Early Release: out/2014

• First Edition: jul/2015

• ~ 700 páginas

• Data structures

• Functions as objects

• Classes and objects


• Control flow

• Metaprogramming
PythonPro: escola virtual
• Instrutores: Renzo Nuccitelli e Luciano Ramalho

• Na sua empresa ou online ao vivo com 

Adobe Connect: http://python.pro.br

• Python Prático

• Python Birds

• Objetos Pythônicos

• Python para quem sabe Python
Simply Scheme: preface
Nosso objetivo
• Expandir o vocabulário com uma idéia
poderosa:

descritores de atributos
O cenário
• Comércio de alimentos a granel

• Um pedido tem vários itens

• Cada item tem descrição, 

peso (kg), preço unitário (p/ kg)
e sub-total

➊ o primeiro doctest
=======
Passo 1
=======
!
Um pedido de alimentos a granel é uma coleção de ``ItemPedido``.
Cada item possui campos para descrição, peso e preço::
!
>>> from granel import ItemPedido
>>> ervilha = ItemPedido('ervilha partida', 10, 3.95)
>>> ervilha.descricao, ervilha.peso, ervilha.preco
('ervilha partida', 10, 3.95)
!
Um método ``subtotal`` fornece o preço total de cada item::
!
>>> ervilha.subtotal()
39.5
➊ mais simples, impossível

o método
inicializador é
conhecido como
class ItemPedido(object):! “dunder init”
!
def __init__(self, descricao, peso, preco):!
self.descricao = descricao!
self.peso = peso!
self.preco = preco!
!
def subtotal(self):!
return self.peso * self.preco
➊ porém, simples demais
>>> ervilha = ItemPedido('ervilha partida', .5, 7.95)
>>> ervilha.descricao, ervilha.peso, ervilha.preco
('ervilha partida', .5, 7.95)
>>> ervilha.peso = -10
>>> ervilha.subtotal()
isso vai dar
-79.5 problema na
hora de cobrar...
“Descobrimos que os clientes

conseguiam encomendar uma

quantidade negativa de livros!

E nós creditávamos o valor em

seus cartões...” Jeff Bezos

Jeff Bezos of Amazon: Birth of a Salesman



WSJ.com - http://j.mp/VZ5not
➊ a solução clássica
class ItemPedido(object):!
!
def __init__(self, descricao, peso, preco):!
self.descricao = descricao!
self.set_peso(peso)!
self.preco = preco! mudanças
! na
def subtotal(self):!
return self.get_peso() * self.preco!
interface
!
def get_peso(self):!
return self.__peso! atributo
!
def set_peso(self, valor):! protegido
if valor > 0:!
self.__peso = valor!
else:!
raise ValueError('valor deve ser > 0')
➊ porém, a API foi alterada!
>>> ervilha.peso
Traceback (most recent call last):
...
AttributeError: 'ItemPedido' object has no attribute 'peso'

• Antes podíamos acessar o peso de um item


escrevendo apenas item.peso, mas agora não...

• Isso quebra código das classes clientes

• Python oferece outro caminho...

➋ o segundo doctest
O peso de um ``ItemPedido`` deve ser maior que zero::
!
>>> ervilha.peso = 0 parece uma
Traceback (most recent call last):
... violação de
ValueError: valor deve ser > 0 encapsulamento
>>> ervilha.peso
10
mas a lógica do negócio é
preservada

peso não foi alterado


➋ validação com property
O peso de um ``ItemPedido`` deve ser maior que zero::
!
>>> ervilha.peso = 0
Traceback (most recent call last):
...
ValueError: valor deve ser > 0
>>> ervilha.peso
10

peso agora é uma property


➋ implementar property
class ItemPedido(object):!
!
def __init__(self, descricao, peso, preco):!
self.descricao = descricao!
self.peso = peso!
self.preco = preco!
!
def subtotal(self):!
return self.peso * self.preco!
!
@property!
def peso(self):! atributo
return self.__peso!
! protegido
@peso.setter!
def peso(self, valor):!
if valor > 0:!
self.__peso = valor!
else:!
raise ValueError('valor deve ser > 0')
➋ implementar property
class ItemPedido(object):!
!
def __init__(self, descricao, peso, preco):!
self.descricao = descricao!
self.peso = peso!
self.preco = preco!
!
def subtotal(self):!
return self.peso * self.preco!
no __init__ a
! property já
@property!
def peso(self):! está em uso
return self.__peso!
!
@peso.setter!
def peso(self, valor):!
if valor > 0:!
self.__peso = valor!
else:!
raise ValueError('valor deve ser > 0')
➋ implementar property
class ItemPedido(object):!
!
def __init__(self, descricao, peso, preco):!
self.descricao = descricao!
self.peso = peso!
self.preco = preco!
!
def subtotal(self):!
return self.peso * self.preco! o atributo
!
@property! protegido
def peso(self):! __peso só é
return self.__peso!
! acessado nos
@peso.setter! métodos da
def peso(self, valor):!
if valor > 0:! property
self.__peso = valor!
else:!
raise ValueError('valor deve ser > 0')
➋ implementar property
class ItemPedido(object):!
!
def __init__(self, descricao, peso, preco):!
self.descricao = descricao!
self.peso = peso!
self.preco = preco! e se quisermos
!
def subtotal(self):!
a mesma lógica
return self.peso * self.preco! para o preco?
!
@property!
def peso(self):!
return self.__peso!
! teremos que
@peso.setter! duplicar tudo
def peso(self, valor):!
if valor > 0:!
isso?
self.__peso = valor!
else:!
raise ValueError('valor deve ser > 0')

➌ os atributos gerenciados
(managed attributes)
ItemPedido usaremos descritores
descricao para gerenciar o acesso
peso {descriptor} aos atributos peso e
preco {descriptor} preco, preservando a
__init__ lógica de negócio
subtotal
➌ validação com descriptor

peso e preco
são atributos
da classe a lógica fica em 

ItemPedido __get__ e __set__,
podendo ser reutilizada
class Quantidade(object):!


__contador = 0!
!
def __init__(self):!
prefixo = self.__class__.__name__!
chave = self.__class__.__contador!
self.nome_alvo = '%s_%s' % (prefixo, chave)!
self.__class__.__contador += 1!
!
def __get__(self, instance, owner):!
return getattr(instance, self.nome_alvo)!
!
implementação def __set__(self, instance, value):!
do descritor if value > 0:!
setattr(instance, self.nome_alvo, value)!
else:!
raise ValueError('valor deve ser > 0')!
!
!
classe class ItemPedido(object):!
new-style peso = Quantidade()!
preco = Quantidade()!
!
def __init__(self, descricao, peso, preco):!
self.descricao = descricao!
self.peso = peso!
self.preco = preco!
!
def subtotal(self):!
return self.peso * self.preco
➊ a classe produz instâncias

classe
instâncias
➌ a classe descriptor
instâncias

classe
➌ uso do descriptor
a classe
ItemPedido
tem duas
instâncias de
Quantidade
associadas a ela
class Quantidade(object):!


__contador = 0!
!
def __init__(self):!
prefixo = self.__class__.__name__!
chave = self.__class__.__contador!
self.nome_alvo = '%s_%s' % (prefixo, chave)!
self.__class__.__contador += 1!
!
def __get__(self, instance, owner):!
return getattr(instance, self.nome_alvo)!
!
implementação def __set__(self, instance, value):!
do descriptor if value > 0:!
setattr(instance, self.nome_alvo, value)!
else:!
raise ValueError('valor deve ser > 0')!
!
!
class ItemPedido(object):!
peso = Quantidade()!
preco = Quantidade()!
!
def __init__(self, descricao, peso, preco):!
self.descricao = descricao!
self.peso = peso!
self.preco = preco!
!
def subtotal(self):!
return self.peso * self.preco
➌ uso do descriptor
a classe
class ItemPedido(object):!
peso = Quantidade()!
ItemPedido
preco = Quantidade()! tem duas
!
def __init__(self, descricao, peso, preco):!
instâncias de
self.descricao = descricao! Quantidade
self.peso = peso!
self.preco = preco!
associadas a ela
!
def subtotal(self):!
return self.peso * self.preco
➌ uso do descriptor

class ItemPedido(object):!
peso = Quantidade()! cada instância
preco = Quantidade()!
! da classe
def __init__(self, descricao, peso, preco):! Quantidade
self.descricao = descricao!
self.peso = peso! controla um
!
self.preco = preco! atributo de
def subtotal(self):! ItemPedido
return self.peso * self.preco
➌ uso do descriptor
todos os acessos
a peso e preco
class ItemPedido(object):! passam pelos
peso = Quantidade()!
preco = Quantidade()! descritores
!
def __init__(self, descricao, peso, preco):!
self.descricao = descricao!
self.peso = peso!
self.preco = preco!
!
def subtotal(self):!
return self.peso * self.preco
➌ implementar o descriptor
class Quantidade(object):!
__contador = 0!
!
def __init__(self):!
prefixo = self.__class__.__name__!
chave = self.__class__.__contador!
self.nome_alvo = '%s_%s' % (prefixo, chave)!
self.__class__.__contador += 1!
!
def __get__(self, instance, owner):!
return getattr(instance, self.nome_alvo)! uma classe
!
def __set__(self, instance, value):! com método
if value > 0:!
setattr(instance, self.nome_alvo, value)!
__get__ é um
else:! descriptor
raise ValueError('valor deve ser > 0')
➌ implementar o descriptor
class Quantidade(object):!
__contador = 0!
!
def __init__(self):!
prefixo = self.__class__.__name__!
chave = self.__class__.__contador!
self.nome_alvo = '%s_%s' % (prefixo, chave)!
self.__class__.__contador += 1!
!
def __get__(self, instance, owner):!
return getattr(instance, self.nome_alvo)!
!
def __set__(self, instance, value):!
if value > 0:!
setattr(instance, self.nome_alvo, value)!
else:!
raise ValueError('valor deve ser > 0')

self é a instância 

do descritor (associada

ao preco ou ao peso)
➌ implementar o descriptor
class Quantidade(object):!
__contador = 0!
!
def __init__(self):!
prefixo = self.__class__.__name__!
chave = self.__class__.__contador!
self.nome_alvo = '%s_%s' % (prefixo, chave)!
self.__class__.__contador += 1!
!
def __get__(self, instance, owner):!
return getattr(instance, self.nome_alvo)!
!
def __set__(self, instance, value):!
if value > 0:!
instance é a instância
setattr(instance, self.nome_alvo, value)!
else:!
de ItemPedido que está
raise ValueError('valor deve ser > 0')

self é a instância 
 sendo acessada


do descritor (associada

ao preco ou ao peso)
➌ implementar o descriptor
class Quantidade(object):!
__contador = 0! nome_alvo é
!
def __init__(self):!
o nome do
prefixo = self.__class__.__name__! atributo da
chave = self.__class__.__contador!
self.nome_alvo = '%s_%s' % (prefixo, chave)! instância de
!
self.__class__.__contador += 1!
ItemPedido
def __get__(self, instance, owner):! que este
return getattr(instance, self.nome_alvo)!
! descritor
def __set__(self, instance, value):!
if value > 0:!
(self)
setattr(instance, self.nome_alvo, value)!
else:!
controla
raise ValueError('valor deve ser > 0')
➌ implementar o descriptor

__get__ e __set__
manipulam o atributo-alvo
no objeto ItemPedido
➌ implementar o descriptor
class Quantidade(object):!
__contador = 0!
!
def __init__(self):!
prefixo = self.__class__.__name__!
chave = self.__class__.__contador!
self.nome_alvo = '%s_%s' % (prefixo, chave)!
self.__class__.__contador += 1!
!
def __get__(self, instance, owner):!
return getattr(instance, self.nome_alvo)!
!
def __set__(self, instance, value):!
if value > 0:!
setattr(instance, self.nome_alvo, value)!
else:!
raise ValueError('valor deve ser > 0')

__get__ e __set__ usam 



getattr e setattr para manipular o
atributo-alvo na instância de ItemPedido
➌ descriptor implementation

cada instância de descritor


gerencia um atributo
específico das instâncias de
ItemPedido e precisa de um
nome_alvo específico
➌ inicialização do descritor
quando um descritor é
instanciado, o atributo ao
class ItemPedido(object):! qual ele será vinculado
peso = Quantidade()!
preco = Quantidade()! ainda não existe!
!
def __init__(self, descricao, peso, preco):!
self.descricao = descricao!
self.peso = peso!
self.preco = preco!
!
def subtotal(self):! exemplo: o
return self.peso * self.preco
atributo preco
só passa a
existir após a
atribuição
➌ implementar o descriptor
class Quantidade(object):!
__contador = 0!
!
def __init__(self):!
prefixo = self.__class__.__name__!
chave = self.__class__.__contador!
self.nome_alvo = '%s_%s' % (prefixo, chave)!
self.__class__.__contador += 1!
!
def __get__(self, instance, owner):!
return getattr(instance, self.nome_alvo)!
!
def __set__(self, instance, value):!
if value > 0:!
temos que gerar um nome
setattr(instance, self.nome_alvo, value)!
else:!
para o atributo-alvo onde
raise ValueError('valor deve ser > 0')
será armazenado o valor
na instância de
ItemPedido
➌ implementar o descriptor
class Quantidade(object):!
__contador = 0!
!
def __init__(self):!
cada instância
prefixo = self.__class__.__name__! de Quantidade
chave = self.__class__.__contador!
self.nome_alvo = '%s_%s' % (prefixo, chave)! precisa criar e
!
self.__class__.__contador += 1!
usar um
def __get__(self, instance, owner):! nome_alvo
return getattr(instance, self.nome_alvo)!
! diferente
def __set__(self, instance, value):!
if value > 0:!
setattr(instance, self.nome_alvo, value)!
else:!
raise ValueError('valor deve ser > 0')
➌ implementar o descriptor
>>> ervilha = ItemPedido('ervilha partida', .5, 3.95)
>>> ervilha.descricao, ervilha.peso, ervilha.preco
('ervilha partida', .5, 3.95)
>>> dir(ervilha)
['Quantidade_0', 'Quantidade_1', '__class__',
'__delattr__', '__dict__', '__doc__', '__format__',
'__getattribute__', '__hash__', '__init__',
'__module__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__',
'__sizeof__', '__str__', '__subclasshook__',
'__weakref__', 'descricao', 'peso', 'preco',
'subtotal']

Quantidade_0 e Quantidade_1 são


os atributos-alvo
➌ os atributos alvo
ItemPedido «descriptor»
descricao «peso» Quantidade
Quantidade_0 nome_alvo
Quantidade_1 «preco» __init__
__init__ __get__
subtotal __set__
«get/set atributo alvo»

Quantidade_0 armazena o valor de peso

Quantidade_1 armazena o valor de preco


➌ os atributos gerenciados

clientes da classe
ItemPedido ItemPedido não precisam
descricao
saber como peso e preco
peso {descriptor}
preco {descriptor}
são gerenciados
__init__
subtotal
E nem precisam saber que
Quantidade_0 e
Quantidade_1 existem!
➌ próximos passos
• Seria melhor se os atributos-alvo fossem
atributos protegidos

• _ItemPedido__peso em vez de
_Quantitade_0

• Para fazer isso, precisamos descobrir o nome do


atributo gerenciado (ex. peso)

• isso não é tão simples quanto parece

• pode ser que não valha a pena complicar mais
➌ o desafio quando cada
descritor é
instanciado, a
classe ItemPedido
não existe, e nem
class ItemPedido(object):! os atributos
peso = Quantidade()!
preco = Quantidade()! gerenciados
!
def __init__(self, descricao, peso, preco):!
self.descricao = descricao!
self.peso = peso!
self.preco = preco!
!
def subtotal(self):!
return self.peso * self.preco
➌ o desafio
por exemplo, o
atributo peso só é
criado depois que
Quantidade() é
class ItemPedido(object):! instanciada
peso = Quantidade()!
preco = Quantidade()!
!
def __init__(self, descricao, peso, preco):!
self.descricao = descricao!
self.peso = peso!
self.preco = preco!
!
def subtotal(self):!
return self.peso * self.preco
Próximos passos
• Se o descritor precisar saber o nome do atributo
gerenciado

(talvez para salvar o valor em um banco de dados, usando
nomes de colunas descritivos, como faz o Django)

• ...então você vai precisar controlar



a construção da classe gerenciada

com uma...

Acelerando...

➎ metaclasses criam classes!
metaclasses são
classes cujas
instâncias são
classes
➏ simplicidade aparente
➏ o poder da abstração

from modelo import Modelo, Quantidade!


!
class ItemPedido(Modelo):!
!
peso = Quantidade()!
preco = Quantidade()!
!
def __init__(self, descricao, peso, preco):!
self.descricao = descricao!
self.peso = peso!
self.preco = preco
➏ módulo modelo.py
class Quantidade(object):!
!
def __init__(self):!
self.set_nome(self.__class__.__name__, id(self))!
!
def set_nome(self, prefix, key):!
self.nome_alvo = '%s_%s' % (prefix, key)!
!
def __get__(self, instance, owner):!
return getattr(instance, self.nome_alvo)!
!
def __set__(self, instance, value):!
if value > 0:!
setattr(instance, self.nome_alvo, value)!
else:!
raise ValueError('valor deve ser > 0')!
!
class ModeloMeta(type):!
!
def __init__(cls, nome, bases, dic):!
super(ModeloMeta, cls).__init__(nome, bases, dic)!
for chave, atr in dic.items():!
if hasattr(atr, 'set_nome'):!
atr.set_nome('__' + nome, chave)!
!
class Modelo(object):!
__metaclass__ = ModeloMeta
➏ esquema final +
➏ o poder da abstração
Referências
• Raymond Hettinger’s 

Descriptor HowTo Guide

• Alex Martelli’s

Python in a Nutshell, 2e.

• David Beazley’s

Python Essential Reference, 

4th edition

(covers Python 2.6)


Você também pode gostar