Para acabarmos os conteúdos referentes a Python, discutiremos o paradigma da programação orientada a objetos. A programação orientada a objetos (POO) é, como o nome diz, baseada em objetos, que são correspondentes aos da realidade, como: pessoa, carro, entre outros.
Nesse modelo existem classes, que são como um gabarito, um molde, para objetos com características semelhantes. Essas características na POO são chamadas, também, de atributos. Um exemplo de classe é a classe Livro, que origina os objetos livro1, livro2, etc. Na classe exemplificada abaixo, os atributos são: cor da capa, nome do livro e o número de páginas.

**Os métodos são como funções, só que pertencentes às classes. Seria como se tivéssemos a classe pessoa e ela falasse. Para isso, criaríamos um método chamado falar. Sempre que desenvolvemos uma classe, a primeira coisa que devemos fazer é criar um construtor, que é um método “especial” que tem como objetivo criar os futuros objetos. Nota-se a estrutura de um construtor abaixo:
def __init__ (self, atributo1, atributo2, …)
self.atributo1 = atributo1
self.atributo2 = atributo2
…
#usa-se dois underlines seguidos antes e depois da
#palavra init
Podemos observar que, antes de definirmos o construtor, utilizamos a palavra def, usada para declarar um método. Já a palavra init é reservada ao construtor. Abaixo, temos um exemplo real de uma classe pessoa que origina os objetos p1 e p2.
class Pessoa:
def __init__(self, nome, idade):
self.nome = nome
self.idade = idade
def falar(self)
print('ola, tudo bem?'
p1 = Pessoa('Teo', 1)
p2 = Pessoa('Laura',10)
p1.falar()
No exemplo anterior, criamos, além de uma classe com atributos, o método falar, que imprime na tela a frase ‘ola, tudo bem?’.
A palavra self serve para fazer referência à própria instância que está chamando o método e deve ser utilizada em todos os atributos que forem criados dentro de uma classe.
Temos ainda os métodos de classe, que são tipos “especiais” e se referem à classe e não ao objeto. Logo, não é requerida a criação de uma instância. Para implementarmos o método de classe, utilizamos o decorador @classmethod e, em seguida, criamos o método. Nesse tipo, ao invés de utilizamos a palavra self para nos referirmos à instância de uma classe, um objeto, utilizamos a palavra cls para fazer referência à classe em si. Abaixo, temos um exemplo, no qual o intuito é criarmos um objeto da classe pessoa. Ao invés de inserirmos o nome dela e sua idade, como no exemplo anterior, criaremos um objeto passando como parâmetro o nome e o ano de nascimento do indivíduo. Para isso, usaremos o método de classe.
class Pessoa:
ano_atual = 2020
def __init__(self, nome, idade):
self.nome = nome
self.idade = idade
@classmethod
def por_ano_nascimento(cls, nome, ano_nascimento):
idade = cls.ano_atual - ano_nascimento
return cls(nome, idade)
def falar(self):
print('ola, tudo bem?')
p1 = Pessoa.por_ano_nascimento('Teo', 2019)
Agora abordaremos os métodos estáticos, que são aqueles que não dependem nem da classe, nem da instância. Eles são como funções comuns, porém são colocados dentro de uma classe para facilitar a organização do código. Como por exemplo, se quisermos uma função que gera um id, ou seja, um número aleatório entre 1 e 1000, podemos torná-la em um método estático que faz parte da classe Pessoa.
from random import randint
class Pessoa:
def __init__(self, nome, idade):
self.nome = nome
self.idade = idade
@staticmethod
def gera_id():
rand = randint(1, 1000)
return rand
print(Pessoa.gera_id())
#OU
p1 = Pessoa('Teo', 1)
print(p1.gera_id())
Notamos ainda que dentro do método estático gera_id(), utilizamos a função randint(), que gera números aleatórios. Os atributos de classe são aqueles que se referem à classe e não aos objetos dela. Eles podem ser acessados pelos objetos, mas não por eles alterados. Observa-se isso no exemplo abaixo:
class A:
atributoDeClasse=10
a1 = A()
a2 = A()
print(A.atributoDeClasse)
print(a2.atributoDeClasse)
print(a1.atributoDeClasse)
O Python, com relação ao encapsulamento, é diferente da maioria das outras linguagens de programação OO (orientadas a objeto). Nas outras, temos uma parte da classe privada ou protegida e uma parte pública. A parte privada ou protegida não pode ser acessada diretamente de fora do objeto, somente por getters e setters. Como em Python não existe esse tipo de divisão, foi convencionado que se um atributo ou método tiver um underline o precedendo, então é como se fosse privado. Porém, embora não seja recomendado, isso não nos impede de modificá-lo diretamente. Agora, caso esse atributo seja precedido por dois underlines, não poderemos acessá-lo diretamente, de modo algum. Para resolver esse problema, usamos a seguinte sintaxe:
nomeDoObjeto._nomeDaClass__nomeDoAtributo
Um exemplo disso é caso tivéssemos como intuito ter uma classe pessoa com o atributo nome “privado”, como pode ser visto abaixo:
class Pessoa:
def __init__(self, nome, idade):
self.__nome = nome
self.idade = idade
p1 = Pessoa('Teo', 1)
print(p1._Pessoa__nome)
Podemos, também, tanto acessar quanto modificar esse atributo nome através de Getters e Setters. Os Getters servem para acessar um atributo “privado”; ou seja, são métodos que retornam um atributo. Já os Setters são métodos cuja função é receber um valor e, em seguida, a e ele igualar um atributo. Logo abaixo, temos o mesmo exemplo que utilizamos acima, mas resolvido de outra forma, com Geters e Setters:
class Teste:
def __init__(self):
self.__x = None
@property
def x(self):;
return self.__x
@x.setter
def x(self, value):
self.__x = value
teste1 = Teste() #estamos criando o objeto teste1
teste1.x = 5 #setter
print(teste1.x) #getter
Podemos notar que, antes de definirmos os métodos Getter e Setter, utilizamos os decoradores @property para o get e @nomeDoAtributo.setter e, por fim, nas duas últimas linhas, invocamos primeiro o método set e, em seguida, o get.
Em um programa, podemos ter diversas classes e elas podem se relacionar entre si de 3 maneiras: associação, agregação e composição.
A associação é quando uma classe utiliza a outra, mas não existe uma relação de interdependência entre elas. Um exemplo disso é um programa em que uma classe escritor usa uma classe caneta, como pode ser visto a seguir:
class Escritor:
def __init__(self, nome):
self.nome = nome
self.ferramenta = None
class Caneta:
def __init__(self, marca):
self.marca = marca
def escrever(self):
print('a caneta escreve')
escritor = Escritor(‘Teo')
caneta = Caneta('Bic')
escritor.ferramneta = caneta
escritor.ferramneta.escrever()
O segundo tipo é a agregação, na qual uma classe existe sem a outra, mas há uma relação de dependência entre elas. Como, por exemplo, uma classe carrinho de compras e uma
classe compra. Uma existe independente da outra, mas o carrinho de compras não faria sentido sem compras. Abaixo, temos a representação, em código, desse exemplo:
class CarrinhoDeCompras:
def __init__(self):
self.produtos = []
def inserir_produto(self, produto):
self.produtos.append(produto)
class Produto:
def __init__(self, nome, valor):
self.nome = nome
self.valor = valor
p1 = Produto('caneta', 5)
carrinho = CarrinhoDeCompras()
carrinho.inserir_produto(p1)
Ainda temos a relação por composição, que é a mais forte. Nela, uma classe é “dona” da outra, isso quer dizer que, se a classe principal for apagada, todos os objetos que ela utilizou serão apagados também. Como, por exemplo, no código abaixo, no qual temos uma loja com inúmeras sedes e queremos armazenar os endereços em uma lista, utilizando uma classe endereço:
class class Loja:
def __init__(self, nome):
self.nome = nome
self.enderencos = []
def insere_enderecos (self, cidade, estado):
self.enderencos.append(Endereco(cidade, estado))
class Endereco:
def __init__(self, cidade, estado):
self.cidade = cidade
self.estado = estado
p1 = Loja('Renner')
p1.insere_enderecos('Curitiba', ‘PR')
p1.insere_enderecos('Guaratuba', 'PR')
print(p1.enderencos[0].cidade)
print(p1.enderencos[0].estado)
Pode-se notar no exemplo acima, que a classe loja contém todos os objetos endereços, ou seja, criaremos um novo objeto endereço sempre que invocarmos a classe Endereco.
Por fim, temos a herança, que serve, principalmente, para reaproveitarmos código. Vamos supor que temos uma classe pessoa que possui dois atributos, o atributo nome e o idade. Temos ainda uma classe estudante que, além do nome e da idade, possui o atributo ano escolar. Nesse caso, poderíamos herdar o nome e idade da classe pessoa, da seguinte forma:
class Pessoa:
def __init__(self, nome, idade):
self.nome=nome
self.idade=idade
class Aluno(Pessoa):
def __init__(self, nome, idade, anoEscolar): Pessoa.__init__(self, nome, idade)
self.anoEscolar = anoEscolar
a1 = Aluno('Teo', 10, 'terceiro ano')
🔗 Para acessar todos os capítulos, visite a página: Livro de Lógica de Programação em Python.