1 Wednesday, February 1, 2012
Transcrição
1 Wednesday, February 1, 2012
Orientação a objetos e frameworks Wednesday, February 1, 2012 1 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 Wednesday, February 1, 2012 2 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) Wednesday, February 1, 2012 3 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) Wednesday, February 1, 2012 4 Smalltalk 1980 Wednesday, February 1, 2012 5 Squeak: Smalltalk livre Wednesday, February 1, 2012 6 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 Wednesday, February 1, 2012 7 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. Wednesday, February 1, 2012 8 Exemplo: um objeto dict • Sobrecarga de operadores: • [ ]: • __getitem__ • __setitem__ >>> 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'} Wednesday, February 1, 2012 9 Exemplo: um objeto dict • Atributos de dados: __class__, __doc__ >>> 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) Wednesday, February 1, 2012 10 Exemplo: um objeto dict • Em Python, métodos também são atributos >>> 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'] Wednesday, February 1, 2012 11 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) Note: Em Tkinter, atributos de dados são acessados via [ ]: __getitem__ e __setitem__ tictac() relogio.mainloop() Wednesday, February 1, 2012 12 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 Wednesday, February 1, 2012 13 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'> Wednesday, February 1, 2012 14 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',) Wednesday, February 1, 2012 15 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 Wednesday, February 1, 2012 16 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 File "<stdin>", line TypeError: unsupported >>> a + int(b) 19 >>> str(a) + b '109' >>> 77 * None Traceback (most recent File "<stdin>", line TypeError: unsupported >>> Wednesday, February 1, 2012 call last): 1, in <module> operand type(s) for +: 'int' and 'str' call last): 1, in <module> operand type(s) for *: 'int' and 'NoneType' 17 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' Wednesday, February 1, 2012 18 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 Wednesday, February 1, 2012 19 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. 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. Wednesday, February 1, 2012 JavaScript (ECMAScript 5) em Node.js 0.6 > 10 + '9' '109' > 10 + '9' * 1 19 > '10' + 9 * 1 '109' > 0 == '0' true > 0 == '' true > '0' == '' false 20 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 Wednesday, February 1, 2012 21 Tipagem em linguagens Smalltalk Python Ruby C (K&R) C (ANSI) Java C# JavaScript PHP Wednesday, February 1, 2012 dinâmica dinâmica dinâmica estática estática estática estática dinâmica dinâmica forte forte forte fraca forte forte forte fraca fraca } combinação perigosa: bugs sorrateiros 22 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' >>> Wednesday, February 1, 2012 23 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 Wednesday, February 1, 2012 24 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' Wednesday, February 1, 2012 25 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 Wednesday, February 1, 2012 26 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 Wednesday, February 1, 2012 27 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... Wednesday, February 1, 2012 28 >>> 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 Wednesday, February 1, 2012 29 Exemplar de cão: instância da classe Cao >>> rex = Cao() Wednesday, February 1, 2012 instanciação 30 instanciação Classe Cao class Mamifero(object): """lição de casa: implementar""" >>> 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! 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 Wednesday, February 1, 2012 31 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) • atributos de dados na classe funcionam como valores default para os atributos das instâncas • __init__ é o construtor, ou melhor, o inicializador modulo2.git/cao.py Wednesday, February 1, 2012 32 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) • self é o 1º parâmetro em todos os métodos de instância • atributos da instância só podem ser acessados via self modulo2.git/cao.py Wednesday, February 1, 2012 33 Classe Cao class Mamifero(object): """lição de casa: implementar""" class Cao(Mamifero): invocação 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 modulo2.git/cao.py Wednesday, February 1, 2012 34 Mamifero: superclasse UML de Cao class Mamifero(object): """lição de casa: implementar""" diagrama de classe 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) generalização modulo2.git/cao.py Wednesday, February 1, 2012 35 Subclasses de Cao • Continuação de cao.py 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) Wednesday, February 1, 2012 Diz a lenda que o cão São Bernardo leva um pequeno barril de conhaque para resgatar viajantes perdidos na neve. 36 Subclasses de Cao >>> 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! 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) Wednesday, February 1, 2012 37 Subclasses de Cao • Continuação de cao.py 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) Wednesday, February 1, 2012 38 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 Wednesday, February 1, 2012 39 Uma pequena parte da hierarquia de classes do Tkinter herança múltipla mixin he ran ça mú lt ipl Wednesday, February 1, 2012 a 40 Um pouco mais da hierarquia de classes do Tkinter Wednesday, February 1, 2012 41 Hierarquia de classes dos objetos gráficos doTkinter Wednesday, February 1, 2012 42 Herança múltipla • Refatoração de cao.py para cao2.py • Reutilizar o latido do mastiff em outros cães grandes mixin heran ça mú ça heran ltipla pla múlti 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 Wednesday, February 1, 2012 43 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 Wednesday, February 1, 2012 44 As duas hierarquias de um sistema OO • Hierarquia de classes • is-a: é um • Hierarquia de objetos • part-of: parte de Object-oriented Analysis and Design with Applications 2ed. - Grady Booch Wednesday, February 1, 2012 45 As duas hierarquias de um sistema OO Object-oriented Analysis and Design with Applications 3ed. - Booch et. al. Wednesday, February 1, 2012 46 from Tkinter import Frame, Label, Button Timer • Exemplo simples de composição 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 Wednesday, February 1, 2012 47 from Tkinter import Frame, Label, Button Timer 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 Wednesday, February 1, 2012 48 Composição • Arranjo de partes de um sistema mostrador = Label() • componentes, sub-componentes... bt_start = Button() timer = Timer() Wednesday, February 1, 2012 49 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 instan self.grid() ciaçã o 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) row=1, sticky='we') ão ç self.bt_start.grid(column=0, a i c n a self.atualizar_mostrador() inst 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) instan ciaçã o 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 Wednesday, February 1, 2012 50 Composição em UML composição composição Wednesday, February 1, 2012 51