Skip to content

Latest commit

 

History

History

modulo08-oop

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Programação Orientada a Objetos

Objetos imutáveis, valor e referência

No Python ao declarar e inicializar uma variável, por inferência de tipos, ele sabe qual o tipo dessa variável.

x = 10
y = 10

No exemplo acima, o Python vai reservar um espaço na mémoria e armazenar o valor inteiro 10 e vai atualizar a sua tabela de símbilos, que diz para qual objeto um símbilo aponta. Nesse exemplo temos as variáveis x e y, onde as duas tem o mesmo valor. No caso do tipo inteiro, em Python ele é um tipo imutável, sendo assim, a implementação do Python vai fazer com que na tabela de símbolos, tanto x quanto y apontem para o mesmo endereço de memória.

Podemos ver qual o endereço de memória de uma variáviel com a função id(x):

print(id(x))
print(id(y))
<style> table th:first-of-type { width: 10%; } table th:nth-of-type(2) { width: 10%; } table th:nth-of-type(3) { width: 50%; } table th:nth-of-type(4) { width: 30%; } td { text-align: center; border: 1px solid #3e3e3e40 } </style>
Símbolo Objeto (Endereço de Memória)
x 0
y 0

Quando fazemos, por exemplo um incremento em uma variável, x += 1, o que vai acontecer é que o Python vai alocar um novo espaço de memória para armazenar o novo valor que agora é 11, sem alterar o anterior.

Dessa forma a tabela de símbolos é atualizada para:

Símbolo Objeto (Endereço de Memória)
x 1
y 0

E se tentarmos ver os endereços de memória das variáveis, apenas x teve alteração;

print(id(x))
print(id(y))

Dessa forma o Python consegue economizar memória, pois uma vez que variáveis recebem valores imutavéis que outras já estão utilizando, elas irão apontar para um mesmo endereço de memória.

Agora digamos que y = 11 e passou a apontar para esse novo endereço de memória, que também é o mesmo de x, o espaço alocado em mémoria para o valor 10 será retirado da mémoria pelo Garbage Collector do Python, pois não tem mais nenhuma variável apontando para ele.

Objetos Imutáveis

Alguns objetos imutáveis no Python:

  • int
  • str
  • complex
  • bool
  • bytes
  • frozenset
  • tuple

Objetos Mutáveis

Alguns objetos mutáveis no Python:

  • list
  • dict
  • set
  • bytearray

Classes

Definindo uma classe

Para definir uma classe em Python usamos a palavra reservada class:

class Evento:
    pass

Poderiamos ...(Ellipsis) no lugar de pass, mas há diferença entre eles que você pode conferir aqui.

Dessa forma podemos criar instâncias dessa classe:

ev1 = Evento()
ev1.nome = "Classe 1"
ev2 = Evento()
ev2.nome = "Classe 2"

Em Python, mesmo não tendo o atribuito nome na classe, podemos criá-los dinamicamente, de forma similar a um objeto Javascript.

E objetos que instanciamos a partir de classes que definimos, por padrão eles são objetos mutáveis. Dessa forma cada uma da instância que criamos, aponta para um endereço de memória diferente:

print(id(ev1))
print(id(ev2))

Se criarmos uma função para alterar o nome, podemos fazer:

def alterar_nome(evento, novo_nome):
    evento.nome = novo_nome

alterar_nome(ev1, 'Novo nome da classe 1')

Mas quando trabalhos com classes, a ideia é que seus atributos e métodos estajam agrupados. Então podemos refatorar a nossa classe, onde a função de alterar_nome passará a ser um método da classe Evento:

Porém, temos algumas convenções a serem seguidas:

  • O primeiro argumento de um método da classe sempre será uma referência da instância dessa classe, chamado self.
  • Seguindo dos outros possíveis argumentos.
  • Para utilizar esse método o Python já passa por padrão o parâmetro da instância da classe que chamou o metódo, dessa forma a chamada ficaria algo como ev1.alterar_nome('Meu novo nome').
class Evento:
    def alterar_nome(self, novo_nome):
        self.nome = novo_nome

ev1 = Evento()
ev1.nome = "Classe 1"

ev1.alterar_nome('Meu novo nome')

Construtores

Dada classe, se tentar acessar o atributo nome:

class Evento:
    def alterar_nome(self, novo_nome):
        self.nome = novo_nome

ev1 = Evento()
print(ev1.nome)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Evento' object has no attribute 'nome'

Teremos um erro, pois o atributo não definido. No casos anteriores fizemos isso explicitamente. Para garantirmos que toda instância dessa classe tenha o atributo nome, devemos usar contrutores.

Um construtor é definido pelo método __int__, onde o primeiro argumento é o self, seguidos dos demais argumentos que deseja que o seu objeto tenha for instânciado:

class Evento:
    def __init__(self, nome):
        self.nome = nome


    def alterar_nome(self, novo_nome):
        self.nome = novo_nome

Dessa forma se tentarmos criar uma nova instância dessa classe, temos um erro:

ev1 = Evento()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __init__() missing 1 required positional argument: 'nome'

Pois agora somos obrigados a informar esse parâmetro quando tentamos criar uma nova instância:

ev1 = Evento('Teste Nome')

Métodos de classes e métodos estáticos

Quando falamos sobre métodos ou funções de uma classe em Python, temos três tipos:

  • Método de instância:

    class Evento:
        def metodo_instancia(self):
            return ("Método de instância chamado", self)
    
    ev = Evento()
    ev.metodo_instancia() # Evento.metodo_instancia(ev)

    Por padrão, os métodos definidos dentro de uma classe, serão métodos de instância e recebem o self como primeiro argumento. Recebe como argumento a instância o objeto dessa classe pelo self. E é chamdo a partir de uma instância de um objeto desssa classe.

  • Método de classe:

    class Evento:
        @classmethod
        def metodo_classe(cls):
            return ("Método de classe chamado", cls)
    
    Evento.metodo_classe() # Evento.metodo_classe(Evento)

    É criado com decorator @classmethod, logo acima da definição do método. Assim o Python sabe que esse método recebe com primeiro argumento, uma referência da classe. Recebe como argumento a referencia da classe pelo cls, abreviação para class no Python. E é chamdo a partir de uma instância de uma classe ou sem a necessidade de criar uma instância. Entras linguagens de programação, temos a opção de sobreescrever construtores, assim temos forma de instanciar um objeto, dada a situação de quais argumentos temos ou queremos iniciar. Mas no Python não temos essa possibilidade, para saber mais clique aqui. Por isso utilizamos o @classmethod, como opções de construtores que recebem outras opções de argumentos.

  • Método estático:

    class Evento:
        @staticmethod
        def metodo_estatico():
            return ("Método estático chamado")
    
    Evento.metodo_classe() # Evento.metodo_estatico()

    É criado com decorator @staticmethod, logo acima da definição do método. Esse tipo de método, pode ou não, receber argumentos. E é chamdo a partir de uma instância de uma classe ou sem a necessidade de criar uma instância.