2.1.1. Classes em Python¶
Todos os valores são objetos em Python, dos mais básicos, como inteiros e booleanos, aos tipos mais complexos definidos por usuários, incluindo as funções (objetos do tipo função). Como vimos anteriormente, objetos são instâncias de classes, então para criar objetos, é preciso antes definir as classes. Para definir classes em Python, usa-se o operador class, seguido do nome da classe e dois pontos. No bloco de código associado à classe, pode-se declarar variáveis e funções. O código abaixo declara a classe MyClass com uma variável e uma função interna, chamadas my_variable e my_func respectivamente. O parâmetro self declarado na função func será explicado mais à frente.
class MyClass:
my_variable = 10
def my_func(self):
print('hello world')
Para criar um objeto da classe MyClass, usa-se a mesma sintaxe de chamada de funções:
my_object = MyClass()
Com isso, a variável my_object agora representa uma instância, i.e. um objeto, da classe MyClass. Portanto, my_object tem acesso tanto à variável my_variable, quanto à função my_func, sendo possível acessá-las como segue:
print(my_object.my_variable)
10
my_object.my_func()
hello world
É possível criar múltiplos objetos da mesma classe. Cada um deles conterá cópias independentes das variáveis e funções definidas na classe. Por exemplo, podemos criar mais um objeto da classe MyClass, chamado b e mudar o valor de my_variable:
b = MyClass()
b.my_variable += 5
print(my_object.my_variable, b.my_variable)
10 15
Assim, my_variable representa um atributo dos objetos da classe MyClass. Como é esperado que diferentes objetos tenham valores diferentes em seus atributos, seria incoveniente se fosse necessário modificar os valores dos atributos após a criação de cada objeto, em outras palavras seria mais interessante que o valor dos atributos pudesse ser informado no momento da criação do objeto. Para isso, usa-se um método especial, chamado __init__, também conhecido como construtor. Uma classe com construtor pode ser definida da seguinte forma:
class MyClass:
def __init__(self, value):
self.my_variable = value
def my_func(self):
print('hello world')
Quando uma classe é definida com um método __init__, a criação de novos objetos da classe automaticamente invoca __init__ para cada novo objeto. Assim, uma nova instância pode ser criada como:
b = MyClass(3)
print(b.my_variable)
3
O primeiro parâmetro dos métodos __init__ e my_func é o mesmo: self. O parâmetro self é uma referência ao próprio objeto, permitindo acesso aos identificadores definidos no namespace do objeto. No método __init__, a linha self.my_variable = value cria o atributo my_variable no namespace do objeto, com o valor passado como argumento. É isso que permite que o valor do atributo seja acessado como b.my_variable. Se uma outra variável fosse declarada dentro do método __init__, mas sem associá-la ao self, ela não ficaria acessível fora do método. Exemplo:
class MyClass:
def __init__(self, value):
self.my_variable = value
other_variable = 10
def my_func(self):
print('hello world')
b = MyClass(3)
print(b.other_variable)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-8-b0b40be46e38> in <module>
8
9 b = MyClass(3)
---> 10 print(b.other_variable)
AttributeError: 'MyClass' object has no attribute 'other_variable'
Note que ao chamar um método de um objeto, não se deve passar um valor para o parâmetro self. Quando o método é chamado, self automaticamente se torna uma referência ao objeto associado. Além disso, self não é uma palavra reservada e não é obrigatório que o primeiro parâmetro chame-se self. Isso é apenas uma convenção.
class MyClass:
def __init__(self, value):
self.my_variable = value
other_variable = 10
def my_func(self):
print('hello world')
def my_other_func(test, value):
print('hi {}'.format(value))
b = MyClass(3)
b.my_other_func('everyone')
hi everyone
Caso um método seja definido sem parâmetros, não haverá um primeiro parâmetro para atribuir a referência do objeto. Como consequência, o método não fará parte do namespace do objeto. Nesses casos, o método fica associado apenas ao namespace da classe. Exemplo:
class MyClass:
def __init__(self, value):
self.my_variable = value
other_variable = 10
def my_func(self):
print('hello world')
def my_other_func(test, value):
print('hi {}'.format(value))
def my_third_func():
print('hi world')
def my_fourth_func(value1, value2):
print(value1 + value2)
b = MyClass(3)
b.my_third_func()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-10-be0ef913fc24> in <module>
17
18 b = MyClass(3)
---> 19 b.my_third_func()
TypeError: my_third_func() takes 0 positional arguments but 1 was given
MyClass.my_third_func()
hi world
O quarto método acima, my_fourth_function, possui dois parâmetros. Portanto, se ele for chamado por meio de um objeto, o primeiro parâmetro será atribuído à referência do objeto, o que gerará um erro de quantidade incorreta de parâmetros:
b.my_fourth_func(10, 5)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-12-4071b474f7a3> in <module>
----> 1 b.my_fourth_func(10, 5)
TypeError: my_fourth_func() takes 2 positional arguments but 3 were given
Note que o método existe no namespace do objeto, mas nesse caso ele associa o parâmetro value1 à referência do objeto e espera apenas que o parâmetro value2 receba algum valor. No namespace da classe, no entanto, o método pode ser chamado com um valor para cada parâmetro:
MyClass.my_fourth_func(10, 5)
15
Python permite a “injeção” dinâmica de atributos. Ou seja, é possível atribuir um valor a um atributo que não foi declarado na definição da classe. Exemplo:
b.new_attribute = 23
print(b.new_attribute)
23