Post on 15-Nov-2018
Objetos PythonicosOrientação a objetos e padrões de projeto em Python
Luciano Ramalholuciano@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 tTrue>>> 'vagao #5' in tFalse>>> for i in reversed(t): print i... vagao #4vagao #3vagao #2vagao #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
InterfaceIterable
• 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 PosterO'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()
Tremcomiterator
>>> t = Trem(4)>>> for vagao in t:... print(vagao)vagao #1vagao #2vagao #3vagao #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 #1vagao #2vagao #3vagao #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çãogeradora
Iteradorclássico
12 linhasde 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 #1vagao #2vagao #3vagao #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 doexercí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 automaticamenteclass 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árioe...
Abuso degetters/setters
nãopythonico!
@ramalhoorg
Getters/setters
• Necessários quando se usa campos privadosmas é desejável oferecer acesso controladoa 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.__idadeTraceback (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__idade20
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.x10>>> a.x = -10>>> print a.x0
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
• Leiturae 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 usoclass 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
Polimorfismo: definição
O conceito de “polimorfismo” significa que podemos tratar instâncias de diferentes classes da mesma forma.
Assim, podemos enviar uma mensagem a um objeto sem saber de antemão qual é o seu tipo, e o objeto ainda assim fará “a coisa certa”, ao menos do pontode dele.
Scott AmblerThe Object Primer, 2nd ed. - p. 173
Exemplo de polimorfismo
• A classe Baralho como sequência
• Live-coding com monkey-patching
• programação ao vivo com modificação de classe em tempo de execução