Objetos Pythonicos
Orientação a objetos e padrões de projeto em Python Luciano Ramalho
luciano@ramalho.org
setembro/2012
Aula 4
• Recapitulando iteráveis etc.
• Herança múltipla, MRO e super
• Propriedades
• Polimorfismo
Objetivos desta aula
• Rever iteradores, geradores etc.
• Corrigir exercício sobre herança
• Entender a MRO (method resolution order) e a função super
• Apresentar encapsulamento, atributos protegidos e propriedades
• Apresentar polimorfismo
@ramalhoorg
Exemplos de iteração
• Iteração em Python não se limita a tipos primitivos
• Exemplos
• string
• arquivo
• Django QuerySet
• Baralho (em: “OO em Python sem Sotaque”)
https://slideshare.net/ramalho/
@ramalhoorg
Em Python, um iterável é...
• Um objeto a partir do qual a função iter consegue obter um iterador.
• A chamada iter(x):
• invoca x.__iter__() para obter um iterador
• ou, se x.__iter__ não existe:
• fabrica um iterador que acessa os itens de x sequenciamente fazendo x[0], x[1], x[2] etc.
protocolo de sequência
interface Iterable
@ramalhoorg class Trem(object):
def __init__(self, num_vagoes):
self.num_vagoes = num_vagoes def __len__(self):
return self.num_vagoes def __getitem__(self, pos):
indice = pos if pos >= 0 else self.num_vagoes + pos
if 0 <= indice < self.num_vagoes: # indice 2 -> vagao #3 return 'vagao #%s' % (indice+1)
else:
raise IndexError('vagao inexistente %s' % pos)
Protocolo
de sequência
• implementação “informal” da interface
@ramalhoorg from collections import Sequence
class Trem(Sequence):
def __init__(self, num_vagoes):
self.num_vagoes = num_vagoes def __len__(self):
return self.num_vagoes def __getitem__(self, pos):
indice = pos if pos >= 0 else self.num_vagoes + pos
if 0 <= indice < self.num_vagoes: # indice 2 -> vagao #3 return 'vagao #%s' % (indice+1)
else:
raise IndexError('vagao inexistente %s' % pos)
Interface Sequence
• collections.Sequence
@ramalhoorg
Herança de Sequence
>>> t = Trem(4)
>>> 'vagao #2' in t True
>>> 'vagao #5' in t False
>>> for i in reversed(t): print i ...
vagao #4 vagao #3 vagao #2 vagao #1
>>> t.index('vagao #2') 1
>>> t.index('vagao #7')
Traceback (most recent call last):
...
ValueError
from collections import Sequence class Trem(Sequence):
def __init__(self, num_vagoes):
self.num_vagoes = num_vagoes def __len__(self):
return self.num_vagoes def __getitem__(self, pos):
indice = pos if pos >= 0 else self.num_vagoes + pos
if 0 <= indice < self.num_vagoes: # indice 2 -> vagao #3 return 'vagao #%s' % (indice+1)
else:
raise IndexError('vagao inexistente %s' % pos)
@ramalhoorg
Interface Iterable
• Iterable provê um método __iter__
• O método __iter__ devolve
uma instância de Iterator
@ramalhoorg
O padrão
Iterator permite acessar os itens
de uma coleção sequencialmente, isolando o cliente da implementação da coleção.
Head First
Design Patterns Poster
O'Reilly,
ISBN 0-596-10214-3
@ramalhoorg
• for vagao in t:
• invoca iter(t)
• devolve IteradorTrem
• invoca itrem.next() até que ele levante StopIteration
class Trem(object):
def __init__(self, num_vagoes):
self.num_vagoes = num_vagoes def __iter__(self):
return IteradorTrem(self.num_vagoes) class IteradorTrem(object):
def __init__(self, num_vagoes):
self.atual = 0
self.ultimo_vagao = num_vagoes - 1 def next(self):
if self.atual <= self.ultimo_vagao:
self.atual += 1
return 'vagao #%s' % (self.atual) else:
raise StopIteration()
Trem com
iterator
>>> t = Trem(4)
>>> for vagao in t:
... print(vagao) vagao #1
vagao #2 vagao #3 vagao #4
iter(t)
@ramalhoorg
• for vagao in t:
• invoca iter(t)
• devolve gerador
• invoca gerador.next() até que ele levante StopIteration
class Trem(object):
def __init__(self, num_vagoes):
self.num_vagoes = num_vagoes def __iter__(self):
for i in range(self.num_vagoes):
yield 'vagao #%s' % (i+1)
Trem c/ função geradora
>>> t = Trem(4)
>>> for vagao in t:
... print(vagao) vagao #1
vagao #2 vagao #3 vagao #4
iter(t)
class Trem(object):
def __init__(self, num_vagoes):
self.num_vagoes = num_vagoes def __iter__(self):
return IteradorTrem(self.num_vagoes) class IteradorTrem(object):
def __init__(self, num_vagoes):
self.atual = 0
self.ultimo_vagao = num_vagoes - 1 def next(self):
if self.atual <= self.ultimo_vagao:
self.atual += 1
return 'vagao #%s' % (self.atual) else:
raise StopIteration()
class Trem(object):
def __init__(self, num_vagoes):
self.num_vagoes = num_vagoes def __iter__(self):
for i in range(self.num_vagoes):
yield 'vagao #%s' % (i+1)
Função
geradora Iterador clássico
12 linhas de código
3 linhas
mesma funcionalidade e desempenho!
@ramalhoorg
• for vagao in t:
• invoca iter(t)
• devolve gerador
• invoca gerador.next() até que ele levante StopIteration
class Trem(object):
def __init__(self, num_vagoes):
self.num_vagoes = num_vagoes def __iter__(self):
return ('vagao #%s' % (i+1)
for i in range(self.num_vagoes))
Trem c/ expressão geradora
>>> t = Trem(4)
>>> for vagao in t:
... print(vagao) vagao #1
vagao #2 vagao #3 vagao #4
iter(t)
@ramalhoorg
• geradores (potencialmente) infinitos
• count(), cycle(), repeat()
• geradores que combinam vários iteráveis
• chain(), tee(), izip(), imap(), product(), compress()...
• geradores que selecionam ou agrupam itens:
• compress(), dropwhile(), groupby(), ifilter(), islice()...
• Iteradores que produzem combinações
• product(), permutations(), combinations()...
Módulo itertools demonstração...
@ramalhoorg
Exemplo prático de função geradora
• Funções geradoras para desacoplar laços de leitura e escrita em uma ferramenta para
conversão de bases de dados semi-estruturadas
https://github.com/ramalho/isis2json
@ramalhoorg
• geradores (potencialmente) infinitos
• count(), cycle(), repeat()
• geradores que combinam vários iteráveis
• chain(), tee(), izip(), imap(), product(), compress()...
• geradores que selecionam ou agrupam itens:
• compress(), dropwhile(), groupby(), ifilter(), islice()...
• Iteradores que produzem combinações
• product(), permutations(), combinations()...
Módulo itertools demonstração...
@ramalhoorg
Solução do
exercício 1.5
• A classe ContadorTotalizadorAmigavel não precisa implementar qualquer método
• nem mesmo __init__
MRO
>>> Contador.__mro__
(<class '__main__.Contador'>, <type 'object'>)
>>> ContadorAmigavel.__mro__
(<class '__main__.ContadorAmigavel'>, <class '__main__.Contador'>,
<type 'object'>)
>>> ContadorTotalizadorAmigavel.__mro__
(<class '__main__.ContadorTotalizadorAmigavel'>, <class '__main__.ContadorTotalizador'>,
<class '__main__.ContadorAmigavel'>, <class '__main__.Contador'>,
<type 'object'>)
• method resolution order
@ramalhoorg
Solução alternativa
• Herança simples
@ramalhoorg
Solução alternativa
• Herança simples
• Evitar o losango (diamond)
@ramalhoorg
O losango não é
necessariamente ruim
• Em Python ele sempre está presente quando se usa herança múltipla
• as classes comuns (new style) herdam de object
• Herança múltipla deve ser usada com moderação
• classes mixin são uma forma segura
• contribuem métodos e campos sem
sobrescrever outros atributos
@ramalhoorg
Invocar método de superclasse
• A forma mais simples
class ContadorTotalizador(Contador):
def __init__(self):
Contador.__init__(self) self.total = 0
def incluir(self, item):
Contador.incluir(self, item) self.total += 1
@ramalhoorg
Invocar método de superclasse
• A forma mais correta
• utiliza a MRO automaticamente
class ContadorTotalizador(Contador):
def __init__(self):
super(ContadorTotalizador, self).__init__() self.total = 0
def incluir(self, item):
super(ContadorTotalizador, self).incluir(item) self.total += 1
@ramalhoorg
• Desnessário e...
Abuso de getters/
setters
não
pythonico!
@ramalhoorg
Getters/setters
• Necessários quando se usa campos privados mas é desejável oferecer acesso controlado a esses campos (encapsulamento)
• para leitura: definir método getter
• para escrita: definir método setter
• Getters e setters que não implementam lógica são
questionáveis em geral e desnecessários em Python
Atributos protegidos
>>> class C(object):
... def __init__(self, idade):
... self.__idade = idade ... >>> o = C(20)
>>> o.__idade
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'C' object has no attribute 'idade'
>>> dir(o)
['_C__idade', '__class__', '__delattr__', '__dict__'...]
>>> o._C__idade 20
Sintaxe:
__atributo
(dois _ _ à esquerda, nenhum à direita)
“name mangling”: desfiguração do nome
Controle de acesso a atributos em Python
• Não existem modificadores de acesso (private, protected etc.)
• Todos os atributos são públicos
• A convenção _x é para o programador (o interpretador ignora)
• A sintaxe __x (dois _ _) tem o efeito de criar
atributos protegidos contra sobrescrita acidental
Atributos protegidos
• Filosofia dos atributos protegidos em Python:
• Salvaguarda (safety)
e não segurança (security)
• Evita acesso acidental
• Não evita acesso intencional
Propriedades
• Atributos que podem ser acessados como se
fossem campos, mas acionam métodos de modo transparente
• sintaxe de acesso: o.x
• e não o.x()
• Isso permite definir campos públicos inicialmente, sem precisar definir getters e setters que não
fazem nada e depois implementar properties
(se necessário)
Propriedades
• Encapsulamento para quem precisa de encapsulamento
>>> a = C()
>>> a.x = 10
>>> print a.x 10 >>> a.x = -10
>>> print a.x 0
violação de encapsulamento?
pergunte-me como!
Propriedade:
implementação
• Apenas para leitura, via decorator:
class C(object):
def __init__(self, x):
self.__x = x @property
def x(self):
return self.__x
atributo
protegido
decorator
Propriedade:
implementação
• Leitura
e escrita
class C(object):
def __init__(self, x=0):
self.__x = x @property
def x(self):
return self.__x @x.setter
def x(self, valor):
if valor >= 0:
self.__x = valor else:
self.__x = 0
Propriedade:
exemplo de uso
class ContadorTotalizador(Contador):
def __init__(self):
super(ContadorTotalizador, self).__init__() self.__total = 0
def incluir(self, item):
super(ContadorTotalizador, self).incluir(item) self.__total += 1
@property
def total(self):
return self.__total