Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

Luciano Ramalho

March 25, 2015
Tweet

More Decks by Luciano Ramalho

Other Decks in Programming

Transcript

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

    a “visão radical” na prática
    Luciano Ramalho

    [email protected]

    @ramalhoorg

    View Slide

  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

    View Slide

  3. Simply Scheme: preface

    View Slide

  4. Nosso objetivo
    • Expandir o vocabulário com uma idéia
    poderosa:
    descritores de atributos

    View Slide

  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

    View Slide


  6. View Slide

  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

    View Slide

  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”

    View Slide

  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

    View Slide

  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

    View Slide

  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'

    View Slide


  12. View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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?

    View Slide


  19. View Slide

  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

    View Slide

  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

    View Slide


  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

    View Slide

  23. Turing.com.br
    ➊ a classe produz instâncias
    classe
    instâncias

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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)

    View Slide

  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

    View Slide

  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

    View Slide

  34. Turing.com.br
    ➌ implementar o descriptor
    __get__ e __set__
    manipulam o atributo-alvo
    no objeto ItemPedido

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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


    View Slide

  47. Turing.com.br
    Acelerando...

    View Slide

  48. Turing.com.br

    View Slide

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

    View Slide

  50. Turing.com.br
    ➏ simplicidade aparente

    View Slide

  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

    View Slide

  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

    View Slide

  53. Turing.com.br
    ➏ esquema final +

    View Slide

  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)



    View Slide

  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

    View Slide