Orientação a objetos e frameworks
Objetivo deste módulo
• Apresentar conceitos fundamentais de:
• orientação a objetos
• organização de programas em vários módulos
• Explicar conceitos a partir do nível básico para:
• programadores que nunca usaram OO
• programadores que já usaram OO em outras linguagens
Orientação a objetos:
a origem
• Linguagem Simula 1967
• Noruega: Ole-Johan Dahl e Kristen Nygaard
• objetos, classes, sub-classes
• métodos virtuais (funções associadas a objetos específicos em tempo de execução)
Orientação a objetos:
evolução
• Smalltalk 1980
• EUA, Xerox PARC (Palo Alto Research Center):
Alan Kay, Dan Ingalls, Adele Goldberg et. al.
• terminologia:
• “Object oriented programming”
• “message passing”, “late binding” (a idéia por trás de métodos virtuais)
Smalltalk 1980
Squeak: Smalltalk livre
Conceito: “objeto”
• Um componente de software que inclui dados (atributos) e comportamentos (métodos)
• Em geral, os atributos são
manipulados pelos métodos do próprio objeto (encapsulamento)
Figuras: bycicle (bicicleta), The Java Tutorial
http://docs.oracle.com/javase/tutorial/java/concepts/object.html
Exemplo: um objeto dict
>>> d = {'AM':'Manaus', 'PE':'Recife', 'PR': 'Curitiba'}
>>> d.keys()
['PR', 'AM', 'PE']
>>> d.get('PE') 'Recife'
>>> d.pop('PR') 'Curitiba'
>>> d
{'AM': 'Manaus', 'PE': 'Recife'}
>>> len(d) 2
>>> d.__len__() 2
• Métodos: keys, get, pop, __len__ etc.
Exemplo: um objeto dict
>>> d
{'AM': 'Manaus', 'PE': 'Recife'}
>>> d['AM']
'Manaus'
>>> d.__getitem__('AM') 'Manaus'
>>> d['MG'] = 'Belo Horizonte'
>>> d.__setitem__('RJ', 'Rio de Janeiro')
>>> d
{'MG': 'Belo Horizonte', 'AM': 'Manaus', 'RJ': 'Rio de Janeiro', 'PE': 'Recife'}
• Sobrecarga de operadores:
• [ ]:
• __getitem__
• __setitem__
Exemplo: um objeto dict
>>> d.__class__
<type 'dict'>
>>> print d.__doc__
dict() -> new empty dictionary.
dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs.
dict(seq) -> new dictionary initialized as if via:
d = {}
for k, v in seq:
d[k] = v
dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
• Atributos de dados: __class__, __doc__
Exemplo: um objeto dict
>>> dir(d)
['__class__', '__cmp__', '__contains__', '__delattr__', '__delitem__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__',
'__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__',
'__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'has_key', 'items', 'iteritems', 'iterkeys', 'itervalues', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
• Em Python, métodos também são atributos
Exemplo:
um objeto Tkinter.Label
import Tkinter
from time import strftime
relogio = Tkinter.Label() relogio.pack()
relogio['font'] = 'Helvetica 120 bold' relogio['text'] = strftime('%H:%M:%S') def tictac():
agora = strftime('%H:%M:%S') if agora != relogio['text']:
relogio['text'] = agora relogio.after(100, tictac) tictac()
relogio.mainloop()
Note:
Em Tkinter, atributos de
dados são acessados via [ ]:
__getitem__ e __setitem__
Objetos em linguagens
• Existem linguagens “baseadas em objetos” e linguagens “orientadas a objetos”
• baseadas em objetos: permitem que o programador use os tipos de objetos
fornecidos, mas não permitem que ele crie seus próprios tipos de objetos
• Ex. Visual Basic antes da era .net
Objetos em Python
• Tudo é objeto: não existem “tipos primitivos”
• desde Python 2.2, dezembro de 2001
>>> 5 + 3 8
>>> 5 .__add__(3) 8
>>> type(5)
<type 'int'>
Funções são objetos
>>> def fatorial(n):
... '''devolve n!'''
... return 1 if n < 2 else n * fatorial(n-1) ...
>>> fatorial(5) 120
>>> fat = fatorial
>>> fat
<function fatorial at 0x1004b5f50>
>>> fat(42)
1405006117752879898543142606244511569936384000000000L
>>> fatorial.__doc__
'devolve n!'
>>> fatorial.__name__
'fatorial'
>>> fatorial.__code__
<code object fatorial at 0x1004b84e0, file "<stdin>", line 1>
>>> fatorial.__code__.co_varnames ('n',)
Funções são objetos
>>> fatorial.__code__.co_code
'|\x00\x00d\x01\x00j\x00\x00o\x05\x00\x01d\x02\x00S\x01|\x00\x00t\x00\x00|
\x00\x00d\x02\x00\x18\x83\x01\x00\x14S'
>>> from dis import dis
>>> dis(fatorial.__code__.co_code)
0 LOAD_FAST 0 (0) 3 LOAD_CONST 1 (1) 6 COMPARE_OP 0 (<)
9 JUMP_IF_FALSE 5 (to 17) 12 POP_TOP
13 LOAD_CONST 2 (2) 16 RETURN_VALUE
>> 17 POP_TOP
18 LOAD_FAST 0 (0) 21 LOAD_GLOBAL 0 (0) 24 LOAD_FAST 0 (0) 27 LOAD_CONST 2 (2) 30 BINARY_SUBTRACT
31 CALL_FUNCTION 1 34 BINARY_MULTIPLY
35 RETURN_VALUE
>>>
Bytecode da função fatorial
Objetos têm tipo
• Tipagem forte: normalmente, Python não faz conversão automática entre tipos
>>> a = 10
>>> b = '9'
>>> a + b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
>>> a + int(b) 19
>>> str(a) + b '109'
>>> 77 * None
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for *: 'int' and 'NoneType'
>>>
Tipagem dinâmica:
variáveis não têm tipo
>>> def dobro(x):
... return x * 2 ...
>>> dobro(7) 14
>>> dobro(7.1) 14.2
>>> dobro('bom') 'bombom'
>>> dobro([10, 20, 30]) [10, 20, 30, 10, 20, 30]
>>> dobro(None)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in dobro
TypeError: unsupported operand type(s) for *: 'NoneType' and 'int'
Duck typing
• “Se voa como um pato, nada como um pato e grasna como um pato, é um pato.”
• Tipagem dinâmica permite duck typing (tipagem pato) estilo de programação que evita verificar os tipos dos objetos, mas apenas seus métodos
• No exemplo anterior, a função dobro funciona com qualquer objeto x que consiga fazer x * 2
Tipagem forte x fraca
• Tipagem forte x fraca refere-se a
conversão automática de valores de tipos diferente.
• Linguagens de tipagem fraca são muito liberais na mistura entre tipos, e isso é uma fonte de bugs.
> 0 == '0' true
> 0 == '' true
> '0' == '' false
Veja alguns resultados estranhos obtidos com JavaScript, que tem tipagem fraca.
Em Python as três expressões acima geram TypeError, e as três últimas resultam False.
Python tem tipagem forte.
> 10 + '9' '109'
> 10 + '9' * 1 19
> '10' + 9 * 1 '109'
JavaScript
(ECMAScript 5) em Node.js 0.6
Tipagem forte x fraca, dinâmica x estática
• Tipagem forte x fraca refere-se a conversão automática de valores de tipos diferentes
• Tipagem dinâmica x estática refere-se à declaração dos tipos das variáveis, parâmetros formais e
valores devolvidos pelas funções
• Linguagens de tipagem estática exigem a
declaração dos tipos, ou usam inferência de tipos para garantir que uma variável será associada a
somente a valores de um tipo
Tipagem em linguagens
Smalltalk dinâmica forte Python dinâmica forte Ruby dinâmica forte C (K&R) estática fraca C (ANSI) estática forte
Java estática forte C# estática forte JavaScript dinâmica fraca
PHP dinâmica fraca } combinaçãoperigosa:bugs sorrateiros
Conversões automáticas
• Python faz algumas (poucas) conversões automáticas entre tipos:
• Promoção de int para float
• Promoção de str para unicode
• assume o encoding padrão: ASCII por default
>>> 6 * 7.0 42.0
>>> 'Spam, ' + u'eggs' u'Spam, eggs'
>>>
Funções para
inspecionar objetos
• type(obj): devolve o tipo do objeto
• o tipo é uma classe
• dir(obj): devolve os nomes dos atributos do objeto
• métodos e atributos de dados
• devolve uma lista útil para uso interativo, mas não necessariamente completa
Objetos podem
receber novos atributos
• Em geral, é possível atribuir valores a atributos não pré-definidos, em tempo de execução.
• Exceções: tipos embutidos, tipos com __slots__
>>> fatorial
<function fatorial at 0x1004b5f50>
>>> fatorial._autor = 'Fulano de Tal'
>>> fatorial._autor 'Fulano de Tal'
>>> s = 'sapo'
>>> s.nome = 'Joca'
Traceback (most recent call last):
...
AttributeError: 'str' object has no attribute 'nome'
Acesso dinâmico a atributos
• getattr(obj, nome_do_atributo): acessar
• hasattr(obj, nome_do_atributo): testar
• setattr(obj, nome_do_atributo, valor): criar ou alterar
• delattr(obj, nome_do_atributo): remover
Acesso dinâmico
>>> dobro
<function dobro at 0x1004412a8>
>>> dobro.__doc__
'devolve duas vezes x'
>>> getattr(dobro, '__doc__') 'devolve duas vezes x'
>>> hasattr(dobro, 'peso') False
>>> setattr(dobro, '_inspecao', '2012-01-31')
>>> dobro._inspecao '2012-01-31'
>>> del dobro._inspecao
>>> hasattr(dobro, '_inspecao') False
Acesso dinâmico
>>> t = (1,)
>>> type(t)
<type 'tuple'>
>>> for nome_atr in dir(t):
... print nome_atr.ljust(16), getattr(t, nome_atr) ...
__add__ <method-wrapper '__add__' of tuple object at...
__class__ <type 'tuple'>
__contains__ <method-wrapper '__contains__' of tuple object at...
__delattr__ <method-wrapper '__delattr__' of tuple object at...
__doc__ tuple() -> empty tuple
tuple(iterable) -> tuple initialized from iterable's items
If the argument is a tuple, the return value is the same object.
__eq__ <method-wrapper '__eq__' of tuple object at...
__format__ <built-in method __format__ of tuple object at...
[...]
__len__ <method-wrapper '__len__' of tuple object at...
[...]
__str__ <method-wrapper '__str__' of tuple object at...
__subclasshook__ <built-in method __subclasshook__ of type object at...
count <built-in method count of tuple object at...
index <built-in method index of tuple object at...
Conceito: “classe”
• Uma categoria, ou tipo, de objeto
• Uma idéia abstrata, uma forma platônica
• Exemplo: classe “Cão”:
• Eu digo: “Ontem eu comprei um cão”
• Você não sabe exatamente qual cão, mas sabe:
• é um mamífero, quadrúpede, carnívoro
• pode ser domesticado (normalmente)
• cabe em um automóvel
Exemplar de cão:
instância da classe Cao
>>> rex = Cao() instanciação
Classe Cao
modulo2.git/cao.py
instanciação
class Mamifero(object):
"""lição de casa: implementar"""
class Cao(Mamifero):
qt_patas = 4
carnivoro = True nervoso = False
def __init__(self, nome):
self.nome = nome
def latir(self, vezes=1):
# quando nervoso, late o dobro
vezes = vezes + (self.nervoso * vezes) print self.nome + ':' + ' Au!' * vezes def __str__(self):
return self.nome def __repr__(self):
return 'Cao({0!r})'.format(self.nome)
>>> rex = Cao('Rex')
>>> rex
Cao('Rex')
>>> print rex Rex
>>> rex.qt_patas 4
>>> rex.latir() Rex: Au!
>>> rex.latir(2) Rex: Au! Au!
>>> rex.nervoso = True
>>> rex.latir(3)
Rex: Au! Au! Au! Au! Au! Au!
Classe Cao em Python
class Mamifero(object):
"""lição de casa: implementar"""
class Cao(Mamifero):
qt_patas = 4
carnivoro = True nervoso = False
def __init__(self, nome):
self.nome = nome
def latir(self, vezes=1):
# quando nervoso, late o dobro
vezes = vezes + (self.nervoso * vezes) print self.nome + ':' + ' Au!' * vezes def __str__(self):
return self.nome def __repr__(self):
return 'Cao({0!r})'.format(self.nome)
modulo2.git/cao.py
• atributos de dados na classe funcionam
como valores default para os atributos das instâncas
• __init__ é o
construtor, ou melhor, o inicializador
Classe Cao em Python
class Mamifero(object):
"""lição de casa: implementar"""
class Cao(Mamifero):
qt_patas = 4
carnivoro = True nervoso = False
def __init__(self, nome):
self.nome = nome
def latir(self, vezes=1):
# quando nervoso, late o dobro
vezes = vezes + (self.nervoso * vezes) print self.nome + ':' + ' Au!' * vezes def __str__(self):
return self.nome def __repr__(self):
return 'Cao({0!r})'.format(self.nome)
modulo2.git/cao.py
• self é o 1º parâmetro em todos os métodos de instância
• atributos da instância só podem ser
acessados via self
Classe Cao
modulo2.git/cao.py
class Mamifero(object):
"""lição de casa: implementar"""
class Cao(Mamifero):
qt_patas = 4
carnivoro = True nervoso = False
def __init__(self, nome):
self.nome = nome
def latir(self, vezes=1):
# quando nervoso, late o dobro
vezes = vezes + (self.nervoso * vezes) print self.nome + ':' + ' Au!' * vezes def __str__(self):
return self.nome def __repr__(self):
return 'Cao({0!r})'.format(self.nome)
>>> rex = Cao('Rex')
>>> rex
Cao('Rex')
>>> print rex Rex
>>> rex.qt_patas 4
>>> rex.latir() Rex: Au!
>>> rex.latir(2) Rex: Au! Au!
• na invocação do
método, a instância é passada implicitamente na posição do self
invocação
Mamifero: superclasse
de Cao diagrama de classeUML
modulo2.git/cao.py
generalização
class Mamifero(object):
"""lição de casa: implementar"""
class Cao(Mamifero):
qt_patas = 4
carnivoro = True nervoso = False
def __init__(self, nome):
self.nome = nome
def latir(self, vezes=1):
# quando nervoso, late o dobro
vezes = vezes + (self.nervoso * vezes) print self.nome + ':' + ' Au!' * vezes def __str__(self):
return self.nome def __repr__(self):
return 'Cao({0!r})'.format(self.nome)
Subclasses de Cao
class Pequines(Cao):
nervoso = True
class Mastiff(Cao):
def latir(self, vezes=1):
# o mastiff não muda seu latido quando nervoso print self.nome + ':' + ' Wuff!' * vezes
class SaoBernardo(Cao):
def __init__(self, nome):
Cao.__init__(self, nome) self.doses = 10
def servir(self):
if self.doses == 0:
raise ValueError('Acabou o conhaque!') self.doses -= 1
msg = '{0} serve o conhaque (restam {1} doses)' print msg.format(self.nome, self.doses)
Diz a lenda que o cão São Bernardo leva um
pequeno barril de conhaque para resgatar viajantes
perdidos na neve.
• Continuação de cao.py
Subclasses de Cao
class Pequines(Cao):
nervoso = True
class Mastiff(Cao):
def latir(self, vezes=1):
# o mastiff não muda seu latido quando nervoso print self.nome + ':' + ' Wuff!' * vezes
class SaoBernardo(Cao):
def __init__(self, nome):
Cao.__init__(self, nome) self.doses = 10
def servir(self):
if self.doses == 0:
raise ValueError('Acabou o conhaque!') self.doses -= 1
msg = '{0} serve o conhaque (restam {1} doses)' print msg.format(self.nome, self.doses)
>>> sansao = SaoBernardo('Sansao')
>>> sansao.servir()
Sansao serve o conhaque (restam 9 doses)
>>> sansao.doses = 1
>>> sansao.servir()
Sansao serve o conhaque (restam 0 doses)
>>> sansao.servir()
Traceback (most recent call last):
...
ValueError: Acabou o conhaque!
Subclasses de Cao
class Pequines(Cao):
nervoso = True
class Mastiff(Cao):
def latir(self, vezes=1):
# o mastiff não muda seu latido quando nervoso print self.nome + ':' + ' Wuff!' * vezes
class SaoBernardo(Cao):
def __init__(self, nome):
Cao.__init__(self, nome) self.doses = 10
def servir(self):
if self.doses == 0:
raise ValueError('Acabou o conhaque!') self.doses -= 1
msg = '{0} serve o conhaque (restam {1} doses)' print msg.format(self.nome, self.doses)
• Continuação de cao.py
Relógio com classe
import Tkinter
from time import strftime
class Relogio(Tkinter.Label):
def __init__(self):
Tkinter.Label.__init__(self) self.pack()
self['text'] = strftime('%H:%M:%S') self['font'] = 'Helvetica 120 bold' self.tictac()
def tictac(self):
agora = strftime('%H:%M:%S') if agora != self['text']:
self['text'] = agora
self.after(100, self.tictac) rel = Relogio()
rel.mainloop()
modulo2.git/relogio_oo.py
Uma pequena parte da
hierarquia de classes do
Tkinter
herança
múltipla
herança múltipla
mixin
Um pouco mais da hierarquia de classes do Tkinter
Hierarquia de classes dos objetos gráficos doTkinter
Herança múltipla
class Cao(Mamifero):
qt_patas = 4
carnivoro = True nervoso = False
def __init__(self, nome):
self.nome = nome
def latir(self, vezes=1):
# quando nervoso, late o dobro
vezes = vezes + (self.nervoso * vezes) print self.nome + ':' + ' Au!' * vezes def __str__(self):
return self.nome def __repr__(self):
return 'Cao({0!r})'.format(self.nome)
class Grande(object):
""" Mixin: muda o latido"""
def latir(self, vezes=1):
# faz de conta que cães grandes não mudam # seu latido quando nervosos
print self.nome + ':' + ' Wuff!' * vezes
class Mastiff(Grande, Cao):
""" O mastiff é o maior cão que existe """
class SaoBernardo(Grande, Cao):
def __init__(self, nome):
Cao.__init__(self, nome) self.doses = 10
def servir(self):
if self.doses == 0:
raise ValueError('Acabou o conhaque!') self.doses -= 1
msg = '{0} serve o conhaque (restam {1} doses)' print msg.format(self.nome, self.doses)
modulo2.git/cao2.py
herança
múltipla
herança múltipla mixin
• Refatoração de cao.py
para cao2.py
• Reutilizar o latido do
mastiff em outros cães grandes
Herança múltipla
class Cao(Mamifero):
qt_patas = 4
carnivoro = True nervoso = False
def __init__(self, nome):
self.nome = nome
def latir(self, vezes=1):
# quando nervoso, late o dobro
vezes = vezes + (self.nervoso * vezes) print self.nome + ':' + ' Au!' * vezes def __str__(self):
return self.nome def __repr__(self):
return 'Cao({0!r})'.format(self.nome)
class Grande(object):
""" Mixin: muda o latido"""
def latir(self, vezes=1):
# faz de conta que cães grandes não mudam # seu latido quando nervosos
print self.nome + ':' + ' Wuff!' * vezes
class Mastiff(Grande, Cao):
""" O mastiff é o maior cão que existe """
class SaoBernardo(Grande, Cao):
def __init__(self, nome):
Cao.__init__(self, nome) self.doses = 10
def servir(self):
if self.doses == 0:
raise ValueError('Acabou o conhaque!') self.doses -= 1
msg = '{0} serve o conhaque (restam {1} doses)' print msg.format(self.nome, self.doses)
modulo2.git/cao2.py
As duas hierarquias de um sistema OO
Object-oriented Analysis
and Design with Applications 2ed. - Grady Booch
• Hierarquia de classes
• is-a: é um
• Hierarquia de objetos
• part-of: parte de
As duas hierarquias de um sistema OO
Object-oriented Analysis
and Design with Applications 3ed. - Booch et. al.
from Tkinter import Frame, Label, Button
class Timer(Frame):
def __init__(self):
Frame.__init__(self)
self.inicio = self.agora = 15
self.pendente = None # alarme pendente self.grid()
self.mostrador = Label(self, width=2, anchor='e', font='Helvetica 120 bold',) self.mostrador.grid(column=0, row=0, sticky='nswe')
self.bt_start = Button(self, text='Start', command=self.start) self.bt_start.grid(column=0, row=1, sticky='we')
self.atualizar_mostrador()
def atualizar_mostrador(self):
self.mostrador['text'] = str(self.agora)
def start(self):
if self.pendente:
self.after_cancel(self.pendente) self.agora = self.inicio
self.atualizar_mostrador()
self.pendente = self.after(1000, self.tictac)
def tictac(self):
self.agora -= 1
self.atualizar_mostrador() if self.agora > 0:
self.pendente = self.after(1000, self.tictac) timer = Timer()
timer.mainloop()
Timer
• Exemplo
simples de composição
modulo2.git/timer.py
from Tkinter import Frame, Label, Button
class Timer(Frame):
def __init__(self):
Frame.__init__(self)
self.inicio = self.agora = 15
self.pendente = None # alarme pendente self.grid()
self.mostrador = Label(self, width=2, anchor='e', font='Helvetica 120 bold',) self.mostrador.grid(column=0, row=0, sticky='nswe')
self.bt_start = Button(self, text='Start', command=self.start) self.bt_start.grid(column=0, row=1, sticky='we')
self.atualizar_mostrador()
def atualizar_mostrador(self):
self.mostrador['text'] = str(self.agora)
def start(self):
if self.pendente:
self.after_cancel(self.pendente) self.agora = self.inicio
self.atualizar_mostrador()
self.pendente = self.after(1000, self.tictac)
def tictac(self):
self.agora -= 1
self.atualizar_mostrador() if self.agora > 0:
self.pendente = self.after(1000, self.tictac) timer = Timer()
timer.mainloop()
Timer
modulo2.git/timer.py
timer = Timer()
bt_start = Button()
Composição
• Arranjo de partes de um sistema
• componentes,
sub-componentes...
mostrador = Label()
from Tkinter import Frame, Label, Button
class Timer(Frame):
def __init__(self):
Frame.__init__(self)
self.inicio = self.agora = 15
self.pendente = None # alarme pendente self.grid()
self.mostrador = Label(self, width=2, anchor='e', font='Helvetica 120 bold',) self.mostrador.grid(column=0, row=0, sticky='nswe')
self.bt_start = Button(self, text='Start', command=self.start) self.bt_start.grid(column=0, row=1, sticky='we')
self.atualizar_mostrador()
def atualizar_mostrador(self):
self.mostrador['text'] = str(self.agora)
def start(self):
if self.pendente:
self.after_cancel(self.pendente) self.agora = self.inicio
self.atualizar_mostrador()
self.pendente = self.after(1000, self.tictac)
def tictac(self):
self.agora -= 1
self.atualizar_mostrador() if self.agora > 0:
self.pendente = self.after(1000, self.tictac) timer = Timer()
timer.mainloop()
modulo2.git/timer.py
instanciação
instanciação
instanciação
Composição em UML
composição composição