Descritores de atributos em Pythonnovembro/2012Luciano Ramalholuciano@ramalho.org
Pré-requistos da palestra• Para acompanhar os slides a seguir, é preciso saber  como funciona o básico de orientação a obj...
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...
Turing.com.br                ➊
➊ o primeiro doctest=======Passo 1=======Um pedido de alimentos a granel é uma coleção de ``ItemPedido``.Cada item possui ...
➊ mais simples, impossível                                              o método                                          ...
➊ porém, simples demais>>> ervilha = ItemPedido(ervilha partida, .5, 7.95)>>> ervilha.descricao, ervilha.peso, ervilha.pre...
➊ a solução clássica class ItemPedido(object):     def __init__(self, descricao, peso, preco):         self.descricao = de...
➊ porém, a API foi alterada!>>> ervilha.pesoTraceback (most recent call last):        ...AttributeError: ItemPedido object...
➊ atributos protegidos• Atributos protegidos em  Python são salvaguardas • servem para evitar    atribuição ou sobrescrita...
➊ atributos protegidos• Atributos protegidos em   Python são salvaguardas  • servem para evitar    atribuição ou sobrescri...
Turing.com.br                ➋
➋ o segundo doctestO peso de um ``ItemPedido`` deve ser maior que zero::!   >>> ervilha.peso = 0                    parece...
➋ validação com propertyO peso de um ``ItemPedido`` deve ser maior que zero::!   >>> ervilha.peso = 0!   Traceback (most r...
➋ implementar propertyclass ItemPedido(object):    def __init__(self, descricao, peso, preco):        self.descricao = des...
➋ implementar propertyclass ItemPedido(object):    def __init__(self, descricao, peso, preco):        self.descricao = des...
➋ implementar propertyclass ItemPedido(object):    def __init__(self, descricao, peso, preco):        self.descricao = des...
➋ implementar propertyclass ItemPedido(object):    def __init__(self, descricao, peso, preco):        self.descricao = des...
Turing.com.br                ➌
➌ os atributos gerenciados  (managed attributes)     ItemPedido        usaremos descritores  descricao            para ger...
➌ validação com descriptor  peso e preco  são atributos  da classe       a lógica fica em  ItemPedido      __get__ e __set_...
class Quantidade(object):➌                      __contador = 0                      def __init__(self):                   ...
➊ a classe produz instâncias classe                 instâncias Turing.com.br
➌ a classe descriptor instâncias                        classe Turing.com.br
➌ uso do descriptor                  a classe                  ItemPedido                  tem duas                  instâ...
class Quantidade(object):➌                      __contador = 0                      def __init__(self):                   ...
➌ uso do descriptor                                                  a classeclass ItemPedido(object):    peso = Quantidad...
➌ uso do descriptorclass ItemPedido(object):    peso = Quantidade()                           cada instância    preco = Qu...
➌ uso do descriptor                                            todos os acessos                                           ...
➌ implementar o descriptorclass Quantidade(object):    __contador = 0    def __init__(self):        prefixo = self.__class...
➌ implementar o descriptorclass Quantidade(object):    __contador = 0    def __init__(self):        prefixo = self.__class...
➌ implementar o descriptorclass Quantidade(object):    __contador = 0    def __init__(self):        prefixo = self.__class...
➌ implementar o descriptorclass Quantidade(object):    __contador = 0                                     nome_alvo é    d...
➌ implementar o descriptor                 __get__ e __set__                 manipulam o atributo-alvo Turing.com.br   no ...
➌ implementar o descriptorclass Quantidade(object):    __contador = 0    def __init__(self):        prefixo = self.__class...
➌ descriptor implementation             cada instância de descritor             gerencia um atributo             específico...
➌ inicialização do descritor                                        quando um descritor é                                 ...
➌ implementar o descriptorclass Quantidade(object):    __contador = 0    def __init__(self):        prefixo = self.__class...
➌ implementar o descriptorclass Quantidade(object):    __contador = 0    def __init__(self):                              ...
➌ implementar o descriptor>>> ervilha = ItemPedido(ervilha partida, .5, 3.95)>>> ervilha.descricao, ervilha.peso, ervilha....
➌ os atributos alvo         ItemPedido                                «descriptor»      descricao                 «peso»  ...
➌ os atributos gerenciados                       clientes da classe     ItemPedido        ItemPedido não precisam  descric...
➌ próximos passos• Seria melhor se os atributos-alvo fossem  atributos protegidos •   _ItemPedido__peso   em vez de     _Q...
➌ o desafio                              quando cada                                        descritor é                    ...
➌ o desafio                                        por exemplo, o                                        atributo peso só é...
Próximos passos•   Se o descritor precisar saber o nome do atributo    gerenciado    (talvez para salvar o valor em um ban...
Acelerando... Turing.com.br
Turing.com.br                ➏
➎ metaclasses criam classes!                  metaclasses são                   classes cujas                  instâncias ...
➏ simplicidade aparente Turing.com.br
➏ o poder da abstração                 from modelo import Modelo, Quantidade                 class ItemPedido(Modelo):    ...
➏ módulo modelo.py                 class Quantidade(object):                     def __init__(self):                      ...
➏ esquema final   + Turing.com.br
Referências•   Raymond Hettinger’s    Descriptor HowTo Guide    (part of the official Python docs.)•   Alex Martelli’s    P...
Referências•   David Beazley’s    Python Essential Reference,    4th edition    (covers Python 2.6)•   Source code and doc...
A revelação final• Funções em Python são descritores! • implementam __get__• É assim que uma função, quando acessada como  ...
Oficinas Turing:computação para programadores• Próximos lançamentos: • 1ª turma de Python para quem usa Django • 3ª turma d...
Próximos SlideShares
Carregando em…5
×

Encapsulamento com descritores

786 visualizações

Publicada em

Publicada em: Tecnologia
0 comentários
3 gostaram
Estatísticas
Notas
  • Seja o primeiro a comentar

Sem downloads
Visualizações
Visualizações totais
786
No SlideShare
0
A partir de incorporações
0
Número de incorporações
6
Ações
Compartilhamentos
0
Downloads
44
Comentários
0
Gostaram
3
Incorporações 0
Nenhuma incorporação

Nenhuma nota no slide

Encapsulamento com descritores

  1. 1. Descritores de atributos em Pythonnovembro/2012Luciano Ramalholuciano@ramalho.org
  2. 2. Pré-requistos da palestra• Para acompanhar os slides a seguir, é preciso saber como funciona o básico de orientação a objetos em Python. Especificamente: • contraste entre atributos de classe e de instância • herança de atributos de classe (métodos e campos) • atributos protegidos: como e quando usar • como e quando usar a função super Turing.com.br
  3. 3. 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 Turing.com.br
  4. 4. Turing.com.br ➊
  5. 5. ➊ 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
  6. 6. ➊ 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): Turing.com.br self.peso * self.preco return
  7. 7. ➊ 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 Turing.com.br WSJ.com - http://j.mp/VZ5not
  8. 8. ➊ 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)
  9. 9. ➊ porém, a API foi alterada!>>> ervilha.pesoTraceback (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...
  10. 10. ➊ atributos protegidos• Atributos protegidos em Python são salvaguardas • servem para evitar atribuição ou sobrescrita acidental • não para evitar usos (ou abusos) intencionais
  11. 11. ➊ atributos protegidos• Atributos protegidos em Python são salvaguardas • servem para evitar atribuição ou sobrescrita acidental • não para evitar usos (ou abusos) intencionais>>> ervilha._ItemPedido__peso10
  12. 12. Turing.com.br ➋
  13. 13. ➋ o segundo doctestO 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
  14. 14. ➋ validação com propertyO 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 Turing.com.br
  15. 15. ➋ 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): atributo return self.__peso protegido @peso.setter def peso(self, valor): if valor > 0: self.__peso = valor else: raise ValueError(valor deve ser > 0) Turing.com.br
  16. 16. ➋ 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 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) Turing.com.br
  17. 17. ➋ 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 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) Turing.com.br
  18. 18. ➋ implementar propertyclass ItemPedido(object): def __init__(self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco ese 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) Turing.com.br
  19. 19. Turing.com.br ➌
  20. 20. ➌ 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
  21. 21. ➌ validação com descriptor peso e preco são atributos da classe a lógica fica em ItemPedido __get__ e __set__, podendo ser reutilizada Turing.com.br
  22. 22. 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
  23. 23. ➊ a classe produz instâncias classe instâncias Turing.com.br
  24. 24. ➌ a classe descriptor instâncias classe Turing.com.br
  25. 25. ➌ uso do descriptor a classe ItemPedido tem duas instâncias de Quantidade associadas a ela Turing.com.br
  26. 26. 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 Turing.com.br def subtotal(self): return self.peso * self.preco
  27. 27. ➌ uso do descriptor a classeclass 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 Turing.com.br
  28. 28. ➌ uso do descriptorclass 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 Turing.com.br
  29. 29. ➌ uso do descriptor todos os acessos a peso e precoclass 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
  30. 30. ➌ 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) 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) Turing.com.br
  31. 31. ➌ 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) self é a instância do descritor (associada Turing.com.br ao preco ou ao peso)
  32. 32. ➌ 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: 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 Turing.com.br ao preco ou ao peso)
  33. 33. ➌ implementar o descriptorclass 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) controla else: raise ValueError(valor deve ser > 0) Turing.com.br
  34. 34. ➌ implementar o descriptor __get__ e __set__ manipulam o atributo-alvo Turing.com.br no objeto ItemPedido
  35. 35. ➌ 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) __get__ e __set__ usam getattr e setattr para manipular o Turing.com.br atributo-alvo na instância de ItemPedido
  36. 36. ➌ 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
  37. 37. ➌ inicialização do descritor quando um descritor é instanciado, o atributo aoclass 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 Turing.com.br
  38. 38. ➌ 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: 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 Turing.com.br
  39. 39. ➌ implementar o descriptorclass 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)
  40. 40. ➌ 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 Turing.com.br
  41. 41. ➌ 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 pesoQuantidade_1 armazena o valor de preco
  42. 42. ➌ 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!
  43. 43. ➌ 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
  44. 44. ➌ o desafio quando cada descritor é instanciado, a classe ItemPedido não existe, e nemclass 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
  45. 45. ➌ 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
  46. 46. 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...
  47. 47. Acelerando... Turing.com.br
  48. 48. Turing.com.br ➏
  49. 49. ➎ metaclasses criam classes! metaclasses são classes cujas instâncias são classes Turing.com.br
  50. 50. ➏ simplicidade aparente Turing.com.br
  51. 51. ➏ 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 Turing.com.br
  52. 52. ➏ 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): Turing.com.br __metaclass__ = ModeloMeta
  53. 53. ➏ esquema final + Turing.com.br
  54. 54. Referências• Raymond Hettinger’s Descriptor HowTo Guide (part of the official Python docs.)• Alex Martelli’s Python in a Nutshell, 2e. (Python 2.5 but still excellent, includes the complete attribute access algorithm)
  55. 55. Referências• David Beazley’s Python Essential Reference, 4th edition (covers Python 2.6)• Source code and doctests for this talk: http://github.com/ramalho/talks/tree/master/encap (will move to: https://github.com/oturing/encapy)
  56. 56. A revelação final• Funções em Python são descritores! • implementam __get__• É assim que uma função, quando acessada como atributo de uma instância se torna um bound method (método vinculado) • fn.__get__ devolve uma aplicação parcial de fn, vinculando self à instância-alvo Elegante, hein?
  57. 57. Oficinas Turing:computação para programadores• Próximos lançamentos: • 1ª turma de Python para quem usa Django • 3ª turma de Objetos Pythonicos • 4ª turma de Python para quem sabe Python Para saber mais sobre estes cursos ONLINE escreva para: ramalho@turing.com.br Turing.com.br

×