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
ervilha.preco ('ervilha partida', .5, 7.95) >>> ervilha.peso = -10 >>> ervilha.subtotal() -79.5 Turing.com.br ➊ porém, simples demais isso vai dar problema na hora de cobrar... Jeff Bezos of Amazon: Birth of a Salesman WSJ.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
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'
! >>> 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
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
__init__(self, descricao, peso, preco):! self.descricao = descricao! self.peso = peso! self.preco = preco! ! def subtotal(self):! return self.peso * self.preco Turing.com.br ➌ uso do descriptor a classe ItemPedido tem duas instâncias de Quantidade associadas a ela
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') Turing.com.br ➌ implementar o descriptor self é a instância do descritor (associada ao preco ou ao peso) instance é a instância de ItemPedido que está sendo acessada
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') Turing.com.br ➌ implementar o descriptor nome_alvo é o nome do atributo da instância de ItemPedido que este descritor (self) controla
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
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') Turing.com.br ➌ implementar o descriptor temos que gerar um nome para o atributo-alvo onde será armazenado o valor na instância de ItemPedido
saber como peso e preco são gerenciados E nem precisam saber que Quantidade_0 e Quantidade_1 existem! __init__ subtotal descricao peso {descriptor} preco {descriptor} ItemPedido
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
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
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
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...
• 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