Exercícios com objetos

Objetivo

Ao final dessa aula, você deve ser capaz de construir classes e escrever programas utilizando objetos dessas classes.

Tópicos

  • Método com parâmetros com valores default.
  • Métodos especiais para operadores comuns como ‘+’, ‘-‘, ‘*’, e ‘/’.

Leituras complementares

Exercício comentado

Vimos que um número harmônico \(H_n\) de um inteiro \(n > 0\) é dado por

\[H_n = 1 + \frac{1}{2} + \frac{1}{3} + \frac{1}{4} + \ldots + \frac{1}{n}\]

Vamos fazer 2 implementações:

    1. DE - da direita para a esquerda e
    1. ED - da esquerda para direita.

Qual dessas formas é estável?

Observe que para \(n = 10\), os resultados são iguais, mas para \(n = 100 \ldots\)

(aula18_numero_harmonico_tentativa)



A razão da diferença é que ambas as somas são aproximadas mas, ao somar os números “grandes” primeiro, a precisão da soma devido a grande quantidade de números “pequenos” é perdida. Portanto o resultado da soma DE (de 1/n a 1) é mais preciso que da soma ED.

Nesse exercício, vamos criar uma classe de números fracionários que permita realizar a soma sem perda de precisão.

Definição da classe `Racional`

Escreva uma classe Racional que representa um número racional, que pode ser expresso por um numerador e um denominador inteiros. Essa classe deve ter ao menos os seguintes métodos:

  • add (para adição de racionais)
  • sub (para subtração)
  • mul (para multiplicação)
  • div (para divisão)

A caminho de uma solução

Como um número racional é definido por seu numerador e denominador, precisamos de ao menos dois atributos para representá-los. Esses atributos podem ser carregados quando criamos um racional. Além disso, podemos também imaginar como será a representação textual de um racional, que pode ser o numerador seguido pelo denominador, separados por /.

Até agora a classe Racional seria:

(Racional_v0)



Em muitos casos, é útil criar um objeto com valores pré-determinados, ou “default”. Por exemplo, podemos definir que o valor default de um Racional seja 0/1 (por que 0/0 seria uma má ideia?). Nesse caso, basta modificar o cabeçalho do método construtor __init__ da seguinte forma:

(Racional_v1)



Observe 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 racional 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 racional 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:

`r4 = Racional(d=-1)`

e até:

`r5 = Racional(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:

`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:

(Racional_v2)



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 é conhecido como polimorfismo, visto que permite que uma mesma função seja chamada de formas diferentes (com um número diferente de argumentos).

E para alterar os valores do numerador e denominador criados com os valores default?

Nesse caso, poderíamos simplesmente alterar os atributos diretamente

r1 = Racional()
r1.num, r1.den = 2, 3

Obviamente nesse caso seria mais simples escrever r1 = Racional(2,3). Em orientação à objetos também é comum usar um método para ler e escrever os valores de atributos, como os métodos put e get:

(Racional_v3)

Operações com Racionais

As operações de multiplicação e divisão são relativamente simples. Na multiplicação basta multiplicar os numerodores e os denominadores, e na divisão basta multiplicar o primeiro pelo inverso do segundo racional.

No caso da adição e subtração, é necessário transformar os racionais para um mesmo denominador, antes de somar e subtrair os numeradores.

No exercício abaixo, escreva os métodos div, add e sub (para divisão, adição e subtração), e alguns testes. Antes de escrever os seus métodos, observe o código do método mul que preserva os estados dos objetos self e other, e retorna um outro (nova instância) racional. Faça o mesmo para div, add e sub.

(Racional_v4)



Exercício 18.1

Altere a classe Racional para que, quando o denominador for 1, ele imprima apenas o valor do numerador.

Exercício 18.2

Altere o construtor da classe Racional para que, quando o numerador e o denominador possam ser reduzidos, a fração reduzida seja armazenada.

Exemplo: para a chamada Racional(12, 20) o valor de self.num e self.den devem ser, respectivamente, 3 e 5.

Dica: crie uma função (ou, se preferir, um método) com mdc que recebe 2 números inteiros a e b e retorna o máximo divisor comum entre a e b usando o algoritmo de Euclides.

Exercício 18.3

Para tornar a classe Racional mais interessante, é possível modificar o comportamento dos símbolos comumente utilizados para adição, subtração, divisão e multiplicação (+, -, /, *) para que possamos escrever programas usando expressões como

r1 = Racional(2,3)
r2 = Racional(1,4)
r3 = Racional(3,5)
r4 = r1 + r2 * r3

Para isso, ao invés de escrever os métodos add, sub etc, podemos os seguintes métodos especiais:

  • __add__: para adição (+)
  • __sub__: para subtração (-)
  • __mul__: para multiplicação (*)
  • __truediv__: para divisão (/)

Altere a classe Racional para utilizar esses métodos ao invés de add, sub, etc (provavelmente, basta modificar o nome dos métodos apropriados), e mofifique os testes para utilizar esses operadores.

Para saber sobre esses e outros métodos mágicos, acesse esse tutorial.

(Racional_v5)



Exercício 18.4

O programa a seguir calcula o harmônico de um dado número inteiro \(n\) utilizando a soma ED (da esquerda para a direita). Rode o programa e verifique se há diferença entre os métodos usando a soma de números reais e a soma de números racionais.

Modifique o programa para incluir o cálculo da direita para a esquerda (DE).

OBSERVAÇÃO: Caso você encontre problemas ao executar esse código no seu navegador, copie o código para um editor como o IDLE3 e execute o programa em seu computador.

(aula18_numero_harmonico_tentativa_com_racional)



Exercício 18.5

Escreva uma classe Ponto3D que representa um ponto no espaço tridimensional, com as seguintes operações:

  • distância à origem
  • distância entre dois pontos
  • ponto médio entre dois pontos
  • __str__ que devolve um string

Nesse caso, os atributos podem ser 3 reais representando as coordenadas do ponto.

(exercício_18_5_tentativa)