• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Encapsulamento com Descritores em Python
 

Encapsulamento com Descritores em Python

on

  • 949 views

 

Statistics

Views

Total Views
949
Views on SlideShare
941
Embed Views
8

Actions

Likes
5
Downloads
27
Comments
0

1 Embed 8

https://twitter.com 8

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Encapsulamento com Descritores em Python Encapsulamento com Descritores em Python Presentation Transcript

    • Descritores de atributos em Pythonoutubro/2012Luciano Ramalholuciano@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 umaTraceback (most recent call last): ... violação deValueError: valor deve ser > 0 encapsulamentomas a lógica do negócioestá preservada:peso agora é uma property Turing.com.br
    • ➋ implementar propertyclass ItemPedido(object): def __init__(self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco def subtotal(self): return self.peso * self.preco @property def peso(self): 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 propertyclass ItemPedido(object): def __init__(self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco def subtotal(self): return self.peso * self.preco @property def peso(self): atributo return self.__peso protegido @peso.setter def peso(self, valor): if valor > 0: self.__peso = valor else: raise ValueError(valor deve ser > 0) Turing.com.br
    • ➋ implementar propertyclass ItemPedido(object): def __init__(self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco def subtotal(self): return self.peso * self.preco no __init__ a property já @property def peso(self): está em uso return self.__peso @peso.setter def peso(self, valor): if valor > 0: self.__peso = valor else: raise ValueError(valor deve ser > 0) Turing.com.br
    • ➋ implementar propertyclass ItemPedido(object): def __init__(self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco def subtotal(self): return self.peso * self.preco o atributo @property protegido def peso(self): __peso só é return self.__peso acessado nos @peso.setter métodos da def peso(self, valor): if valor > 0: property self.__peso = valor else: raise ValueError(valor deve ser > 0) Turing.com.br
    • ➋ implementar propertyclass ItemPedido(object): def __init__(self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco ese quisermos def subtotal(self): a mesma lógica return self.peso * self.preco para o preco? @property def peso(self): return self.__peso teremos que @peso.setter duplicar tudo def peso(self, valor): if valor > 0: isso? self.__peso = valor else: raise ValueError(valor deve ser > 0) Turing.com.br
    • ➌ 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 classeclass ItemPedido(object): peso = Quantidade() ItemPedido preco = Quantidade() tem duas def __init__(self, descricao, peso, preco): instâncias de self.descricao = descricao Quantidade self.peso = peso self.preco = preco associadas a ela def subtotal(self): return self.peso * self.preco Turing.com.br
    • ➌ uso do descriptorclass ItemPedido(object): peso = Quantidade() cada instância preco = Quantidade() da classe def __init__(self, descricao, peso, preco): Quantidade self.descricao = descricao self.peso = peso controla um self.preco = preco atributo de def subtotal(self): ItemPedido return self.peso * self.preco Turing.com.br
    • ➌ implementar o descriptorclass 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 descriptorclass 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 descriptorclass 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 descriptorclass 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 descriptorclass 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 aoclass ItemPedido(object): qual ele será vinculado peso = Quantidade() preco = Quantidade() ainda não existe! def __init__(self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco def subtotal(self): exemplo: o return self.peso * self.preco atributo preco só passa a existir após a atribuição Turing.com.br
    • ➌ implementar o descriptorclass 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_nomeclass 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 descritivosclass 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 descritivosItemPedido.__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 caro1500000.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 metaclasseclass 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 aclass 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 metaclasseclass 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__ invocaclass 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 melhor1500000.0 1427386 mesmo desempenho, + nomes amigáveis1125000.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