.. -*- coding: utf-8 -*- Melhorando o comportamento da classe Fraction ============================================= Na aula anterior vimos alguns recursos básicos oferecidos para a criação de classes em Python. Nessa aula vamos continuar essa introdução à **Programação Orientada a Objetos**. que facilita o desenvolvimento de algoritmos com **tipos de dados abstratos** que você mesmo pode criar e usar. Tópicos ------- - Mais sobre classes, objetos, atributos e métodos; - Método com parâmetros com ``valores default``; - Métodos especiais para operadores comuns como, por exemplo, ``+``, ``-``, ``*``, e ``/``; Introdução ---------- Na aula anterior começamos a definir uma classe ``Fraction`` usada para **abstrair** o comportamento de frações. Vimos que, ao definir uma classe, é necessário definir atributos e métodos. Alguns métodos têm nome pré-definido e são chamados de **especiais** pois enriquecem o comportamento das classes para facilitar a criação e uso de objetos em seus programas. Eles são fáceis de identificar pois são escritos entre dois sublinhados como ``__init__`` e ``__str__``. Antes, porém, veremos outra característica importante na definição de métodos que é o uso de valores default para os parâmetros. Objetos criados com valores "default" ------------------------------------- Em muitos casos, é útil criar um objeto com valores pré-determinados, ou "default". Por exemplo, podemos definir que o valor default de um ``Fraction`` seja ``0/1`` (por que ``0/0`` seria uma má ideia?). Nesse caso, basta modificar o cabeçalho do método construtor ``__init__`` como ilustrado no trecho de código abaixo. Clique em ``Forward`` para ver a simulação passo-a-passo desse programa. .. raw:: html .. .. code:: python class Fraction: def __init__(self, n=0, d=1): # o valor default de n é 0 e de d é 1 self.num = n self.den = d def __str__(self): return f"{self.num}/{self.den}" # testes r1 = Fraction() ## usa os dois valores default, n=0 e d=1 r2 = Fraction(4) ## usa o valor default d = 1 r3 = Fraction(3,2) print(r1) print(r2) print(r3) Observe na linha 2 que, agora, o valor default de ``n`` é ``0`` (parâmetro correspondente ao numerador) e de ``d`` é ``1``. Devido aos valores default, podemos definir os valores de ``n`` e ``d`` apenas quando são diferentes de ``0`` e ``1`` respectivamente. Nesse caso, observe que o valor do Fraction ``r1`` é ``0/1``, o de ``r2`` é ``4/1`` e o de ``r3``, que recebeu os 2 argumentos, é ``3/2``. Quando apenas 1 argumento é passado, a ordem dos parâmetros se torna importante. Assim, na criação do Fraction ``r2``, o segundo parâmetro (correspondente ao denominador) não foi fornecido e assume portanto o seu valor default ``1``. Mas e se eu quiser passar apenas o valor do denominador? ........................................................ Nesse caso, o Python permite que você passe os parâmetros usando os nomes definidos no cabeçalho do método. Assim: .. code:: python r4 = Fraction(d=-1) e até: .. code:: python r5 = Fraction(d=3, n=2) são chamadas válidas. É possível ainda misturar parâmetros com e sem valores default. Se você decidir por misturar, procure agrupar os parâmetros com valores default no final do cabeçalho, para deixar as chamadas mais consistentes, como: .. code:: python def metodo(self, a, b, c = 0, d = 1): Experimente colocar outros parâmetros no ``__init__()``, e crie outros métodos, usando o trecho de código abaixo: .. raw:: html .. .. code:: python # Fraction_v2 class Fraction: def __init__(self, a, b, n=0, d=1): self.num = n self.den = d print("Construtor: ", a, b, n, d) # observe que a e b não se tornam atributos de Fraction def __str__(self): return f"{self.num}/{self.den}" # testes r1 = Fraction('in', 'out') r2 = Fraction('a', 'b', 4) r3 = Fraction('x', 'y', d=5, n=3) .. admonition:: Polimorfismo o uso de valores default é permitido também no cabeçalho de funções comuns, não apenas métodos, do Python. Esse recurso é um tipo de **polimorfismo**, visto que permite que uma mesma função seja chamada de **formas diferentes** (com um número diferente de argumentos). Métodos e funções ----------------- Vimos que, por ser um novo tipo numérico, é importante que a classe ``Fraction`` permita realizar operações numéricas como: - ``add()``: para adição de duas frações; - ``sub()``: para subtração de frações; - ``mul()``: para multiplicação de duas frações; - ``div()``: para divisão entre frações. Esse métodos definem as formas como podemos manipular objetos do tipo ``Fraction`` e, portanto, precisam estar associados ao objeto para usar a notação de ponto como no trecho a seguir: .. code:: python f23 = Fraction(2, 3) f34 = Fraction(3, 4) soma = f23.add(f34) # chamada do método add usando a notação de ponto As pessoas iniciantes na POO podem as vezes confundir métodos com funções. Funções auxiliares devem ser definidas **fora** da definição da classe, para facilitar o reuso dessas funções por outras classes e até mesmo por outros programas. O trecho de código abaixo ilustra uma classe ``Fraction`` cujo construtor usa a função auxiliar ``mdc_euclides()`` para criar funções irredutíveis. Essa função é chamada dentro do construtor (``__init__``) na linha 20, para calcular o **máximo divisor comum** enter o numerador e o denominador da fração. Assim, frações com valores como ``-12/4`` são reduzidas para o valor ``-3/1``. Estude o código abaixo e complete essa classe com os métodos ``sub`` e ``div``. .. raw:: html .. source na pasta py/a05/classFraction01.py Como usar os símbolos ``+``, ``*``, ``-`` e ``/`` ------------------------------------------------- Uma inconveniência do método ``add``, como vimos na aula anterior, é que precisamos usar a notação de ponto para usar esse método, como no trecho de código abaixo: .. code:: python f23 = Fraction(2,3) # cria um objeto do tipo Fraction f34 = Fraction(3,4) soma = f23.add(f34) # chamada do método 'add' Por ser um tipo numérico, seria mais conveniente se pudéssemos escrever: .. code:: python f23 = Fraction(2,3) # cria um objeto do tipo Fraction f34 = Fraction(3,4) soma = f23 + f34 # chamada do método especial '__add__' A notação de ponto deixa explícito o uso de um método associado a um objeto. No entanto, para tipos numéricos, estamos acostamos a usar símbolos como ``+`` e ``*`` para representar operações como soma e multiplicação. Essa sintaxe mais "simples", escrevendo ``f23 + f34`` ao invés de ``f23.add(f34)`` facilita a leitura e manutenção do código que usa objetos do tipo ``Fraction``. Para fazer com que nossa classe ``Fraction`` reconheça esses símbolos, podemos trocar os nomes que usamos anteriormente para o nome do método especial correspondente. Consulte a [documentação do Python](https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types) para ver o nome de outros métodos especiais numéricos que você pode utilizar. Por exemplo, para os operadores que escolhemos escrever para a nossa classe ``Fraction``, os nomes dos métodos especiais são: - ``__add__(self, other)``: para adição de duas frações usando a notação "self + other"; - ``__sub__(self, other)``: para subtração de frações usando "self - other"; - ``__mul__(self. other)``: para multiplicação de duas frações usando "self * other"; - ``__truediv__(self, other)``: para divisão entre frações usando "self / other". O trecho de código a seguir modifica nossa classe ``Fraction`` que acabamos de ver anteriormente para que ela passe a fazer as operações de soma e multiplicação usando os símbolos ``+`` e ``*``, respectivamente. Complete a classe ``Fraction`` com os métodos ``__sub__`` e ``__truediv__``. .. raw:: html Exercícios ---------- #. Um número complexo é representado por dois números reais. Nesse exercício, cada objeto dessa classe deve possuir dois atributos de estado de nomes: * `real`: um número real (float) que corresponde à parte real * `imag`: um número real que corresponde à parte imaginária Estude a seguinte função ``main()`` e verifique os resultados esperados. Implemente uma classe ``Complexo`` que deve possuir os atributos `real` e `imag`, e os métodos que permitam a execução dessa função ``main()``, gerando exatamente os mesmos resultados esperados. .. code:: python def main(): ''' Testes da classe Complexo ''' print("Testes da classe Complexo\n") c0 = Complexo() # construtor chama __init__() print(f"Atributos: real = {c0.real} e imag = {c0.imag}") c1 = Complexo(9) print(f"Atributos: real = {c1.real} e imag = {c1.imag}") c2 = Complexo(7, 5) print(f"Atributos: real = {c2.real} e imag = {c2.imag}") print("\nChamadas dentro de print") print(f"c0 = {c0}") # chama __str__ print(f"c1 = {c1}") print(f"c2 = {c2}") print("\nResultados dos métodos") c3 = c0.some(c1) c4 = c1 * c2 print(f"c3 = {c3}") print(f"c4 = {c4}") A saída do programa deve ser: .. code:: python Testes da classe Complexo Atributos: real = 0.0 e imag = 0.0 Atributos: real = 9.0 e imag = 0.0 Atributos: real = 7.0 e imag = 5.0 Chamadas dentro de print c0 = 0.0 c1 = 9.0 c2 = 7.0+j5.0 Resultados dos métodos c3 = 9.0 c4 = 63.0+j45.0 Onde estamos e para onde vamos? ------------------------------- Como Python é uma linguagem orientada a objetos, onde todos os tipos pré-definidos como ``int`` e ``flost`` correspondem a classes pré-definidas, a criação de novas classes é relativamente simples uma vez que você domine a sintaxe para criação de atributos e métodos de uma classe. Em particular, nessa aula exploramos métodos especiais que podemos utilizar ao definir novos tipos numéricos que nos permite escrever expressões aritméticas usando símbolos matemáticos tradicionais como ``+``, ``-``, ``*`` e ``/``. Na próxima aula vamos ver que o uso desses métodos especias tem implicações bem mais poderosas pois a nossa nova classe númerica herda comportamentos dos tipos númericos primitivos do Python! Para saber mais --------------- * `Tutorial de POO em Python `__. * `Classe Fração `__; .. * `Tutorial sobre método mágicos `__. Para praticar mais ------------------ #. Escreva uma classe ``Ponto3D`` que representa um ponto no espaço tridimensional, ou seja, os atributos podem ser 3 reais representando as coordenadas do ponto. Sua classe deve permitir as seguintes operações: - sua distância à origem - sua distância até um outro ponto - ponto médio entre o ponto e outro ponto - ``__str__()`` que devolve uma string. **DICA**: a raiz quadrada de 2 pode ser calculada por ``2 ** 0.5``.