Descritores de atributos em Python
novembro/2012




Luciano Ramalho
luciano@ramalho.org
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
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
Turing.com.br
                ➊
➊ 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):
 Turing.com.br self.peso * self.preco
         return
➊ 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
➊ 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...
➊ 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
➊ 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__peso
10
Turing.com.br
                ➋
➋ 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



     Turing.com.br
➋ 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')
 Turing.com.br
➋ 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')
 Turing.com.br
➋ 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')
 Turing.com.br
➋ implementar property
class 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
Turing.com.br
                ➌
➌ 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
 Turing.com.br
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


 Turing.com.br
➌ a classe descriptor
 instâncias




                        classe



 Turing.com.br
➌ uso do descriptor
                  a classe
                  ItemPedido
                  tem duas
                  instâncias de
                  Quantidade
                  associadas a ela




 Turing.com.br
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
➌ 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




  Turing.com.br
➌ 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




  Turing.com.br
➌ 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')




   Turing.com.br
➌ 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
   Turing.com.br 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
   Turing.com.br 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)   controla
        else:
            raise ValueError('valor deve ser > 0')




   Turing.com.br
➌ implementar o descriptor




                 __get__ e __set__
                 manipulam o atributo-alvo
 Turing.com.br   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
   Turing.com.br 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
  Turing.com.br
➌ 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
   Turing.com.br
➌ 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
  Turing.com.br
➌ 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...




 Turing.com.br
Turing.com.br
                ➏
➎ metaclasses criam classes!
                  metaclasses são
                   classes cujas
                  instâncias são
                      classes




 Turing.com.br
➏ simplicidade aparente




 Turing.com.br
➏ 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
➏ 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
➏ esquema final   +




 Turing.com.br
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)
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)
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?
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

Encapsulamento com descritores

  • 1.
    Descritores de atributosem Python novembro/2012 Luciano Ramalho luciano@ramalho.org
  • 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.
    O cenário • Comérciode 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.
  • 5.
    ➊ o primeirodoctest ======= 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.
    ➊ 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.
    ➊ porém, simplesdemais >>> 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.
    ➊ a soluçãoclá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.
    ➊ porém, aAPI 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...
  • 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.
    ➊ 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__peso 10
  • 12.
  • 13.
    ➋ o segundodoctest 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
  • 14.
    ➋ validação comproperty 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 Turing.com.br
  • 15.
    ➋ implementar property classItemPedido(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.
    ➋ implementar property classItemPedido(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.
    ➋ implementar property classItemPedido(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.
    ➋ implementar property classItemPedido(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.
  • 20.
    ➌ os atributosgerenciados (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.
    ➌ validação comdescriptor peso e preco são atributos da classe a lógica fica em ItemPedido __get__ e __set__, podendo ser reutilizada Turing.com.br
  • 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.
    ➊ a classeproduz instâncias classe instâncias Turing.com.br
  • 24.
    ➌ a classedescriptor instâncias classe Turing.com.br
  • 25.
    ➌ uso dodescriptor a classe ItemPedido tem duas instâncias de Quantidade associadas a ela Turing.com.br
  • 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.
    ➌ uso dodescriptor 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 Turing.com.br
  • 28.
    ➌ uso dodescriptor 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 Turing.com.br
  • 29.
    ➌ uso dodescriptor 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
  • 30.
    ➌ implementar odescriptor 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') Turing.com.br
  • 31.
    ➌ implementar odescriptor 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 Turing.com.br ao preco ou ao peso)
  • 32.
    ➌ implementar odescriptor 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 Turing.com.br ao preco ou ao peso)
  • 33.
    ➌ implementar odescriptor 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) controla else: raise ValueError('valor deve ser > 0') Turing.com.br
  • 34.
    ➌ implementar odescriptor __get__ e __set__ manipulam o atributo-alvo Turing.com.br no objeto ItemPedido
  • 35.
    ➌ implementar odescriptor 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 Turing.com.br atributo-alvo na instância de ItemPedido
  • 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.
    ➌ inicialização dodescritor 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 Turing.com.br
  • 38.
    ➌ implementar odescriptor 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 Turing.com.br
  • 39.
    ➌ implementar odescriptor 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')
  • 40.
    ➌ implementar odescriptor >>> 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.
    ➌ os atributosalvo 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
  • 42.
    ➌ os atributosgerenciados 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.
    ➌ 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.
    ➌ 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
  • 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.
    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.
  • 48.
  • 49.
    ➎ metaclasses criamclasses! metaclasses são classes cujas instâncias são classes Turing.com.br
  • 50.
  • 51.
    ➏ o poderda 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.
    ➏ 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.
    ➏ esquema final + Turing.com.br
  • 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.
    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.
    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.
    Oficinas Turing: computação paraprogramadores • 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