Luciano Ramalho [email protected] setembro/2012 Objetos Pythonicos Orientação a objetos e padrões de projeto em Python 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 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. interface Iterable • 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 @ramalhoorg Protocolo de sequência • implementação “informal” da interface 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: @ramalhoorg raise IndexError('vagao inexistente %s' % pos) Interface Sequence • collections.Sequence 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: @ramalhoorg raise IndexError('vagao inexistente %s' % pos) 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 Seque class Trem(Sequence): def __init__(self, num_va self.num_vagoes = num def __len__(self): return self.num_vagoe def __getitem__(self, pos @ramalhoorg indice = pos if pos > 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 Trem com iterator 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() • >>> t = Trem(4) >>> for vagao in t: ... print(vagao) vagao #1 vagao #2 vagao #3 vagao #4 for vagao in t: • • invoca iter(t) • devolve IteradorTrem invoca itrem.next() até que ele levante StopIteration @ramalhoorg Trem c/ função geradora iter(t) 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) • >>> t = Trem(4) >>> for vagao in t: ... print(vagao) vagao #1 vagao #2 vagao #3 vagao #4 for vagao in t: • • invoca iter(t) • devolve gerador invoca gerador.next() até que ele levante StopIteration @ramalhoorg Iterador clássico 12 linhas de código Função geradora 3 linhas 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() mesma funcionalidade e desempenho! 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/ expressão geradora iter(t) 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)) • >>> t = Trem(4) >>> for vagao in t: ... print(vagao) vagao #1 vagao #2 vagao #3 vagao #4 for vagao in t: • • invoca iter(t) • devolve gerador invoca gerador.next() até que ele levante StopIteration @ramalhoorg Módulo itertools demonstração... • 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()... @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 Módulo itertools demonstração... • 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()... @ramalhoorg Solução do exercício 1.5 • A classe ContadorTotalizadorAmigavel não precisa implementar qualquer método • nem mesmo __init__ @ramalhoorg MRO • method resolution order >>> 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'>) Solução alternativa • Herança simples @ramalhoorg Solução alternativa • Evitar o losango (diamond) • Herança simples @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 Abuso de getters/ setters • Desnessário e... 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 @ramalhoorg Atributos protegidos Sintaxe: __atributo (dois _ _ à esquerda, nenhum à direita) >>> 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 “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 >>> >>> >>> 10 >>> >>> 0 a = C() a.x = 10 print a.x a.x = -10 print a.x violação de encapsulamento? pergunte-me como! Propriedade: implementação • Apenas para leitura, via decorator: atributo protegido decorator class C(object): def __init__(self, x): self.__x = x @property def x(self): return self.__x 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 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 ponto de dele. Scott Ambler The 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