Aprenda Python

  • Página Inicial
  • Contato!
  • Tudo sobre Python Parte 1!
  • Tudo sobre Python Parte 2!
  • Tudo sobre Python Parte 3!
  • Tudo sobre Python Parte 4!
  • Tudo sobre Python Parte 5!
  • Tudo sobre Python Parte 6!
  • Tudo sobre Python Parte 7!
  • Tudo sobre Python Parte 8!
  • Programção Orientada a Objetos em Python - Parte 1

    Criando Classes e Objetos na Prática

    Como sabemos, uma variável simples é um espaço na memória, um local que fica reservado para guardar alguma coisa, como uma caixinha de papelão, vamos supor que esta caixinha tenha a etiqueta estado, e dentro dela colocamos um valor como SP (São Paulo), se tentarmos colocar outro valor (como RJ), o SP sai e dá lugar ao RJ. Exemplo:

    
    estado = "SP"
    estado = "RJ" # SP sai e entra RJ
    
    print(estado) # Exibe RJ
    
    

    Para guardarmos vários valores usamos variáveis compostas (como uma sequência de caixinhas com o nome estados), onde podemos colocar em cada uma delas um valor, com índices numerados a partir do 0 (como RJ, SP e CE), e ao acessar algo como estados[2] acessaremos o valor na caixinha 2 (no caso, CE), e podemos substituir esses valores também, isso no Python é o princípio de lista. Também temos os dicionários, onde podemos ter índices de nomes literais (como uma sequência de caixinhas com o nome aluno e índices como nome, turma, nota e ativo. Tanto em listas quanto em dicionários podemos ter valores de vários tipos numa mesma lista ou dicionário. Exemplos:

    
    estados = ["RJ", "SP", "CE"]
    
    estados[1] = "RS" # Substitui SP por RS
    
    print(estados[2]) # Exibe CE
    
    aluno = {"nome": "José", "turma": 301, "nota": 8.5, "ativo": True}
    
    print(aluno["nome"]) # Exibe José
    
    

    O maior problema em tudo isso é a separação entre dados e funções. Variáveis, tanto simples como compostas, só guardam dados. Para usar esses mesmos dados em funcionalidades, precisaríamos criar funções separadas pra isso.

    O ideal seria permitir que a variável execute funcionalidades internas, por isso que surgiu a programação orientada a objetos, que permite que a variável guarde funções também. De certa forma, um objeto é uma variável evoluída, que além de dados, guarda funcionalidades em métodos. Em outras palavras, objetos são variáveis que, além de guardar dados, podem fazer coisas com esses dados.

    PS: No Python, qualquer variável é considerada um objeto, isso geralmente acontece em muitas linguagens orientadas a objetos, mas não em todas.

    Em uma classe, devemos delimitar, por organização, as partes que colocamos os atributos, que geralmente vem primeiro, e depois os métodos. Para criar uma classe usamos a palavra class e por organização, deve ter a primeira letra maiúscula, com PascalCase permitido (como class MinhaClasse, e a tabulação é importante também, algo assim:

    
    class MinhaClasse:
        # Atributos
    
        # Métodos
        
    obj = MinhaClasse() # Criação do objeto (instância).
    
    

    PS: Não confunda a instânciação de uma classe com a invocação de uma função, apesar dela também usar parênteses. Ela é a instanciação sim de um método, que é o construtor.

    Crie um arquivo Python e coloque esse código:

    
    # Declaração de classe:
    class Gafanhoto:
        # Método construtor, onde ficará nossos atributos
        def __init__(self):
            # Atributos de instância
            self.nome = ""
            self.idade = 0
    
        # Métodos de instância, que manipularão os atributos
        def aniversario(self):
            self.idade = self.idade + 1
    
        def mensagem(self):
            return f"{self.nome} é Gafanhoto(a) e tem {self.idade} anos de idade."
    
    # Declaração de objetos:
    
    

    O self será substituído pelo objeto a ser criado, e ele também deve estar em todo método da classe, inclusive o construtor.

    No caso acima, a classe é apenas um molde para criar os objetos, mas eles ainda não foram criados. Abaixo do comentário, crie um objeto, fora de qualquer tabulação, assim:

    
    # Declaração de objetos:
    g1 = Gafanhoto()
    
    print(g1.mensagem())
    
    

    No caso acima, a variável g1 é um objeto que foi instanciado da classe Gafanhoto (que chamará automaticamente o método __init__, que é o construtor).

    Para chamar os atributos e métodos de um objeto, usamos o ponto, e os atributos não usam parênteses, apenas os métodos (sem o self, nesse caso).

    Claro, que como não colocamos nada nos atributos dele, ele vai mostrar uma string vazia pro nome e 0 anos de idade. Esses foram os valores iniciados dentro do construtor. Para colocar dados no objeto, fazemos assim, de forma básica:

    
    # Declaração de objetos:
    g1 = Gafanhoto()
    
    g1.nome = "Maria"
    g1.idade = 17
    
    print(g1.mensagem())
    
    

    Podemos utilizar os métodos assim:

    
    # Declaração de objetos:
    g1 = Gafanhoto()
    
    g1.nome = "Maria"
    g1.idade = 17
    
    g1.aniversario() # Adicionará 1 na idade
    
    print(g1.mensagem())
    
    

    Podemos criar outros objetos, onde um não terá vínculo com o outro, apesar de vierem da mesma classe:

    
    # Declaração de objetos:
    g1 = Gafanhoto()
    
    g1.nome = "Maria"
    g1.idade = 17
    
    g1.aniversario()
    
    print(g1.mensagem())
    
    g2 = Gafanhoto()
    
    g2.nome = "Mauro"
    g2.idade = 53
    
    print(g2.mensagem())
    
    

    No caso acima, os atributos e métodos serão manipulados apenas nos objetos que os chamaram, onde um não interfere no outro. Por isso que na classe têm o self, que significa si mesmo, ou seja, o próprio objeto que o chama.

    PS: Podemos criar as classes em módulos separados também.

    Melhorando Classes e Criando uma Conta Bancária na Prática

    Vamos salvar o código anterior num novo arquivo Python, e vamos melhorar esse código.

    Uma das melhorias vai ser usar métodos para manipular os atributos, pois o ideal é utilizar eles, ao invés de manipular os atributos diretamente.

    A primeira melhoria é no método construtor da classe Gafanhoto (__init__), no qual passaremos parâmetros para definir os atributos automaticamente, ao criar o objeto, assim:

    
    def __init__(self, n, i):
        # Atributos de instância
        self.nome = n
        self.idade = i
    
    

    Nesse caso, podemos passar diretamente os atributos a serem configurados na criação do objeto, assim:

    
    g1 = Gafanhoto("Maria", 17)
    
    g1.aniversario()
    
    print(g1.mensagem())
    
    g2 = Gafanhoto("Mauro", 53)
    
    print(g2.mensagem())
    
    

    Com isso, ele criará os objetos da mesma forma, mas com menos linhas e mais segurança.

    No caso acima, os parâmetros no construtor são obrigatório, mas eles podem ser opcionais também, pra isso, altere o construtor assim:

    
    def __init__(self, n = "vazio", i = 0):
        # Atributos de instância
        self.nome = n
        self.idade = i
    
    

    Dessa forma, caso não passe valores, ele receberá os valores predefinido no método construtor (no caso, a string vazio pra n e 0 para i.

    Podemos ver que, a alteração na classe já atualiza as características de todos os objetos oriundos do mesmo.

    Os parâmetros do construtor pode ter os mesmo nomes dos atributos da classe, assim:

    
    def __init__(self, nome = "vazio", idade = 0):
        # Atributos de instância
        self.nome = nome
        self.idade = idade
    
    

    No caso acima, o nome e idade dos parâmetros estão sozinhos, e não devem ser confundidos com o nome e idade atributos da classe (estes contém o self antes).

    PS: Para descobrir o nome da classe, podemos fazer isso:

    
    print(int)
    
    

    E para ver a documentação da classe especificada, basta colocar __doc__ na frente dela:

    
    print(int.__doc__)
    
    

    No entanto, isso só vale pra classes nativas do Python, por exemplo, int, str, tuple, list, e outras. Nos objetos e classes criados por nós (como a Gafanhoto), ele retornará None, dizendo que não tem manual, que deverá ser criados por nós mesmos. Para isso, basta colocar entre três aspas o código, nas primeiras linhas da classe, antes do restante do código, como se fazia pra documentar as funções, assim:

    
    class Gafanhoto:
        """
        Essa classe cria um Gafanhoto com nome e idade
        
        Para criar uma nova pessoa, use:
        variavel = Gafanhoto(nome, idade)
        """
    
    

    Ao mostrar um objeto puro, ele retorna o nome da classe e o endereço de memória dele, assim:

    
    print(g1)
    
    

    E ele mostrará algo tipo assim:

    
    <__main__.Gafanhoto object at 0x00000291C1876E40>
    
    

    Podemos personalizar a mensagem sobrescrevendo o método padrão das classes __str__, assim:

    
    def __str__(self):
        return "Vou te mostrar uma coisa."
    
    

    No caso, por padrão, o método __str__ mostra o nome e o endereço do objeto, mas no código acima sobrescrevemos o método e escrevemos a frase especificada acima. Aí, ao executar print(g1) novamente, ele exibirá essa frase escrita. Sabendo isso, podemos colocar os dados do objeto nesse mesmo método, pra serem exibidos por padrão:

    
    def __str__(self):
        return f"{self.nome} é Gafanhoto(a) e tem {self.idade} anos de idade."
    
    

    Assim, podemos remover tudo referente ao método mensagem, tanto na classe quanto nos objetos, e deixar o código assim:

    
    # Declaração de classe:
    class Gafanhoto:
        """
        Essa classe cria um Gafanhoto com nome e idade
    
        Para criar uma nova pessoa, use:
        variavel = Gafanhoto(nome, idade)
        """
        # Método Construtor, onde ficará nossos atributos
        def __init__(self, nome = "vazio", idade = 0):
            # Atributos de instância
            self.nome = nome
            self.idade = idade
    
        # Métodos de Instância
        def aniversario(self):
            self.idade = self.idade + 1
    
        # Métodos Sobrescritos
        def __str__(self):
            return f"{self.nome} é Gafanhoto(a) e tem {self.idade} anos de idade."
    
    # Declaração de objetos:
    g1 = Gafanhoto("Maria", 17)
    
    g1.aniversario()
    
    print(g1)
    
    g2 = Gafanhoto("Mauro", 53)
    
    print(g2)
    
    g3 = Gafanhoto()
    
    print(g3)
    
    

    Podemos mostrar os atributos do objeto em um dicionário, usando o atributo __dict__, assim:

    
    print(g1.__dict__)
    
    

    PS: Podemos usar o método __getstate__(), que funciona da mesma forma, só que este último é um método e pode ser sobrescrito na classe, assim:

    
    def __getstate__(self):
        return f"Estado:\n\nnome = {self.nome}\nidade = {self.idade}"
    
    

    E assim, isso é adicionado em todos os objetos oriundos dessa classe:

    
    # Declaração de objetos:
    g1 = Gafanhoto("Maria", 17)
    
    g1.aniversario()
    
    print(g1.__getstate__())
    
    g2 = Gafanhoto("Mauro", 53)
    
    print(g2.__getstate__())
    
    g3 = Gafanhoto()
    
    print(g3.__getstate__())
    
    

    Para saber o nome da classe da qual um objeto se origina, coloque o atributo __class__, assim:

    
    print(g1.__class__)
    
    

    Para exemplificar o que foi aprendido até agora, vamso criar uma classe simulando uma conta bancária, para isso, crie um novo arquivo para exercício e faça assim:

    
    class ContaBancaria:
        """
        Cria uma conta bancária e permite fazer saques e depósitos
        """
    
        # Construtor
        def __init__(self, id, nome, saldo = 0): # Apenas saldo é opcional, os outros são obrigatórios
            self.id = id
            self.titular = nome
            self.saldo = saldo
            print(f"Conta {self.id} criada com sucesso. Saldo atual de R${saldo:.2f}.")
    
    c1 = ContaBancaria(112, "Gustavo", 3000)
    
    print(c1)
    
    

    No caso acima, ele exibirá a classe e o endereço de memória do objeto, para ele exibir os dados do mesmo, sobrescreva na classe o método __str__ assim:

    
    def __str__(self):
        return f"A conta {self.id} de {self.titular} tem R${self.saldo:.2f} de saldo."
    
    

    Podemos exibir o __doc__ assim:

    
    print(c1.__doc__)
    
    

    Agora, defina esses métodos de instância na classe, que por enquanto ficarão com o bloco vazio (que pro Python, deverão ter dentro apenas a palavra pass), assim:

    
    # Métodos de Instância:
    def depositar(self, valor):
        pass
    
    def sacar(self, valor):
        pass
    
    

    Aí, pra chamar os métodos, fazemos assim:

    
    c1 = ContaBancaria(112, "Gustavo", 3000)
    
    c1.depositar(500)
    c1.sacar(2000)
    
    print(c1)
    
    

    Só que, claro, ele não fará nada, para ele depositar o valor, fazemos assim no método depositar e sacar:

    
    def depositar(self, valor):
        self.saldo += valor
        print(f"Depósito de R${valor:.2f} autorizado na conta {self.id}.")
    
    def sacar(self, valor):
        self.saldo -= valor
        print(f"Saque de R${valor:.2f} autorizado na conta {self.id}.")
    
    

    Claro que isso é um código básico, ainda necessita várias melhorias, um exemplo é de sacar mais dinheiro do que tem na conta, por exemplo:

    
    c1 = ContaBancaria(112, "Gustavo", 3000)
    
    c1.depositar(500)
    c1.sacar(2_000_000) # É o mesmo que 2000000
    
    print(c1)
    
    

    No caso acima, ele sacará e ficará com um saldo negativo, mas não é assim que as contas funcionam na prática. Para isso, deveremos fazer métodos mais robustos. Veja como ficaria o método sacar, com a correção:

    
    def sacar(self, valor):
        if valor > self.saldo:
            print(f"Saque de R${valor:.2f} negado na conta {self.id}. Saldo insuficiente.")
        else:
            self.saldo -= valor
            print(f"Saque de R${valor:.2f} autorizado na conta {self.id}.")
    
    

    PS: Podemos fazer outras melhorias, como colocar cores, por exemplo.

    Rich no Python: Colocando a Biblioteca na Nossa Classe

    Na classe ContaBancaria, coloque as importações da biblioteca Rich assim:

    
    from rich import print
    from rich import inspect
    
    

    E depois crie um objeto assim, e exiba ele com o inspect:

    
    c = ContaBancaria(111, "José", 500)
    
    inspect(c) # É melhor visualmente do que print(c.__getstate__())
    
    

    Ele mostrará todos os dados que colocamos anteriormente na classe, incluindo a documentação. Não esqueça de colocar a opção Emulate Terminal no Run With Parameters.