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))
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.
Alguns objetos imutáveis no Python:
int
str
complex
bool
bytes
frozenset
tuple
Alguns objetos mutáveis no Python:
list
dict
set
bytearray
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')
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')
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 peloself
. 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 pelocls
, abreviação paraclass
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.