Classes e Orientação a Objetos

Classes são o principal mecanismo do paradigma de programação chamado "Programação Orientada a Objetos" (POO). De acordo com esse paradigma, programas manipulam certos componentes básicos chamados de objetos, que podem ser vistos como dados mais um conjuntos de operações associadas a esses dados. Uma classe é uma estrutura que representa um objeto bem como as operações que podem ser executadas sobre esse objeto.

Objetos possuem um tipo, uma representação interna (campos), e provêm uma interface (métodos). As informações (campos) de um objeto são chamadas de atributos do objeto, e as operações realizadas sobre o objeto são chamadas métodos do objeto/classe.

Métodos em Python

Em linhas gerais, um método é uma função que funciona somente dentro da classe, isto é, somente quando invocado por objetos da classe, seguindo uma sintaxe específica: objeto.metodo. Além disso, ao invocar um método, Python sempre passa como primeiro parâmetro para o método o objeto que está invocando o método. Para indicar isso, a convenção é declarar self como primeiro parâmetro de todo método.

Mesmo sem saber, você já deve ter utilizado classes antes, visto que elas estão por toda parte em Python. Por exemplo, quando você invoca o método reverse() para inverter os elementos de uma lista, está invocando um método de uma classe da biblioteca padrão Python.

Nesta seção, aprenderemos a definir nossas próprias classes e a criar e interagir com objetos dessas classes. Mas antes disso, vamos abrir um pequeno parêntese para explicar orientação a objetos.

Orientação a objetos é basicamente uma forma de se organizar programas. Suponha, por exemplo, que você precise escrever um programa para calcular o consumo médio de combustível de um veículo baseado nas características desse veículo. Inicialmente, seu programa precisa funcionar somente para carros e motos. Uma opção possível seria escrever código para testar se o veículo em questão é um carro ou uma moto, e então calcular o consumo médio de combustível de acordo com as características desse veículo. Outra opção (defendida pelo paradigma de programação orientada a objetos) seria criar duas classes (moto e carro) e colocar dentro de cada uma a fórmula para calcular o consumo de combustível do veículo representado pela classe em questão. Assim, para calcular o consumo de combustível, você simplesmente invoca um método do objeto da classe, que por sua vez possui uma implementação de como calcular o consumo de combustível da forma apropriada.

Agora suponha que seu programa também tenha que calcular o consumo de combustível de caminhões. Como você faria?

De acordo com a primeira estratégia, você teria que adicionar um teste para ver se o veículo em questão é um caminhão e adicionar o código para calcular o consumo de combustível de caminhões. Segundo o paradigma de orientação a objetos, você poderia criar uma classe para representar caminhões e adicionar a essa classe um método para calcular o consumo de combustível de caminhões. A vantagem da orientação a objetos é que a classe que representa caminhões poderia ser implementada de forma relativamente independente das classes que representam motocicletas e carros. Além disso, modificações na forma de se calcular o consumo de combustível de um tipo de veículo não afetam o código que utiliza as funções que realizam esses cálculos.

Vejamos agora em mais detalhes como criar classes e objetos em Python.

Exemplos de classe

Vamos começar com um exemplo simples de classe, uma classe para armazenar informações básicas de uma pessoa.

class Pessoa:
    """Armazena informações básicas de uma pessoa."""
    def __init__(self, nome, idade):
        self.nome = nome
        self.idade = idade

    def get_nome(self):
        return self.nome

    def get_idade(self):
        return self.idade

# Usando a classe.
p1 = Pessoa("João", 20)
print("O {} tem {} anos.".format(p1.get_nome(), p1.get_idade()))
O João tem 20 anos.

No exemplo acima, a classe armazena o nome e a idade de uma pessoa. Criamos os métodos get_nome() e get_idade() para recuperar o nome e a idade da pessoa em questão. Poderíamos também ter acessado os campos nome e idade por meio das operações p1.nome e p1.idade. Entretanto, sempre que temos os chamados "métodos acessórios" (métodos usados para acessar campos de uma classe), é melhor usá-los em vez de acessar as propriedades diretamente.

Vejamos agora um exemplo mais complicado de classe em Python.

O exemplo abaixo cria uma classe para representar um vetor em Python. Esse exemplo foi tirado do livro Python Fluente, e seu código-fonte pode ser encontrado no repositório do livro no github.

from math import hypot

class Vector:
    """Representa um vetor em Python."""
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def __str__(self):
        return 'Vector(%r, %r)' % (self.x, self.y)

    def __abs__(self):
        return hypot(self.x, self.y)

    def __bool__(self):
        return bool(abs(self))

    def __add__(self, other):
        x = self.x + other.x
        y = self.y + other.y
        return Vector(x, y)

    def mul(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)

Pela definição da classe acima, notamos que:

  • A criação de uma classe envolve duas coisas: a definição do nome da classe, a definição dos atributos da classe. Opcionalmente, podemos definir também métodos para a classe.

  • Alguns métodos de classes em Python possuem um papel especial: + O método init é invocado sempre que um objeto da classe é criado. Ele funciona como um construtor da classe. + O método str é usado para se converter a representação da classe para uma string. Ele é útil para imprimirmos um objeto da classe, por exemplo.

  • É possível emular tipos numéricos em Python. Isso é feito por métodos como abs, bool, e add acima. Em nosso exemplo, usamos a emulação de tipos numéricos para declarar a operação de adição de vetores, e usamos o método clássico de criação de métodos para declarar a multiplicação de um vetor por um escalar, mas mesmo no caso da multiplicação poderíamos ter emulado tipos numéricos definindo o método como mul.

Para criar um novo objeto da classe Vector, podemos fazer como abaixo:

v1 = Vector(10, 20) # O método __init__ será chamado.
print(v1)           # O método __str__ será chamado.

v2 = Vector(30, 40) # O método __init__ será chamado.
print(v2)           # O método __str__ será chamado.
Vector(10, 20)
Vector(30, 40)

Chamando um método da classe:

print(v.mul(5))
Vector(50, 100)

Pelos exemplos acima, vemos que o uso de uma classe em Python envolve:

  • A criação de novos objetos (instâncias da classe).

  • A realização de operações com os objetos criados.

Vemos ainda que objetos diferentes de uma mesma classe guardam estados diferentes (v1 e v2 possuem valores distintos), ou seja, é possível ter inúmeros objetos de um mesmo tipo no mesmo programa, cada um possuindo um estado diferente dos demais.

Playground

# Use este espaço para praticar o que você aprendeu nesta seção. # Basta digitar o código no espaço abaixo e clicar 'run'.