A visão radical na prática

A visão radical na prática

Produtividade e qualidade em Python através da metaprogramação: palestra apresentada na QCon SP 2015

27c093d0834208f4712faaaec38c2c5c?s=128

Luciano Ramalho

March 25, 2015
Tweet

Transcript

  1. 1.

    Produtividade e qualidade em Python através da metaprogramação ou a

    “visão radical” na prática Luciano Ramalho ramalho@python.pro.br @ramalhoorg
  2. 2.

    Fluent Python (O’Reilly) • Early Release: out/2014 • First Edition:

    jul/2015 • ~ 700 páginas • Data structures • Functions as objects • Classes and objects • Control flow • Metaprogramming
  3. 5.

    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
  4. 6.

  5. 7.

    ➊ 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. 8.

    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):! return self.peso * self.preco o método inicializador é conhecido como “dunder init”
  7. 9.

    >>> ervilha = ItemPedido('ervilha partida', .5, 7.95) >>> ervilha.descricao, ervilha.peso,

    ervilha.preco ('ervilha partida', .5, 7.95) >>> ervilha.peso = -10 >>> ervilha.subtotal() -79.5 Turing.com.br ➊ porém, simples demais isso vai dar problema na hora de cobrar... Jeff Bezos of Amazon: Birth of a Salesman
 WSJ.com - http://j.mp/VZ5not “Descobrimos que os clientes
 conseguiam encomendar uma quantidade negativa de livros! E nós creditávamos o valor em seus cartões...” Jeff Bezos
  8. 10.

    ➊ a solução clássica class ItemPedido(object):! ! def __init__(self, descricao,

    peso, preco):! self.descricao = descricao! self.set_peso(peso)! self.preco = preco! ! def subtotal(self):! return self.get_peso() * self.preco! ! def get_peso(self):! return self.__peso! ! def set_peso(self, valor):! if valor > 0:! self.__peso = valor! else:! raise ValueError('valor deve ser > 0') atributo protegido mudanças na interface
  9. 11.

    • 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... ➊ porém, a API foi alterada! >>> ervilha.peso Traceback (most recent call last): ... AttributeError: 'ItemPedido' object has no attribute 'peso'
  10. 12.

  11. 13.

    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 ➋ o segundo doctest parece uma violação de encapsulamento mas a lógica do negócio é preservada peso não foi alterado
  12. 14.

    Turing.com.br ➋ validação com property peso agora é uma 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
  13. 15.

    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') atributo protegido
  14. 16.

    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') no __init__ a property já está em uso
  15. 17.

    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') o atributo protegido __peso só é acessado nos métodos da property
  16. 18.

    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') e se quisermos a mesma lógica para o preco? teremos que duplicar tudo isso?
  17. 19.

  18. 20.

    ➌ os atributos gerenciados (managed attributes) usaremos descritores para gerenciar

    o acesso aos atributos peso e preco, preservando a lógica de negócio __init__ subtotal descricao peso {descriptor} preco {descriptor} ItemPedido
  19. 21.

    Turing.com.br ➌ validação com descriptor peso e preco são atributos

    da classe ItemPedido a lógica fica em 
 __get__ e __set__, podendo ser reutilizada
  20. 22.

    ➌ implementação do descritor 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')! ! ! 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):! return self.peso * self.preco classe new-style
  21. 25.

    Turing.com.br ➌ uso do descriptor a classe ItemPedido tem duas

    instâncias de Quantidade associadas a ela
  22. 26.

    Turing.com.br ➌ implementação do 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')! ! ! 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):! return self.peso * self.preco
  23. 27.

    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):! return self.peso * self.preco Turing.com.br ➌ uso do descriptor a classe ItemPedido tem duas instâncias de Quantidade associadas a ela
  24. 28.

    Turing.com.br ➌ uso do descriptor 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):! return self.peso * self.preco cada instância da classe Quantidade controla um atributo de ItemPedido
  25. 29.

    ➌ uso do descriptor 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):! return self.peso * self.preco todos os acessos a peso e preco passam pelos descritores
  26. 30.

    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') uma classe com método __get__ é um descriptor
  27. 31.

    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') Turing.com.br ➌ implementar o descriptor self é a instância 
 do descritor (associada
 ao preco ou ao peso)
  28. 32.

    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') Turing.com.br ➌ implementar o descriptor self é a instância 
 do descritor (associada
 ao preco ou ao peso) instance é a instância de ItemPedido que está sendo acessada
  29. 33.

    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') Turing.com.br ➌ implementar o descriptor nome_alvo é o nome do atributo da instância de ItemPedido que este descritor (self) controla
  30. 35.

    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') Turing.com.br ➌ implementar o descriptor __get__ e __set__ usam 
 getattr e setattr para manipular o atributo-alvo na instância de ItemPedido
  31. 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
  32. 37.

    Turing.com.br ➌ inicialização do descritor 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):! return self.peso * self.preco quando um descritor é instanciado, o atributo ao qual ele será vinculado ainda não existe! exemplo: o atributo preco só passa a existir após a atribuição
  33. 38.

    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') Turing.com.br ➌ implementar o descriptor temos que gerar um nome para o atributo-alvo onde será armazenado o valor na instância de ItemPedido
  34. 39.

    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') cada instância de Quantidade precisa criar e usar um nome_alvo diferente ➌ implementar o descriptor
  35. 40.

    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_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
  36. 41.

    ➌ os atributos alvo Quantidade_0 armazena o valor de peso

    __init__ __get__ __set__ nome_alvo «descriptor» Quantidade __init__ subtotal descricao Quantidade_0 Quantidade_1 ItemPedido «peso» «preco» «get/set atributo alvo» Quantidade_1 armazena o valor de preco
  37. 42.

    ➌ os atributos gerenciados clientes da classe ItemPedido não precisam

    saber como peso e preco são gerenciados E nem precisam saber que Quantidade_0 e Quantidade_1 existem! __init__ subtotal descricao peso {descriptor} preco {descriptor} ItemPedido
  38. 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
  39. 44.

    ➌ o desafio 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):! return self.peso * self.preco quando cada descritor é instanciado, a classe ItemPedido não existe, e nem os atributos gerenciados
  40. 45.

    ➌ o desafio 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):! return self.peso * self.preco por exemplo, o atributo peso só é criado depois que Quantidade() é instanciada
  41. 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...

  42. 51.

    Turing.com.br 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 ➏ o poder da abstração
  43. 52.

    Turing.com.br 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):! __metaclass__ = ModeloMeta ➏ módulo modelo.py
  44. 54.

    Referências • Raymond Hettinger’s 
 Descriptor HowTo Guide • Alex

    Martelli’s
 Python in a Nutshell, 2e. • David Beazley’s
 Python Essential Reference, 
 4th edition
 (covers Python 2.6)
 
 

  45. 55.

    PythonPro: escola virtual • Instrutores: Renzo Nuccitelli e Luciano Ramalho

    • Na sua empresa ou online ao vivo com 
 Adobe Connect: http://python.pro.br • Python Prático • Python Birds • Objetos Pythônicos • Python para quem sabe Python