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
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,
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
Como aproveitar o Python Data Model para construir APIs idiomáticas e fáceis de usar
PYTHON FLUENTE
Fluent Python (O’Reilly, 2015) Python Fluente (Novatec, 2015) 流暢的 Python (Gotop, 2016)
2
3
Sometimes you need a blank template.
Um conceito relativo
4
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
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
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
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
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]
O modelo de objetos da linguagem
10
OBJECT MODEL
Modelo de objeto ou “protocolo de meta-objetos”: Interfaces-padrão de todos
programas em uma linguagem:
11
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))
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!
COMO FUNCIONAM OS MÉTODOS ESPECIAIS
Métodos especiais são invocados em contextos sintáticos especiais:
14
Um classe vetor para matemática aplicada
15
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!
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)))
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]
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]
Algo além de toString()
20
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 4W
about how to use different sorts ofHow to display an object
as a string: printString and
displayString
Bobby Woolf
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)
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
Suporte ao operador [ ]
24
O OPERADOR [ ]
Para sobrecarregar [ ], implemente __getitem__
O mesmo método é usado para acessar um item por índice/chave e para produzir
Além de self, __getitem__ recebe argumento que pode ser:
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)'
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))
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])
Aplicação de um padrão clássico: double dispatch
28
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));
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
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])
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'
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
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])
Uma das novidades do Python 3.5
35
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'
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
Para usar instâncias de Vector em sets ou chaves de dicionários
39
HASHABLE OBJECTS
Um objeto é hashable se e somente se:
40
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
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])}