View
18
Download
0
Category
Preview:
Citation preview
Produtividade e qualidade em Python através da
metaprogramaçãoou
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
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
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 método inicializador é conhecido como “dunder init”
>>> ervilha = ItemPedido('ervilha partida', .5, 7.95) >>> ervilha.descricao, ervilha.peso, ervilha.preco ('ervilha partida', .5, 7.95) >>> ervilha.peso = -10 >>> ervilha.subtotal() -79.5
➊ porém, simples demais
isso vai dar problema na hora de cobrar...
Jeff Bezos of Amazon: Birth of a SalesmanWSJ.com - http://j.mp/VZ5not
“Descobrimos que os clientes conseguiam encomendar uma quantidade negativa de livros! E nós creditávamos o valor em seus cartões...” Jeff Bezos
➊ a solução clássicaclass ItemPedido(object):!! def __init__(self, descricao, peso, preco):! self.descricao = descricao! self.set_peso(peso)! self.preco = preco!! def subtotal(self):! return self.get_peso() * self.preco!! def get_peso(self):! return self.__peso!! def set_peso(self, valor):! if valor > 0:! self.__peso = valor! else:! raise ValueError('valor deve ser > 0')
atributo protegido
mudanças na interface
• 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...
➊ porém, a API foi alterada!>>> ervilha.peso Traceback (most recent call last): ... AttributeError: 'ItemPedido' object has no attribute 'peso'
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
➋ o segundo doctest
parece uma violação de encapsulamento
mas a lógica do negócio é preservada
peso não foi alterado
➋ validação com property
peso agora é uma 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
➋ implementar propertyclass 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):! return self.__peso!! @peso.setter! def peso(self, valor):! if valor > 0:! self.__peso = valor! else:! raise ValueError('valor deve ser > 0')
atributo protegido
➋ implementar propertyclass 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):! return self.__peso!! @peso.setter! def peso(self, valor):! if valor > 0:! self.__peso = valor! else:! raise ValueError('valor deve ser > 0')
no __init__ a property já está em uso
➋ implementar propertyclass 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):! return self.__peso!! @peso.setter! def peso(self, valor):! if valor > 0:! self.__peso = valor! else:! raise ValueError('valor deve ser > 0')
o atributo protegido __peso só é acessado nos métodos da property
➋ implementar propertyclass 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):! return self.__peso!! @peso.setter! def peso(self, valor):! if valor > 0:! self.__peso = valor! else:! raise ValueError('valor deve ser > 0')
e se quisermos a mesma lógica para o preco?
teremos que duplicar tudo isso?
➌ os atributos gerenciados (managed attributes)
usaremos descritores para gerenciar o acesso aos atributos peso e preco, preservando a lógica de negócio__init__
subtotal
descricaopeso {descriptor}preco {descriptor}
ItemPedido
➌ validação com descriptor
peso e preco são atributos da classe ItemPedido
a lógica fica em __get__ e __set__, podendo ser reutilizada
➌
implementação do descritor
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')!!!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
classe new-style
➌
implementação do 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')!!!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
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 ItemPedido tem duas instâncias de Quantidade associadas a ela
➌ uso do descriptor
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
cada instância da classe Quantidade controla um atributo de ItemPedido
➌ uso do descriptor
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
todos os acessos a peso e preco passam pelos descritores
➌ implementar o descriptorclass 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')
uma classe com método __get__ é um 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')
➌ implementar o descriptor
self é a instância do descritor (associadaao preco ou ao peso)
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')
➌ implementar o descriptor
self é a instância do descritor (associadaao preco ou ao peso)
instance é a instância de ItemPedido que está sendo acessada
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')
➌ implementar o descriptornome_alvo é o nome do atributo da instância de ItemPedido que este descritor (self) controla
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')
➌ implementar o descriptor
__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
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
quando um descritor é instanciado, o atributo ao qual ele será vinculado ainda não existe!
exemplo: o atributo preco só passa a existir após a atribuição
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')
➌ implementar o descriptor
temos que gerar um nome para o atributo-alvo onde será armazenado o valor na instância de ItemPedido
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')
cada instância de Quantidade precisa criar e usar um nome_alvo diferente
➌ implementar o descriptor
➌ 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
Quantidade_0 armazena o valor de peso
__init____get____set__
nome_alvo
«descriptor»Quantidade
__init__subtotal
descricaoQuantidade_0Quantidade_1
ItemPedido«peso»
«preco»
«get/set atributo alvo»
Quantidade_1 armazena o valor de preco
➌ os atributos gerenciados
clientes da classe ItemPedido não precisam saber como peso e preco são gerenciados
E nem precisam saber que Quantidade_0 e Quantidade_1 existem!
__init__subtotal
descricaopeso {descriptor}preco {descriptor}
ItemPedido
➌ 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
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
quando cada descritor é instanciado, a classe ItemPedido não existe, e nem os atributos gerenciados
➌ o desafio
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
por exemplo, o atributo peso só é criado depois que Quantidade() é instanciada
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 controlara construção da classe gerenciadacom uma...
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
➏ o poder da abstração
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
➏ módulo modelo.py
Referências
• Raymond Hettinger’s Descriptor HowTo Guide
• Alex Martelli’sPython in a Nutshell, 2e.
• David Beazley’sPython Essential Reference, 4th edition (covers Python 2.6)
Recommended