SlideShare uma empresa Scribd logo
1 de 57
Baixar para ler offline
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

Mais conteúdo relacionado

Mais procurados

Java Básico :: Classe Vector
Java Básico :: Classe VectorJava Básico :: Classe Vector
Java Básico :: Classe VectorWesley R. Bezerra
 
Python para quem sabe Python (aula 2)
Python para quem sabe Python (aula 2)Python para quem sabe Python (aula 2)
Python para quem sabe Python (aula 2)Luciano Ramalho
 
Aprendendo Kotlin na Prática
Aprendendo Kotlin na PráticaAprendendo Kotlin na Prática
Aprendendo Kotlin na PráticaFelipe Pedroso
 
Iteraveis e geradores em Python
Iteraveis e geradores em PythonIteraveis e geradores em Python
Iteraveis e geradores em PythonLuciano Ramalho
 
Herança e polimorfismo em Java
Herança e polimorfismo em Java Herança e polimorfismo em Java
Herança e polimorfismo em Java Evandro Júnior
 
Jython no JavaOne Latin America 2011
Jython no JavaOne Latin America 2011Jython no JavaOne Latin America 2011
Jython no JavaOne Latin America 2011Luciano Ramalho
 
Python: Iteraveis, geradores etc
Python: Iteraveis, geradores etcPython: Iteraveis, geradores etc
Python: Iteraveis, geradores etcLuciano Ramalho
 

Mais procurados (9)

Escrevendo plugins JQuery
Escrevendo plugins JQueryEscrevendo plugins JQuery
Escrevendo plugins JQuery
 
Java Básico :: Classe Vector
Java Básico :: Classe VectorJava Básico :: Classe Vector
Java Básico :: Classe Vector
 
Python para quem sabe Python (aula 2)
Python para quem sabe Python (aula 2)Python para quem sabe Python (aula 2)
Python para quem sabe Python (aula 2)
 
Iteraveis e geradores
Iteraveis e geradoresIteraveis e geradores
Iteraveis e geradores
 
Aprendendo Kotlin na Prática
Aprendendo Kotlin na PráticaAprendendo Kotlin na Prática
Aprendendo Kotlin na Prática
 
Iteraveis e geradores em Python
Iteraveis e geradores em PythonIteraveis e geradores em Python
Iteraveis e geradores em Python
 
Herança e polimorfismo em Java
Herança e polimorfismo em Java Herança e polimorfismo em Java
Herança e polimorfismo em Java
 
Jython no JavaOne Latin America 2011
Jython no JavaOne Latin America 2011Jython no JavaOne Latin America 2011
Jython no JavaOne Latin America 2011
 
Python: Iteraveis, geradores etc
Python: Iteraveis, geradores etcPython: Iteraveis, geradores etc
Python: Iteraveis, geradores etc
 

Semelhante a Encapsulamento com descritores

Programando em python classes
Programando em python   classesProgramando em python   classes
Programando em python classessamuelthiago
 
Introdução à análise orientada a objetos parte 2
Introdução à análise orientada a objetos parte 2Introdução à análise orientada a objetos parte 2
Introdução à análise orientada a objetos parte 2irenescotolo
 
Java - Visão geral e Exercícios
Java - Visão geral e ExercíciosJava - Visão geral e Exercícios
Java - Visão geral e ExercíciosArthur Emanuel
 
AULA 1 - Classes e Objetos com codigicação Java.ppt
AULA 1 - Classes e Objetos com codigicação Java.pptAULA 1 - Classes e Objetos com codigicação Java.ppt
AULA 1 - Classes e Objetos com codigicação Java.pptJoberthSilva
 
AULA 1 - Classes e Objetos.ppt
AULA 1 - Classes e Objetos.pptAULA 1 - Classes e Objetos.ppt
AULA 1 - Classes e Objetos.pptJoberthSilva
 
07 construtores e finalize
07   construtores e finalize07   construtores e finalize
07 construtores e finalizeArtur Todeschini
 

Semelhante a Encapsulamento com descritores (11)

Programando em python classes
Programando em python   classesProgramando em python   classes
Programando em python classes
 
Programando em python - Classes
Programando em python -  ClassesProgramando em python -  Classes
Programando em python - Classes
 
Refactoring
RefactoringRefactoring
Refactoring
 
05 poo-ii
05   poo-ii05   poo-ii
05 poo-ii
 
Introdução à análise orientada a objetos parte 2
Introdução à análise orientada a objetos parte 2Introdução à análise orientada a objetos parte 2
Introdução à análise orientada a objetos parte 2
 
Java - Visão geral e Exercícios
Java - Visão geral e ExercíciosJava - Visão geral e Exercícios
Java - Visão geral e Exercícios
 
Pacotes e Encapsulamento
Pacotes e EncapsulamentoPacotes e Encapsulamento
Pacotes e Encapsulamento
 
Pacotes e Encapsulamento
Pacotes e EncapsulamentoPacotes e Encapsulamento
Pacotes e Encapsulamento
 
AULA 1 - Classes e Objetos com codigicação Java.ppt
AULA 1 - Classes e Objetos com codigicação Java.pptAULA 1 - Classes e Objetos com codigicação Java.ppt
AULA 1 - Classes e Objetos com codigicação Java.ppt
 
AULA 1 - Classes e Objetos.ppt
AULA 1 - Classes e Objetos.pptAULA 1 - Classes e Objetos.ppt
AULA 1 - Classes e Objetos.ppt
 
07 construtores e finalize
07   construtores e finalize07   construtores e finalize
07 construtores e finalize
 

Mais de Luciano Ramalho

Introdução a linguagem Python
Introdução a linguagem PythonIntrodução a linguagem Python
Introdução a linguagem PythonLuciano Ramalho
 
Iteráveis e geradores (versão RuPy)
Iteráveis e geradores (versão RuPy)Iteráveis e geradores (versão RuPy)
Iteráveis e geradores (versão RuPy)Luciano Ramalho
 
Orientação a objetos em Python (compacto)
Orientação a objetos em Python (compacto)Orientação a objetos em Python (compacto)
Orientação a objetos em Python (compacto)Luciano Ramalho
 
Arduino: hardware hacking & coding dojo
Arduino: hardware hacking & coding dojoArduino: hardware hacking & coding dojo
Arduino: hardware hacking & coding dojoLuciano Ramalho
 
Objetos Pythonicos - compacto
Objetos Pythonicos - compactoObjetos Pythonicos - compacto
Objetos Pythonicos - compactoLuciano Ramalho
 
OO em Python sem sotaque
OO em Python sem sotaqueOO em Python sem sotaque
OO em Python sem sotaqueLuciano Ramalho
 
Python, a arma secreta do Google
Python, a arma secreta do GooglePython, a arma secreta do Google
Python, a arma secreta do GoogleLuciano Ramalho
 
Alex Martelli's Python Design Patterns
Alex Martelli's Python Design PatternsAlex Martelli's Python Design Patterns
Alex Martelli's Python Design PatternsLuciano Ramalho
 
JavaScript agora é sério (TDC 2011)
JavaScript agora é sério (TDC 2011)JavaScript agora é sério (TDC 2011)
JavaScript agora é sério (TDC 2011)Luciano Ramalho
 
JavaScript agora é sério (FISL 2011)
JavaScript agora é sério (FISL 2011)JavaScript agora é sério (FISL 2011)
JavaScript agora é sério (FISL 2011)Luciano Ramalho
 
JavaScript: agora é sério
JavaScript: agora é sérioJavaScript: agora é sério
JavaScript: agora é sérioLuciano Ramalho
 

Mais de Luciano Ramalho (20)

Wiki-wiki S/A
Wiki-wiki S/AWiki-wiki S/A
Wiki-wiki S/A
 
Mongodb: agregação
Mongodb: agregaçãoMongodb: agregação
Mongodb: agregação
 
Introdução a linguagem Python
Introdução a linguagem PythonIntrodução a linguagem Python
Introdução a linguagem Python
 
Iteráveis e geradores (versão RuPy)
Iteráveis e geradores (versão RuPy)Iteráveis e geradores (versão RuPy)
Iteráveis e geradores (versão RuPy)
 
Orientação a objetos em Python (compacto)
Orientação a objetos em Python (compacto)Orientação a objetos em Python (compacto)
Orientação a objetos em Python (compacto)
 
Arduino: hardware hacking & coding dojo
Arduino: hardware hacking & coding dojoArduino: hardware hacking & coding dojo
Arduino: hardware hacking & coding dojo
 
Objetos Pythonicos - compacto
Objetos Pythonicos - compactoObjetos Pythonicos - compacto
Objetos Pythonicos - compacto
 
Dojo com Processing
Dojo com ProcessingDojo com Processing
Dojo com Processing
 
Dojo com Arduino
Dojo com ArduinoDojo com Arduino
Dojo com Arduino
 
Open Library no Mongodb
Open Library no MongodbOpen Library no Mongodb
Open Library no Mongodb
 
OO em Python sem sotaque
OO em Python sem sotaqueOO em Python sem sotaque
OO em Python sem sotaque
 
Modelos ricos
Modelos ricosModelos ricos
Modelos ricos
 
Python, a arma secreta do Google
Python, a arma secreta do GooglePython, a arma secreta do Google
Python, a arma secreta do Google
 
Ensinando OO com Python
Ensinando OO com PythonEnsinando OO com Python
Ensinando OO com Python
 
Alex Martelli's Python Design Patterns
Alex Martelli's Python Design PatternsAlex Martelli's Python Design Patterns
Alex Martelli's Python Design Patterns
 
Dspace em 5 minutos
Dspace em 5 minutosDspace em 5 minutos
Dspace em 5 minutos
 
JavaScript agora é sério (TDC 2011)
JavaScript agora é sério (TDC 2011)JavaScript agora é sério (TDC 2011)
JavaScript agora é sério (TDC 2011)
 
JavaScript agora é sério (FISL 2011)
JavaScript agora é sério (FISL 2011)JavaScript agora é sério (FISL 2011)
JavaScript agora é sério (FISL 2011)
 
Wiki sa-v2
Wiki sa-v2Wiki sa-v2
Wiki sa-v2
 
JavaScript: agora é sério
JavaScript: agora é sérioJavaScript: agora é sério
JavaScript: agora é sério
 

Encapsulamento com descritores

  • 1. Descritores de atributos em 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é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
  • 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. ➊ 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, 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. ➊ 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. ➊ 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...
  • 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
  • 13. ➋ 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
  • 14. ➋ 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
  • 15. ➋ 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
  • 16. ➋ 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
  • 17. ➋ 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
  • 18. ➋ 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
  • 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. ➌ 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. 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 classe produz instâncias classe instâncias Turing.com.br
  • 24. ➌ a classe descriptor instâncias classe Turing.com.br
  • 25. ➌ uso do descriptor 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 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
  • 28. ➌ 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
  • 29. ➌ 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
  • 30. ➌ 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
  • 31. ➌ 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)
  • 32. ➌ 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)
  • 33. ➌ 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
  • 34. ➌ implementar o descriptor __get__ e __set__ manipulam o atributo-alvo Turing.com.br no objeto ItemPedido
  • 35. ➌ 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
  • 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 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
  • 38. ➌ 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
  • 39. ➌ 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')
  • 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. ➌ 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
  • 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. ➌ 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...
  • 49. ➎ metaclasses criam classes! metaclasses são classes cujas instâncias são classes Turing.com.br
  • 50. ➏ simplicidade aparente Turing.com.br
  • 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. ➏ 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 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