Produtividade e qualidade em Python através da metaprogramação

May 16, 2016 | Author: Thais Bennert Benevides | Category: N/A
Share Embed Donate


Short Description

1 Produtividade e qualidade em Python através da metaprogramação ou a visão radical na pr&aa...

Description

Produtividade e qualidade em Python através da metaprogramação ou

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

[email protected]

@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

class ItemPedido(object):!

!

o método inicializador é conhecido como “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 isso vai dar >>> ervilha.subtotal() problema na -79.5

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)! mudanças self.preco = preco! ! na def subtotal(self):! interface return self.get_peso() * self.preco! ! def get_peso(self):! return self.__peso! atributo ! protegido def set_peso(self, valor):! 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):! return self.__peso!

!

atributo 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!

! @property! def peso(self):! return self.__peso!

no __init__ a property já está em uso

! @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!

! @property! def peso(self):! return self.__peso!

!

o atributo protegido __peso só é acessado nos métodos da property

@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! e self.preco = preco!

! def subtotal(self):! return self.peso * self.preco!

! @property! def peso(self):! return self.__peso!

!

se quisermos a mesma lógica para o preco?

teremos que duplicar tudo isso?

@peso.setter! def peso(self, valor):! if valor > 0:! self.__peso = valor! else:! raise ValueError('valor deve ser > 0')



➌ os atributos gerenciados (managed attributes) ItemPedido descricao peso {descriptor} preco {descriptor} __init__ subtotal

usaremos descritores para gerenciar o acesso aos atributos peso e preco, preservando a lógica de negócio

➌ validação com descriptor

peso e preco são atributos da classe ItemPedido

a lógica fica em 
 __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 do descritor classe new-style

! 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

➊ 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 do descriptor

! 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

➌ uso do descriptor class ItemPedido(object):! peso = Quantidade()! preco = Quantidade()!

! def __init__(self, descricao, peso, self.descricao = descricao! self.peso = peso! self.preco = preco!

! def subtotal(self):! return self.peso * self.preco

a classe ItemPedido tem duas instâncias de preco):! 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()!

todos os acessos a peso e preco passam pelos 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)!

! 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

➌ 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:! setattr(instance, self.nome_alvo, value)! else:! raise ValueError('valor deve ser > 0')

instance é a instância de ItemPedido que está self é a instância 
 sendo acessada 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:! setattr(instance, self.nome_alvo, value)! else:! raise ValueError('valor deve ser > 0')

nome_alvo é o nome do atributo da instância de ItemPedido que este descritor (self) controla

➌ 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 class ItemPedido(object):! peso = Quantidade()! preco = Quantidade()!

quando um descritor é instanciado, o atributo ao qual ele será vinculado ainda não existe!

! def __init__(self, descricao, peso, preco):! self.descricao = descricao! self.peso = peso! self.preco = preco!

! def subtotal(self):! return self.peso * self.preco

exemplo: o 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:! setattr(instance, self.nome_alvo, value)! else:! raise ValueError('valor deve ser > 0')

temos que gerar um nome para o atributo-alvo onde será armazenado o valor na instância de 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')

cada instância de Quantidade precisa criar e usar um nome_alvo diferente

➌ 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 descricao Quantidade_0 Quantidade_1 __init__ subtotal

«descriptor» «peso» «preco»

Quantidade nome_alvo __init__ __get__ __set__

«get/set atributo alvo»

Quantidade_0 armazena o valor de peso Quantidade_1 armazena o valor de preco

➌ os atributos gerenciados ItemPedido descricao peso {descriptor} preco {descriptor} __init__ subtotal

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!

➌ próximos passos • Seria melhor se os atributos-alvo fossem atributos protegidos



_ItemPedido__peso _Quantitade_0

em vez de

• 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 os atributos gerenciados

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

➌ o desafio por exemplo, o atributo peso só é criado depois que Quantidade() é instanciada

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

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)


View more...

Comments

Copyright � 2017 SILO Inc.