Python Data Model¶
O modelo de dados python¶
- Nesses ultimo dias vc pode ter achado estranho escrever len(collection)
em vez de collection.len()...
A ponta do iceberg...¶
O iceberg é chamado de python data model e descreve a API que você pode usar para fazer com que seus próprios objetos interajam bem com os recursos mais “malukos” da linguagem.
- É como se fosse um framework, formalizando a contrução da propria linguagem
- O interpretador python chama metodos especiais para realizar operações básicas.
- Os metodos especiais são sempre descritos com underscores duplos
Exemplo Um Baralho Pythonico¶
import collections
Card = collections.namedtuple('Card', ['rank', 'suit'])
class FrenchDeck:
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
suits = 'spades diamonds clubs hearts'.split()
def __init__(self):
"""
O método __init__ é o construtor da classe, ou seja
é o método que será executado quando a classe for instanciada.
"""
self._cards = [Card(rank, suit) for suit in self.suits
for rank in self.ranks]
def __len__(self):
return len(self._cards)
def __getitem__(self, position):
return self._cards[position]
>>> from frenchdeck import FrenchDeck, Card
>>> beer_card = Card('7', 'diamonds')
>>> beer_card
Card(rank='7', suit='diamonds')
>>> deck = FrenchDeck()
>>> len(deck)
52
>>> deck[:3]
[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')]
>>> deck[12::13]
[Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'), Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')]
>>> Card('Q', 'hearts') in deck
True
>>> Card('Z', 'clubs') in deck
False
>>> for card in deck:
... print(card)
Card(rank='2', suit='spades')
Card(rank='3', suit='spades')
Card(rank='4', suit='spades')
...
>>> for card in reversed(deck):
... print(card)
Card(rank='A', suit='hearts')
Card(rank='K', suit='hearts')
Card(rank='Q', suit='hearts')
>>> for n, card in enumerate(deck, 1):
... print(n, card)
1 Card(rank='2', suit='spades')
2 Card(rank='3', suit='spades')
3 Card(rank='4', suit='spades')
Como os metodos especiais sao usados¶
- Eles foram criados para serem chamados pelo interpretador e não por você.
- Não escrevemos my_object.__len__(), escrevemos len(my_object)
Se my_object for uma instancia de uma classe definida por você, o python chamara o metodo __len__ que voce implementou.
Para muitos tipos embutidos o interpretador usará um atalho: a implementação de len() do CPython, retorna o valor do ob_size da Scruct C PyVarObject que representa qualquer objeto embutido de tamanho váriavel na memoria. Isto é muito mais rapido que chamar um metodo.
Emulando tipos numéricos¶
from math import hypot
class Vector:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __repr__(self):
return 'Vector(%r, %r)' % (self.x, self.y)
def __abs__(self):
return hypot(self.x, self.y)
def __bool__(self):
return bool(abs(self))
def __add__(self, other):
x = self.x + other.x
y = self.y + other.y
return Vector(x, y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
Mecanismo flexivel de parametros¶
Um dos melhores recursos das funções python:
- “*” faz com que o vetor seja utilizado como argumentos ordenados.
- “**” faz com que o dicionario seja usado como argumentos nomeados.
def func_var_args(*args):
print(args)
func_var_args(1, 2, '3')
# (1, 2, '3')
Isto é usado quando não sabemos a quantidade de parâmetros.
def func_keyword_arg(**kwargs):
print(kwargs)
func_keyword_arg(keyword1=10, keyword2='foo')
# {'keyword2': 'foo', 'keyword1': 10}
def tag(name, cls=None, *content, **attrs):
"""Generate one or more HTML tags"""
if cls is not None:
attrs['class'] = cls
if attrs:
attr_str = ''.join(' %s="%s"' % (attr, value) for attr, value in sorted(attrs.items()))
else:
attr_str = ''
if content:
return '\n'.join('<%s%s>%s</%s>' % (name, attr_str, c, name) for c in content)
else:
return '<%s%s />' % (name, attr_str)
>>> tag('br') # <1>
'<br />'
>>> tag('p', 'hello') # <2>
'<p>hello</p>'
>>> print(tag('p', 'hello', 'world'))
<p>hello</p>
<p>world</p>
>>> tag('p', 'hello', id=33) # <3>
'<p id="33">hello</p>'
>>> print(tag('p', 'hello', 'world', cls='sidebar')) # <4>
<p class="sidebar">hello</p>
<p class="sidebar">world</p>
>>> tag(content='testing', name="img") # <5>
'<img content="testing" />'
>>> my_tag = {'name': 'img', 'title': 'Sunset Boulevard',
... 'src': 'sunset.jpg', 'cls': 'framed'}
>>> tag(**my_tag)
Estudo de caso: Refatorando Strategy¶
O Padrão Strategy: Define uma familia de algoritimos, encapsula cada um e torna-os intercambiábeis.
Exemplo:
Aplicação de descontos em pedidos com atributos diferentes ou da inpeção dos itens comprados.
Considere:
- Clientes com mil ou mais pontos no program de fidelidade obtêm um desconto global de 5% sobre o pedido.
- Um desconto de 10% é aplicado a cada item com 20 ou mais unidades no mesmo pedido.
- Pedidos com pelo menos dez itens diferentes recebem um desconto global de 7%.
Customer = namedtuple('Customer', 'name fidelity')
class LineItem:
def __init__(self, product, quantity, price):
self.product = product
self.quantity = quantity
self.price = price
def total(self):
return self.price * self.quantity
class Order: # the Context
def __init__(self, customer, cart, promotion=None):
self.customer = customer
self.cart = list(cart)
self.promotion = promotion
def total(self):
if not hasattr(self, '__total'):
self.__total = sum(item.total() for item in self.cart)
return self.__total
def due(self):
if self.promotion is None:
discount = 0
else:
discount = self.promotion.discount(self)
return self.total() - discount
def __repr__(self):
fmt = '<Order total: {:.2f} due: {:.2f}>'
return fmt.format(self.total(), self.due())
class Promotion(ABC): # the Strategy: an Abstract Base Class
@abstractmethod
def discount(self, order):
"""Return discount as a positive dollar amount"""
class FidelityPromo(Promotion): # first Concrete Strategy
"""5% discount for customers with 1000 or more fidelity points"""
def discount(self, order):
return order.total() * .05 if order.customer.fidelity >= 1000 else 0
class BulkItemPromo(Promotion): # second Concrete Strategy
"""10% discount for each LineItem with 20 or more units"""
def discount(self, order):
discount = 0
for item in order.cart:
if item.quantity >= 20:
discount += item.total() * .1
return discount
class LargeOrderPromo(Promotion): # third Concrete Strategy
"""7% discount for orders with 10 or more distinct items"""
def discount(self, order):
distinct_items = {item.product for item in order.cart}
if len(distinct_items) >= 10:
return order.total() * .07
return 0
>>> joe = Customer('John Doe', 0) # <1>
>>> ann = Customer('Ann Smith', 1100)
>>> cart = [LineItem('banana', 4, .5), # <2>
... LineItem('apple', 10, 1.5),
... LineItem('watermellon', 5, 5.0)]
>>> Order(joe, cart, FidelityPromo()) # <3>
<Order total: 42.00 due: 42.00>
>>> Order(ann, cart, FidelityPromo()) # <4>
<Order total: 42.00 due: 39.90>
>>> banana_cart = [LineItem('banana', 30, .5), # <5>
... LineItem('apple', 10, 1.5)]
>>> Order(joe, banana_cart, BulkItemPromo()) # <6>
<Order total: 30.00 due: 28.50>
>>> long_order = [LineItem(str(item_code), 1, 1.0) # <7>
... for item_code in range(10)]
>>> Order(joe, long_order, LargeOrderPromo()) # <8>
<Order total: 10.00 due: 9.30>
>>> Order(joe, cart, LargeOrderPromo())
<Order total: 42.00 due: 42.00>
Com funções¶
class LineItem:
...
class Order: # the Context
...
def fidelity_promo(order):
"""5% discount for customers with 1000 or more fidelity points"""
return order.total() * .05 if order.customer.fidelity >= 1000 else 0
def bulk_item_promo(order):
"""10% discount for each LineItem with 20 or more units"""
discount = 0
for item in order.cart:
if item.quantity >= 20:
discount += item.total() * .1
return discount
def large_order_promo(order):
"""7% discount for orders with 10 or more distinct items"""
distinct_items = {item.product for item in order.cart}
if len(distinct_items) >= 10:
return order.total() * .07
return 0
promos = [fidelity_promo, bulk_item_promo, large_order_promo] # <1>
def best_promo(order): # <2>
"""Select best discount available """
return max(promo(order) for promo in promos) # <3>
Decorators¶
promos = [] # <1>
def promotion(promo_func): # <2>
promos.append(promo_func)
return promo_func
@promotion # <3>
def fidelity(order):
"""5% discount for customers with 1000 or more fidelity points"""
return order.total() * .05 if order.customer.fidelity >= 1000 else 0
@promotion
def bulk_item(order):
"""10% discount for each LineItem with 20 or more units"""
discount = 0
for item in order.cart:
if item.quantity >= 20:
discount += item.total() * .1
return discount
@promotion
def large_order(order):
"""7% discount for orders with 10 or more distinct items"""
distinct_items = {item.product for item in order.cart}
if len(distinct_items) >= 10:
return order.total() * .07
return 0
def best_promo(order): # <4>
return max(promo(order) for promo in promos)
Closure¶
def make_averager():
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return total/len(series)
return averager
>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0
Herança¶
class Parent(object):
def override(self):
print "PARENT override()"
class Child(Parent):
def __init__(self, *args, **kwargs):
"""
O super é realmente estranho pra quem o vê da primeira vez, mas
ele é 'super' tranquilo de entender ;)
Quando você herda de uma classe e sobrescreve um determinado método
(nesse caso o nosso construtor), é interessante que você mantenha
o comportamento original da classe pai (Parent)
"""
super(Child).__init__(*args, **kwargs)
def override(self):
print "CHILD override()"
dad = Parent()
son = Child()
dad.override()
son.override()
>>> PARENT override()
>>> CHILD override()