Descritores de atributos em Python
outubro/2012




Luciano Ramalho
luciano@ramalho.org
Ritmo desta palestra




                 Recomendação:
 Turing.com.br
                 manter os olhos abertos
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
Roteiro da palestra
 • A partir de um cenário inicial, implementamos uma
   classe muito simples
 • A partir daí, evoluímos a implementação em seis
   etapas para controlar o acesso aos campos das
   instâncias, usando propriedades, descritores e
   finalmente uma metaclasse
 • Nos slides, as etapas são identificadas por
   números: ➊, ➋, ➌...

 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
➊ 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):
 Turing.com.br self.peso * self.preco
         return
➊ a classe produz instâncias




 classe
                 instâncias


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




  Turing.com.br
➊ 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...
   “We found that customers could
    order a negative quantity of books!
    And we would credit their credit
    card with the price...” Jeff Bezos


                          Jeff Bezos of Amazon: Birth of a Salesman
  Turing.com.br                       WSJ.com - http://j.mp/VZ5not
➊ 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
➋ validação com property
>>> ervilha = ItemPedido('ervilha partida', .5, 7.95)
>>> ervilha.descricao, ervilha.peso, ervilha.preco
('ervilha partida', .5, 7.95)
>>> ervilha.peso = -10                      parece uma
Traceback (most recent call last):
  ...                                       violação de
ValueError: valor deve ser > 0              encapsulamento


mas a lógica do negócio
está preservada:
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):
        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

    @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
➌ 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
➌ validação com descriptor
 instâncias




                      classe



 Turing.com.br
class Quantidade(object):

➌                     def __init__(self):
                          prefixo = self.__class__.__name__
                          chave = id(self)
                          self.nome_alvo = '%s_%s' % (prefixo, chave)

                      def __get__(self, instance, owner):
                          return getattr(instance, self.nome_alvo)

                      def __set__(self, instance, value):
implementação             if value > 0:
do descriptor                 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):
  Turing.com.br           return self.peso * self.preco
➌ uso do descriptor
                  a classe
                  ItemPedido
                  tem duas
                  instâncias de
                  Quantidade
                  associadas a ela




 Turing.com.br
➌ 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
➌ implementar o descriptor
class Quantidade(object):

    def __init__(self):
        prefixo = self.__class__.__name__
        chave = id(self)
        self.nome_alvo = '%s_%s' % (prefixo, chave)

    def __get__(self, instance, owner):
        return getattr(instance, self.nome_alvo)
                                                       uma classe
    def __set__(self, instance, value):
        if value > 0:
                                                       com método
            setattr(instance, self.nome_alvo, value)   __get__ é um
        else:
            raise ValueError('valor deve ser > 0')     descriptor



   Turing.com.br
➌ implementar o descriptor
class Quantidade(object):

    def __init__(self):
        prefixo = self.__class__.__name__
        chave = id(self)
        self.nome_alvo = '%s_%s' % (prefixo, chave)

    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):

    def __init__(self):
        prefixo = self.__class__.__name__
        chave = id(self)
        self.nome_alvo = '%s_%s' % (prefixo, chave)

    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:                       instance é a instância
                                    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):                              nome_alvo é
    def __init__(self):
                                                       o nome do
        prefixo = self.__class__.__name__              atributo da
        chave = id(self)
        self.nome_alvo = '%s_%s' % (prefixo, chave)    instância de
    def __get__(self, instance, owner):
                                                       ItemPedido
        return getattr(instance, self.nome_alvo)       que este
    def __set__(self, instance, value):                descritor
        if value > 0:
            setattr(instance, self.nome_alvo, value)
                                                       (self)
        else:                                          controla
            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):

    def __init__(self):
        prefixo = self.__class__.__name__
        chave = id(self)
        self.nome_alvo = '%s_%s' % (prefixo, chave)

    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
➌ 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):

    def __init__(self):
        prefixo = self.__class__.__name__
        chave = id(self)
        self.nome_alvo = '%s_%s' % (prefixo, chave)

    def __get__(self, instance, owner):
        return getattr(instance, self.nome_alvo)

    def __set__(self, instance, value):
        if value > 0:
                                     temos que inventar um
            setattr(instance, self.nome_alvo, value)
        else:
                                     nome para o atributo-alvo
            raise ValueError('valor deve ser > 0')

                                     onde será armazenado o
                                     valor na instância de
                                     ItemPedido
   Turing.com.br
➌ implementar o descriptor
>>> ervilha = ItemPedido('ervilha partida', .5, 3.95)
>>> ervilha.descricao, ervilha.peso, ervilha.preco
('ervilha partida', .5, 3.95)
>>> dir(ervilha)
['Quantidade_4299545872', 'Quantidade_4299546064',
'__class__', '__delattr__', '__dict__', '__doc__',
'__format__', '__getattribute__', '__hash__',
'__init__', '__module__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__',
'__sizeof__', '__str__', '__subclasshook__',
'__weakref__', 'descricao', 'peso', 'preco',
'subtotal']


                    nesta implementação, os nomes dos
                    atributos-alvo não são descritivos,
  Turing.com.br
                    dificultando a depuração
➍ usar nomes descritivos




                 ItemPedido.__new__ invoca
                 «quantidade».set_nome
 Turing.com.br   para redefinir o nome_alvo
➍ usar nomes descritivos
 ItemPedido.__new__ invoca «quantidade».set_nome




                          «quantidade».set_nome
 Turing.com.br            redefine o nome_alvo
➍ usar nomes descritivos
                                               ItemPedido.__new__
class ItemPedido(object):
    peso = Quantidade()                               invoca
    preco = Quantidade()
                                              «quantidade».set_nome
    def __new__(cls, *args, **kwargs):
        for chave, atr in cls.__dict__.items():
            if hasattr(atr, 'set_nome'):
                atr.set_nome('__' + cls.__name__, chave)
        return super(ItemPedido, cls).__new__(cls, *args, **kwargs)

    def __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
➍ usar nomes descritivos
                                              «quantidade».set_nome
class Quantidade(object):
                                              redefine o nome_alvo
    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')




   Turing.com.br
➍ usar nomes descritivos
class ItemPedido(object):
    peso = Quantidade()
    preco = Quantidade()

    def __new__(cls, *args, **kwargs):
        for chave, atr in cls.__dict__.items():
            if hasattr(atr, 'set_nome'):
                atr.set_nome('__' + cls.__name__, chave)
        return super(ItemPedido, cls).__new__(cls, *args, **kwargs)

    def __init__(self, descricao, peso, preco):
        self.descricao = descricao
        self.peso = peso
        self.preco = preco                           Quando __init__
    def subtotal(self):                              executa, o descritor
        return self.peso * self.preco
                                                     já está configurado
                                                     com um nome de
                                                     atributo-alvo
   Turing.com.br
                                                     descritivo
➍ usar nomes descritivos
ItemPedido.__new__ invoca «quantidade».set_nome




 Turing.com.br
➍ usar nomes descritivos




                 «quantidade».set_nome
 Turing.com.br   redefine o nome_alvo
➍ nomes descritivos
>>> ervilha = ItemPedido('ervilha partida', .5, 3.95)
>>> ervilha.descricao, ervilha.peso, ervilha.preco
('ervilha partida', 0.5, 3.95)
>>> dir(ervilha)
['__ItemPedido_peso', '__ItemPedido_preco',
'__class__', '__delattr__', '__dict__', '__doc__',
'__format__', '__getattribute__', '__hash__',
'__init__', '__module__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__',
'__sizeof__', '__str__', '__subclasshook__',
'__weakref__', 'descricao', 'peso', 'preco',
'subtotal']

                    nesta implementação e nas
                    próximas, os nomes dos atributos-
                    alvo seguem a convenção de
  Turing.com.br     atributos protegidos de Python
➍ funciona, mas custa caro
 • ItemPedido aciona __new__
   para construir cada nova
   instância
 • Porém a associação dos
   descritores é com a classe
   ItemPedido: o nome do
   atributo-alvo nunca vai mudar,
   uma vez definido corretamente


 Turing.com.br
➍ funciona, mas custa caro
 • Isso significa que para cada
   nova instância de
   ItemPedido que é criada,
   «quantidade».set_nome
   é invocado duas vezes
 • Mas o nome do atributo-alvo
   não tem porque mudar na vida
   de uma «quantidade»


 Turing.com.br
➍ funciona, mas custa caro
1500000.0   1427386



                                             7.3 ×
1125000.0
                       980708



 750000.0
                                  585394



 375000.0

                                              80004

       0
            versão 1   versão 2   versão 3   versão 4


        Número de instâncias de ItemPedido criadas
        por segundo (MacBook Pro 2011, Intel Core i7)
    Turing.com.br
➎ como evitar trabalho inútil
 • ItemPedido.__new__ resolveu
   mas de modo ineficiente.
 • Cada «quantidade» deve receber
   o nome do seu atributo-alvo apenas
   uma vez, quando a própria classe
   ItemPedido for criada
 • Para isso precisamos de uma...

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




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


                         ItemPedido é uma
                          instância de type


                 type é a metaclasse default
                   em Python: a classe que
                    normalmente constroi
 Turing.com.br          outras classes
➎ nossa metaclasse
         antes       depois




 Turing.com.br
➎ nossa metaclasse
                  ModeloMeta é a
                  metaclasse que vai
                  construir a classe
                  ItemPedido




                 ModeloMeta.__init__
                 fará apenas uma vez o
                 que antes era feito em
                 ItemPedido.__new__
 Turing.com.br   a cada nova instância
➎ nossa metaclasse
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)

                                                  Assim dizemos que a
class ItemPedido(object):
    __metaclass__ = ModeloMeta                    classe ItemPedido herda
    peso = Quantidade()                           de object, mas é uma
    preco = Quantidade()
                                                  instância (construida por)
    def __init__(self, descricao, peso, preco):
        self.descricao = descricao                ModeloMeta
        self.peso = peso
        self.preco = preco

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



    Turing.com.br
➎ nossa metaclasse
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)
                                                  Este __init__ invoca
class ItemPedido(object):
    __metaclass__ = ModeloMeta                    «quantidade».set_nome,
    peso = Quantidade()
                                                  para cada descritor, uma
    preco = Quantidade()
                                                  vez só, na inicialização da
    def __init__(self, descricao, peso, preco):
        self.descricao = descricao
                                                  classe ItemPedido
        self.peso = peso
        self.preco = preco

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



    Turing.com.br
➎ nossa metaclasse em ação




 Turing.com.br
➎ nossa metaclasse em ação




 Turing.com.br
➎ nossa metaclasse em ação
                     +




 Turing.com.br
➎ desempenho melhor
1500000.0   1427386
                        mesmo desempenho,
                         + nomes amigáveis
1125000.0
                      980708



 750000.0
                               585394            585771



 375000.0

                                        80004

       0
            versão 1 versão 2 versão 3 versão 4 versão 5


        Número de instâncias de ItemPedido criadas
        por segundo (MacBook Pro 2011, Intel Core i7)
    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
➏ esquema final




 Turing.com.br
Oficinas Turing:
    computação para programadores

•   Próximos lançamentos:

    •   4ª turma de Python para quem sabe Python

    •   3ª turma de Objetos Pythonicos

    •   1ª turma de Aprenda Python com um Pythonista



        Turing.com.br

Encapsulamento com Descritores em Python

  • 1.
    Descritores de atributosem Python outubro/2012 Luciano Ramalho luciano@ramalho.org
  • 2.
    Ritmo desta palestra Recomendação: Turing.com.br manter os olhos abertos
  • 3.
    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
  • 4.
    Roteiro da palestra • A partir de um cenário inicial, implementamos uma classe muito simples • A partir daí, evoluímos a implementação em seis etapas para controlar o acesso aos campos das instâncias, usando propriedades, descritores e finalmente uma metaclasse • Nos slides, as etapas são identificadas por números: ➊, ➋, ➌... Turing.com.br
  • 5.
    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
  • 6.
    ➊ 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): Turing.com.br self.peso * self.preco return
  • 7.
    ➊ a classeproduz instâncias classe instâncias Turing.com.br
  • 8.
    ➊ 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
  • 9.
    ➊ 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... Turing.com.br
  • 10.
    ➊ 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... “We found that customers could order a negative quantity of books! And we would credit their credit card with the price...” Jeff Bezos Jeff Bezos of Amazon: Birth of a Salesman Turing.com.br WSJ.com - http://j.mp/VZ5not
  • 11.
    ➊ 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
  • 12.
    ➋ validação comproperty >>> ervilha = ItemPedido('ervilha partida', .5, 7.95) >>> ervilha.descricao, ervilha.peso, ervilha.preco ('ervilha partida', .5, 7.95) >>> ervilha.peso = -10 parece uma Traceback (most recent call last): ... violação de ValueError: valor deve ser > 0 encapsulamento mas a lógica do negócio está preservada: peso agora é uma property Turing.com.br
  • 13.
    ➋ 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): 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
  • 14.
    ➋ 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
  • 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 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
  • 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 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
  • 17.
    ➋ 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
  • 18.
    ➌ 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
  • 19.
    ➌ validação comdescriptor instâncias classe Turing.com.br
  • 20.
    class Quantidade(object): ➌ def __init__(self): prefixo = self.__class__.__name__ chave = id(self) self.nome_alvo = '%s_%s' % (prefixo, chave) def __get__(self, instance, owner): return getattr(instance, self.nome_alvo) def __set__(self, instance, value): implementação if value > 0: do descriptor 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): Turing.com.br return self.peso * self.preco
  • 21.
    ➌ uso dodescriptor a classe ItemPedido tem duas instâncias de Quantidade associadas a ela Turing.com.br
  • 22.
    ➌ 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
  • 23.
    ➌ 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
  • 24.
    ➌ implementar odescriptor class Quantidade(object): def __init__(self): prefixo = self.__class__.__name__ chave = id(self) self.nome_alvo = '%s_%s' % (prefixo, chave) def __get__(self, instance, owner): return getattr(instance, self.nome_alvo) uma classe def __set__(self, instance, value): if value > 0: com método setattr(instance, self.nome_alvo, value) __get__ é um else: raise ValueError('valor deve ser > 0') descriptor Turing.com.br
  • 25.
    ➌ implementar odescriptor class Quantidade(object): def __init__(self): prefixo = self.__class__.__name__ chave = id(self) self.nome_alvo = '%s_%s' % (prefixo, chave) 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)
  • 26.
    ➌ implementar odescriptor class Quantidade(object): def __init__(self): prefixo = self.__class__.__name__ chave = id(self) self.nome_alvo = '%s_%s' % (prefixo, chave) 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: instance é a instância 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)
  • 27.
    ➌ implementar odescriptor class Quantidade(object): nome_alvo é def __init__(self): o nome do prefixo = self.__class__.__name__ atributo da chave = id(self) self.nome_alvo = '%s_%s' % (prefixo, chave) instância de def __get__(self, instance, owner): ItemPedido return getattr(instance, self.nome_alvo) que este def __set__(self, instance, value): descritor if value > 0: setattr(instance, self.nome_alvo, value) (self) else: controla raise ValueError('valor deve ser > 0') Turing.com.br
  • 28.
    ➌ implementar odescriptor __get__ e __set__ manipulam o atributo-alvo Turing.com.br no objeto ItemPedido
  • 29.
    ➌ implementar odescriptor class Quantidade(object): def __init__(self): prefixo = self.__class__.__name__ chave = id(self) self.nome_alvo = '%s_%s' % (prefixo, chave) 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
  • 30.
    ➌ 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
  • 31.
    ➌ implementar odescriptor class Quantidade(object): def __init__(self): prefixo = self.__class__.__name__ chave = id(self) self.nome_alvo = '%s_%s' % (prefixo, chave) def __get__(self, instance, owner): return getattr(instance, self.nome_alvo) def __set__(self, instance, value): if value > 0: temos que inventar um setattr(instance, self.nome_alvo, value) else: nome para o atributo-alvo raise ValueError('valor deve ser > 0') onde será armazenado o valor na instância de ItemPedido Turing.com.br
  • 32.
    ➌ implementar odescriptor >>> ervilha = ItemPedido('ervilha partida', .5, 3.95) >>> ervilha.descricao, ervilha.peso, ervilha.preco ('ervilha partida', .5, 3.95) >>> dir(ervilha) ['Quantidade_4299545872', 'Quantidade_4299546064', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'descricao', 'peso', 'preco', 'subtotal'] nesta implementação, os nomes dos atributos-alvo não são descritivos, Turing.com.br dificultando a depuração
  • 33.
    ➍ usar nomesdescritivos ItemPedido.__new__ invoca «quantidade».set_nome Turing.com.br para redefinir o nome_alvo
  • 34.
    ➍ usar nomesdescritivos ItemPedido.__new__ invoca «quantidade».set_nome «quantidade».set_nome Turing.com.br redefine o nome_alvo
  • 35.
    ➍ usar nomesdescritivos ItemPedido.__new__ class ItemPedido(object): peso = Quantidade() invoca preco = Quantidade() «quantidade».set_nome def __new__(cls, *args, **kwargs): for chave, atr in cls.__dict__.items(): if hasattr(atr, 'set_nome'): atr.set_nome('__' + cls.__name__, chave) return super(ItemPedido, cls).__new__(cls, *args, **kwargs) def __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
  • 36.
    ➍ usar nomesdescritivos «quantidade».set_nome class Quantidade(object): redefine o nome_alvo 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') Turing.com.br
  • 37.
    ➍ usar nomesdescritivos class ItemPedido(object): peso = Quantidade() preco = Quantidade() def __new__(cls, *args, **kwargs): for chave, atr in cls.__dict__.items(): if hasattr(atr, 'set_nome'): atr.set_nome('__' + cls.__name__, chave) return super(ItemPedido, cls).__new__(cls, *args, **kwargs) def __init__(self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco Quando __init__ def subtotal(self): executa, o descritor return self.peso * self.preco já está configurado com um nome de atributo-alvo Turing.com.br descritivo
  • 38.
    ➍ usar nomesdescritivos ItemPedido.__new__ invoca «quantidade».set_nome Turing.com.br
  • 39.
    ➍ usar nomesdescritivos «quantidade».set_nome Turing.com.br redefine o nome_alvo
  • 40.
    ➍ nomes descritivos >>>ervilha = ItemPedido('ervilha partida', .5, 3.95) >>> ervilha.descricao, ervilha.peso, ervilha.preco ('ervilha partida', 0.5, 3.95) >>> dir(ervilha) ['__ItemPedido_peso', '__ItemPedido_preco', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'descricao', 'peso', 'preco', 'subtotal'] nesta implementação e nas próximas, os nomes dos atributos- alvo seguem a convenção de Turing.com.br atributos protegidos de Python
  • 41.
    ➍ funciona, mascusta caro • ItemPedido aciona __new__ para construir cada nova instância • Porém a associação dos descritores é com a classe ItemPedido: o nome do atributo-alvo nunca vai mudar, uma vez definido corretamente Turing.com.br
  • 42.
    ➍ funciona, mascusta caro • Isso significa que para cada nova instância de ItemPedido que é criada, «quantidade».set_nome é invocado duas vezes • Mas o nome do atributo-alvo não tem porque mudar na vida de uma «quantidade» Turing.com.br
  • 43.
    ➍ funciona, mascusta caro 1500000.0 1427386 7.3 × 1125000.0 980708 750000.0 585394 375000.0 80004 0 versão 1 versão 2 versão 3 versão 4 Número de instâncias de ItemPedido criadas por segundo (MacBook Pro 2011, Intel Core i7) Turing.com.br
  • 44.
    ➎ como evitartrabalho inútil • ItemPedido.__new__ resolveu mas de modo ineficiente. • Cada «quantidade» deve receber o nome do seu atributo-alvo apenas uma vez, quando a própria classe ItemPedido for criada • Para isso precisamos de uma... Turing.com.br
  • 45.
    ➎ metaclasses criamclasses! metaclasses são classes cujas instâncias são classes Turing.com.br
  • 46.
    ➎ metaclasses criamclasses! metaclasses são classes cujas instâncias são classes ItemPedido é uma instância de type type é a metaclasse default em Python: a classe que normalmente constroi Turing.com.br outras classes
  • 47.
    ➎ nossa metaclasse antes depois Turing.com.br
  • 48.
    ➎ nossa metaclasse ModeloMeta é a metaclasse que vai construir a classe ItemPedido ModeloMeta.__init__ fará apenas uma vez o que antes era feito em ItemPedido.__new__ Turing.com.br a cada nova instância
  • 49.
    ➎ nossa metaclasse classModeloMeta(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) Assim dizemos que a class ItemPedido(object): __metaclass__ = ModeloMeta classe ItemPedido herda peso = Quantidade() de object, mas é uma preco = Quantidade() instância (construida por) def __init__(self, descricao, peso, preco): self.descricao = descricao ModeloMeta self.peso = peso self.preco = preco def subtotal(self): return self.peso * self.preco Turing.com.br
  • 50.
    ➎ nossa metaclasse classModeloMeta(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) Este __init__ invoca class ItemPedido(object): __metaclass__ = ModeloMeta «quantidade».set_nome, peso = Quantidade() para cada descritor, uma preco = Quantidade() vez só, na inicialização da def __init__(self, descricao, peso, preco): self.descricao = descricao classe ItemPedido self.peso = peso self.preco = preco def subtotal(self): return self.peso * self.preco Turing.com.br
  • 51.
    ➎ nossa metaclasseem ação Turing.com.br
  • 52.
    ➎ nossa metaclasseem ação Turing.com.br
  • 53.
    ➎ nossa metaclasseem ação + Turing.com.br
  • 54.
    ➎ desempenho melhor 1500000.0 1427386 mesmo desempenho, + nomes amigáveis 1125000.0 980708 750000.0 585394 585771 375000.0 80004 0 versão 1 versão 2 versão 3 versão 4 versão 5 Número de instâncias de ItemPedido criadas por segundo (MacBook Pro 2011, Intel Core i7) Turing.com.br
  • 55.
  • 56.
    ➏ 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
  • 57.
    ➏ 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
  • 58.
    ➏ esquema final + Turing.com.br
  • 59.
    ➏ esquema final Turing.com.br
  • 60.
    Oficinas Turing: computação para programadores • Próximos lançamentos: • 4ª turma de Python para quem sabe Python • 3ª turma de Objetos Pythonicos • 1ª turma de Aprenda Python com um Pythonista Turing.com.br