OBJETOS PYTHNICOS Como aproveitar o Python Data Model para - - PowerPoint PPT Presentation

objetos pyth nicos
SMART_READER_LITE
LIVE PREVIEW

OBJETOS PYTHNICOS Como aproveitar o Python Data Model para - - PowerPoint PPT Presentation

P r o d u t i v i d a d e p e l a c o n s i s t n c i a OBJETOS PYTHNICOS Como aproveitar o Python Data Model para construir APIs idiomticas e fceis de usar PYTHON FLUENTE Fluent Python (OReilly, 2015) Python Fluente (Novatec,


slide-1
SLIDE 1

P r o d u t i v i d a d e p e l a c o n s i s t ê n c i a

OBJETOS PYTHÔNICOS

Como aproveitar o Python Data Model para
 construir APIs idiomáticas e fáceis de usar

slide-2
SLIDE 2

PYTHON FLUENTE

Fluent Python (O’Reilly, 2015) Python Fluente (Novatec, 2015) 流暢的 Python (Gotop, 2016)

2

slide-3
SLIDE 3

3

Sometimes you need a blank template.

slide-4
SLIDE 4

CONSISTÊNCIA

Um conceito relativo

4

slide-5
SLIDE 5

CONSISTENTE EM RELAÇÃO A … ?

Python é consistente? E Java?

5

len(texto) # string len(pesos) # array de floats len(nomes) # lista texto.length() // String pesos.length // array de floats nomes.size() // ArrayList

slide-6
SLIDE 6

THE ZEN OF PYTHON, BY TIM PETERS

Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. Special cases aren't special enough to break the rules. Although practicality beats purity. Errors should never pass silently. Unless explicitly silenced. In the face of ambiguity, refuse the temptation to guess. There should be one-- and preferably only one --obvious way to do it. Although that way may not be obvious at first unless you're Dutch. Now is better than never. Although never is often better than *right* now. If the implementation is hard to explain, it's a bad idea. If the implementation is easy to explain, it may be a good idea. Namespaces are one honking great idea -- let's do more of those!

6

slide-7
SLIDE 7

INCONSISTÊNCIA PRÁTICA

Java optou por implementar array.length como um atributo para evitar invocações de métodos…

7

pesos.length // array de floats

slide-8
SLIDE 8

CONSISTÊNCIA PRAGMÁTICA

Python implementou len() como uma função built-in pelo mesmo motivo — evitar invocações de métodos:

len(texto) # string len(pesos) # array de floats len(nomes) # lista

Para os tipos built-in (implementados em C), len(x) devolve o valor de um campo em uma struct que descreve o objeto. Só acontece uma invocação de método quando o tipo é implementado em Python (user-defined type).

8

slide-9
SLIDE 9

CONSISTÊNCIA: TIPO DEFINIDO EM PYTHON

Classes implementadas em Python podem ser consistentes com os tipos built-in e suportar len(), abs(), ==, iteração…

9

>>> v1 = Vector([3, 4]) >>> len(v1) 2 >>> abs(v1) 5.0 >>> v1 == Vector((3.0, 4.0)) True >>> x, y = v1 >>> x, y (3.0, 4.0) >>> list(v1) [3.0, 4.0]

slide-10
SLIDE 10

THE PYTHON
 DATA MODEL

O modelo de objetos da linguagem

10

slide-11
SLIDE 11

OBJECT MODEL

Modelo de objeto ou “protocolo de meta-objetos”: Interfaces-padrão de todos

  • s objetos que formam os

programas em uma linguagem:

  • funções
  • classes
  • instâncias
  • módulos
  • etc…

11

slide-12
SLIDE 12

PYTHON DATA MODEL

O modelo de objetos de Python; sua API mais fundamental. Define métodos especiais com nomes no formato __dunder__

12

class Vector: typecode = 'd' def __init__(self, components): self._components = array(self.typecode, components) def __len__(self): return len(self._components) def __iter__(self): return iter(self._components) def __abs__(self): return math.sqrt(sum(x * x for x in self))

slide-13
SLIDE 13

COMO FUNCIONAM OS MÉTODOS ESPECIAIS

Métodos especiais são invocados principalmente pelo interpretador Python, e não por você!

Lembra a programação com um framework: implementamos métodos para o framework invocar.

13

THE HOLLYWOOD PRINCIPLE: 
 DON’T CALL US, WE’LL CALL YOU!

slide-14
SLIDE 14

COMO FUNCIONAM OS MÉTODOS ESPECIAIS

Métodos especiais são invocados em contextos sintáticos especiais:

  • expressões aritméticas e lógicas — i.e. sobrecarga de operadores
  • conversão para str (ex: print(x))
  • conversão para bool em contextos booleanos if, while, and, or, not
  • acesso a atributos (o.x), inclusive atributos dinâmicos
  • emulação de coleções: o[k], k in o, len(o)
  • iteração (for)
  • context managers (with)
  • metaprogração: descritores, metaclasses

14

slide-15
SLIDE 15

NOSSO EXEMPLO

Um classe vetor para matemática aplicada

15

slide-16
SLIDE 16

VETOR EUCLIDEANO

16

Vetor(2, 1) Vetor(2, 4) Vetor(4, 5)

x y

>>> v1 = Vector([2, 4]) >>> v2 = Vector([2, 1]) >>> v1 + v2 Vector([4.0, 5.0]) Este é apenas um exemplo didático! Se você precisa lidar com vetores na vida real, use a biblioteca NumPy!

slide-17
SLIDE 17

VETOR EUCLIDEANO

17

from array import array import math class Vector: typecode = 'd' def __init__(self, components): self._components = array(self.typecode, components) def __len__(self): return len(self._components) def __iter__(self): return iter(self._components) def __abs__(self): return math.sqrt(sum(x * x for x in self)) def __eq__(self, other): return (len(self) == len(other) and all(a == b for a, b in zip(self, other)))

slide-18
SLIDE 18

from array import array import math class Vector: typecode = 'd' def __init__(self, components): self._components = array(self.typecode, components) def __len__(self): return len(self._components) def __iter__(self): return iter(self._components) def __abs__(self): return math.sqrt(sum(x * x for x in self)) def __eq__(self, other): return (len(self) == len(other) and all(a == b for a, b in zip(self, other)))

VETOR EUCLIDEANO

18

>>> v1 = Vector([3, 4]) >>> len(v1) 2 >>> abs(v1) 5.0 >>> v1 == Vector((3.0, 4.0)) True >>> x, y = v1 >>> x, y (3.0, 4.0) >>> list(v1) [3.0, 4.0]

slide-19
SLIDE 19

ALGUNS DOS MÉTODOS ESPECIAIS

__len__

Número de itens da sequência ou coleção
 


__iter__

Interface Iterable: devolve um Iterator
 


__abs__

Implementa abs(): valor absoluto de um valor numérico
 


__eq__

Sobrecarga do operador ==

19

>>> abs(3+4j) 5.0 >>> abs(v1) 5.0 >>> len('abc') 3 >>> v1 = Vector([3, 4]) >>> len(v1) 2 >>> v1 == Vector((3.0, 4.0)) True >>> x, y = v1 >>> x, y (3.0, 4.0) >>> list(v1) [3.0, 4.0]

slide-20
SLIDE 20

REPRESENTAÇÕES TEXTUAIS

Algo além de toString()

20

slide-21
SLIDE 21

TEXTO PARA VOCÊ OU PARA O USUÁRIO FINAL?

Desde 1996 já se sabe: uma
 só função não basta! str(o)

String para mostrar ao usuário final. Implementar via __str__.
 Caso ausente, __repr__ é utilizado.

repr(o)

String para depuração. Implementar via __repr__.
 Se possível, igual ao objeto literal.

21

http://www.sigs.com The Smalltalk Report 4

W

   about how to use different sorts of
  • bjects, people often ask me what these objects
look like. I draw a bunch of bubbles and arrows, underline things while I’m talking, and (hopefully) peo- ple nod knowingly. The bubbles are the objects I’m talk- ing about, and the arrows are the pertinent relationships between them. But of course the diagram is not just cir- cles and lines; everything has labels to identify them. The labels for the arrows are easy: The name of the method in the source that returns the target. But the labels for the bubbles are not so obvious. It’s a label that somehow describes the object and tells you which one it is. We all know how to label objects in this way, but what is it that we’re doing? This is a Smalltalk programmer’s first brush with a big- ger issue: How do you display an object as a string? Turns
  • ut this is not a very simple issue. VisualWorks gives you
four different ways to display an object as a string: printString, displayString, TypeConverter, and PrintConverter. Why does there need to be more than one way? Which
  • ption do you use when?
This article is in two parts. This month, I’ll talk about printString and displayString. In September, I’ll talk about TypeConverter and PrintConverter. printString AND displayString There are two messages you can send to an object to dis- play it as a string:
  • printString—Displays the object the way the
developer wants to see it.
  • displayString—Displays the object the way the user
wants to see it. printString is as old as Smalltalk itself. It was part of the
  • riginal Smalltalk-80 standard and was probably in
Smalltalk long before that. It is an essential part of how Inspector is implemented, an inspector being a develop- ment tool that can open a window to display any object. An inspector shows all of an object’s slots (its named and indexed instance variables); when you select one, it shows that slot’s value as a string by sending the slot’s value the message printString. The inspector also shows another slot, the pseudovariable self. When you select that slot, the inspector displays the object it’s inspecting by sending it printString. displayString was introduced in VisualWorks 1.0, more than 10 years after printString. displayString is an essential part of how SequenceView (VisualWorks’ List widget) is
  • implemented. The list widget displays its items by dis-
playing a string for each item. The purpose of this dis- play-string is very similar to that of the print-string, but the results are often different. printString describes an object to a Smalltalk program-
  • mer. To a programmer, one of an object’s most important
properties is its class. Thus a print-string either names the object’s class explicitly (a VisualLauncher, Ordered- Collection (#a #b), etc.) or the class is implied (#printString is a Symbol, 1/2 is a Fraction, etc.). The user, on the other hand, couldn’t care less what an object’s class is. Because most users don’t know OO, telling them that this is an
  • bject and what its class is would just confuse them. The
user wants to know the name of the object. displayString describes the object to the user by printing the object’s name (although what constitutes an object’s “name” is
  • pen to interpretation).
STANDARD IMPLEMENTATION The first thing to understand about printString is that it doesn’t do much; its companion method, printOn:, does all of the work. This makes printString more efficient because it uses a stream for concatenation.1 Here are the basic implementors in VisualWorks: Object>>printString | aStream | aStream := WriteStream on: (String new: 16). self printOn: aStream. ^aStream contents Object>>printOn: aStream | title | title := self class name.

How to display an object

as a string: printString and

displayString

Bobby Woolf

slide-22
SLIDE 22

STR X REPR

22

from array import array import math import reprlib class Vector: typecode = 'd' # ... def __str__(self): return str(tuple(self)) def __repr__(self): components = reprlib.repr(self._components) components = components[components.find('['):-1] return 'Vector({})'.format(components)

slide-23
SLIDE 23

STR X REPR

23

from array import array import math import reprlib class Vector: typecode = 'd' # ... def __str__(self): return str(tuple(self)) def __repr__(self): components = reprlib.repr(self._components) components = components[components.find('['):-1] return 'Vector({})'.format(components)

>>> Vector([3.1, 4.2]) Vector([3.1, 4.2]) >>> Vector(range(10)) Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) >>> v3 = Vector([3, 4, 5]) >>> v3 Vector([3.0, 4.0, 5.0]) >>> print(v3) (3.0, 4.0, 5.0) >>> v3_clone = eval(repr(v3)) >>> v3_clone == v3 True

slide-24
SLIDE 24

INDEXAR E FATIAR

Suporte ao operador [ ]

24

slide-25
SLIDE 25

O OPERADOR [ ]

Para sobrecarregar [ ], implemente __getitem__

O mesmo método é usado para acessar um item por índice/chave e para produzir

  • fatias. Não é obrigatório implementar fatiamento (pode não fazer sentido).

Além de self, __getitem__ recebe argumento que pode ser:

  • Um índice numérico ou chave
  • Uma instância de slice

25

>>> class Foo: ... def __getitem__(self, x): ... return 'x -> ' + repr(x) ... >>> o = Foo() >>> o[42] 'x -> 42' >>> o[1:3] 'x -> slice(1, 3, None)' >>> o[10:100:3] 'x -> slice(10, 100, 3)'

slide-26
SLIDE 26

STR X REPR

26

from array import array import math import reprlib import numbers class Vector: typecode = 'd' # ... def __getitem__(self, index): cls = type(self) if isinstance(index, slice): return cls(self._components[index]) elif isinstance(index, numbers.Integral): return self._components[index] else: msg = '{cls.__name__} indices must be integers' raise TypeError(msg.format(cls=cls))

slide-27
SLIDE 27

STR X REPR

27

from array import array import math import reprlib import numbers class Vector: typecode = 'd' # ... def __getitem__(self, index): cls = type(self) if isinstance(index, slice): return cls(self._components[index]) elif isinstance(index, numbers.Integral): return self._components[index] else: msg = '{cls.__name__} indices must be integers' raise TypeError(msg.format(cls=cls)) >>> v = Vector([10, 20, 30, 40, 50]) >>> v[0] 10.0 >>> v[-1] 50.0 >>> v[:3] Vector([10.0, 20.0, 30.0])

slide-28
SLIDE 28

SOBREGARGA DE OPERADORES

Aplicação de um padrão clássico: double dispatch

28

slide-29
SLIDE 29

SOBRECARGA DE OPERADORES

Fórmula de juros compostos, compatível com todos os tipos numéricos da biblioteca padrão de Python: Versão Java da mesma fórmula para operar com BigDecimal:

29

interest = principal * ((1 + rate) ** periods - 1)

interest = principal.multiply(BigDecimal.ONE.add(rate).pow(periods).subtract(BigDecimal.ONE));

slide-30
SLIDE 30

SOBRECARGA DE OPERADORES

Métodos especiais como __add__, __eq__, __xor__ etc. representam os operadores aritméticos, relacionais e bitwise. Página 13 de Fluent Python:

30

>>> a = 2 >>> b = 3 >>> a + b 5 >>> a.__add__(b) 5

slide-31
SLIDE 31

MULTIPLICAÇÃO POR UM ESCALAR

31

from array import array import math import reprlib import numbers class Vector: typecode = 'd' # ... def __mul__(self, scalar): if isinstance(scalar, numbers.Real): return Vector(n * scalar for n in self) else: return NotImplemented >>> v1 = Vector([1, 2, 3]) >>> v1 * 10 Vector([10.0, 20.0, 30.0]) >>> from fractions import Fraction >>> v1 * Fraction(1, 3) Vector([0.3333333333333333, 0.6666666666666666, 1.0])

slide-32
SLIDE 32

SURGE UM PROBLEMA…

A operação a * b é executada como a.__mul__(b). Mas se a é um int, a implementação de __mul__ em int não sabe lidar com Vector!

32

>>> v1 = Vector([1, 2, 3]) >>> v1 * 10 Vector([10.0, 20.0, 30.0]) >>> 10 * v1 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for *: 'int' and 'Vector'

slide-33
SLIDE 33

DOUBLE-DISPATCH

33

call a.__mul__(b) a has __mul__
 ? yes result is NotImplemented ?

a * b

no return result call b.__rmul__(a) b has __rmul__
 ? yes result is NotImplemented ? yes yes raise TypeError no no no

slide-34
SLIDE 34

IMPLEMENTAÇÃO DO OPERADOR * REVERSO

34

from array import array import math import reprlib import numbers class Vector: typecode = 'd' # ... def __mul__(self, scalar): if isinstance(scalar, numbers.Real): return Vector(n * scalar for n in self) else: return NotImplemented def __rmul__(self, scalar): return self * scalar >>> v1 = Vector([1, 2, 3]) >>> v1 * 10 Vector([10.0, 20.0, 30.0]) >>> 10 * v1 Vector([10.0, 20.0, 30.0])

slide-35
SLIDE 35

O OPERADOR @

Uma das novidades do Python 3.5

35

slide-36
SLIDE 36

OPERADOR @

Para multiplicação de matrizes ou produto escalar entre vetores (dot product).

36

>>> va = Vector([1, 2, 3]) >>> vz = Vector([5, 6, 7]) >>> va @ vz # 1*5 + 2*6 + 3*7 38 >>> [10, 20, 30] @ vz 380.0 >>> va @ 3 Traceback (most recent call last): ... TypeError: unsupported operand type(s) for @: 'Vector' and 'int'

slide-37
SLIDE 37

IMPLEMENTAÇÃO DO OPERADOR @

37

from array import array import math import reprlib import numbers class Vector: typecode = 'd' # ... def __matmul__(self, other): try: return sum(a * b for a, b in zip(self, other)) except TypeError: return NotImplemented def __rmatmul__(self, other): return self @ other # só funciona em Python 3.5 >>> va = Vector([1, 2, 3]) >>> vz = Vector([5, 6, 7]) >>> va @ vz # 1*5 + 2*6 + 3*7 38 >>> [10, 20, 30] @ vz 380.0

slide-38
SLIDE 38

MUITO GRATO!

slide-39
SLIDE 39

BÔNUS: HASHING

Para usar instâncias de Vector em sets ou chaves de dicionários

39

slide-40
SLIDE 40

HASHABLE OBJECTS

Um objeto é hashable se e somente se:

  • Seu valor é imutável
  • Implementa um método __hash__
  • Implementa um método __eq__
  • Quando a == b, então hash(a) == hash(b)

40

slide-41
SLIDE 41

COMO COMPUTAR O HASH DE UM OBJETO

Algoritmo básico: computar o hash de cada atributo do objeto, agregando recursivamente com xor.

41

>>> v1 = Vector([3, 4]) >>> hash(v1) == 3 ^ 4 True >>> v3 = Vector([3, 4, 5]) >>> hash(v3) == 3 ^ 4 ^ 5 True >>> v6 = Vector(range(6)) >>> hash(v6) == 0 ^ 1 ^ 2 ^ 3 ^ 4 ^ 5 True >>> v2 = Vector([3.1, 4.2]) >>> hash(v2) == hash(3.1) ^ hash(4.2) True

slide-42
SLIDE 42

MAP-REDUCE

slide-43
SLIDE 43

HASHABLE VECTOR

Exemplo de map-reduce:

43

from array import array import math import reprlib import numbers import functools import operator class Vector: typecode = 'd' # ... def __hash__(self): hashes = (hash(x) for x in self) return functools.reduce(operator.xor, hashes, 0) >>> {v1, v2, v3, v6} {Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]), Vector([3.0, 4.0, 5.0]), Vector([3.0, 4.0]), Vector([3.1, 4.2])}

slide-44
SLIDE 44

PERGUNTAS?