7. 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.
7.1. 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/
;
7.2. 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.
7.3. 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.
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
.
7.3.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 = Fraction(d=-1)
e até:
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:
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:
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).
7.4. 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:
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
.
source na pasta py/a05/classFraction01.py
7.5. 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:
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:
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__
.
7.6. 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 classeComplexo
que deve possuir os atributos real e imag, e os métodos que permitam a execução dessa funçãomain()
, gerando exatamente os mesmos resultados esperados.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:
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
7.7. 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!
7.8. Para saber mais¶
7.9. 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
.
- Escreva uma classe