Se você já programa em Python (programação orientada a objetos) há algum tempo, certamente já se deparou com métodos que têm self
como primeiro parâmetro.
Vamos primeiro tentar entender o que é esse parâmetro próprio recorrente.
O que é self em Python?
Na programação orientada a objetos, sempre que definimos métodos para uma classe, usamos self
como o primeiro parâmetro em cada caso. Vejamos a definição de uma classe chamada Cat
.
class Cat: def __init__(self, name, age): self.name = name self.age = age def info(self): print(f"I am a cat. My name is (self.name). I am (self.age) years old.") def make_sound(self): print("Meow")
Nesse caso, todos os métodos, inclusive __init__
, têm o primeiro parâmetro como self
.
Sabemos que a classe é um projeto para os objetos. Este blueprint pode ser usado para criar vários números de objetos. Vamos criar dois objetos diferentes da classe acima.
cat1 = Cat('Andy', 2) cat2 = Cat('Phoebe', 3)
A self
palavra-chave é usada para representar uma instância (objeto) da classe dada. Neste caso, os dois Cat
objetos cat1
e cat2
têm seus próprios atributos name
e age
. Se não houvesse argumento próprio, a mesma classe não poderia conter as informações para esses dois objetos.
No entanto, como a classe é apenas um blueprint, self
permite acesso aos atributos e métodos de cada objeto em python. Isso permite que cada objeto tenha seus próprios atributos e métodos. Portanto, muito antes de criar esses objetos, referimo-nos aos objetos self
enquanto definimos a classe.
Por que o self é definido explicitamente todas as vezes?
Mesmo quando entendemos o uso de self
, ainda pode parecer estranho, especialmente para programadores vindos de outras linguagens, que self
seja passado como um parâmetro explicitamente toda vez que definimos um método. Como diz The Zen of Python , " Explícito é melhor do que implícito ".
Então, por que precisamos fazer isso? Vamos dar um exemplo simples para começar. Temos uma Point
classe que define um método distance
para calcular a distância da origem.
class Point(object): def __init__(self,x = 0,y = 0): self.x = x self.y = y def distance(self): """Find distance from origin""" return (self.x**2 + self.y**2) ** 0.5
Vamos agora instanciar esta classe e encontrar a distância.
>>> p1 = Point(6,8) >>> p1.distance() 10.0
No exemplo acima, __init__()
define três parâmetros, mas acabamos de passar dois (6 e 8). Da mesma forma distance()
requer um, mas nenhum argumento foi passado. Por que o Python não está reclamando sobre essa incompatibilidade de número de argumento?
O que acontece internamente?
Point.distance
e p1.distance
no exemplo acima são diferentes e não exatamente iguais.
>>> type(Point.distance) >>> type(p1.distance)
Podemos ver que o primeiro é uma função e o segundo é um método. Uma coisa peculiar sobre métodos (em Python) é que o próprio objeto é passado como o primeiro argumento para a função correspondente.
No caso do exemplo acima, a chamada do método p1.distance()
é realmente equivalente a Point.distance(p1)
.
Geralmente, quando chamamos um método com alguns argumentos, a função de classe correspondente é chamada colocando o objeto do método antes do primeiro argumento. Então, qualquer coisa assim obj.meth(args)
se torna Class.meth(obj, args)
. O processo de chamada é automático, enquanto o processo de recebimento não é (é explícito).
Esta é a razão pela qual o primeiro parâmetro de uma função na classe deve ser o próprio objeto. Escrever este parâmetro self
é apenas uma convenção. Não é uma palavra-chave e não tem nenhum significado especial em Python. Poderíamos usar outros nomes (como this
), mas é altamente desencorajado. Usar outros nomes que não self
seja desaprovado pela maioria dos desenvolvedores e prejudica a legibilidade do código ( contagens de legibilidade ).
Auto pode ser evitado
Agora você já sabe que o próprio objeto (instância) é passado automaticamente como o primeiro argumento. Este comportamento implícito pode ser evitado ao fazer um método estático . Considere o seguinte exemplo simples:
class A(object): @staticmethod def stat_meth(): print("Look no self was passed")
Aqui, @staticmethod
está um decorador de função que torna stat_meth()
estático. Vamos instanciar essa classe e chamar o método.
>>> a = A() >>> a.stat_meth() Look no self was passed
A partir do exemplo acima, podemos ver que o comportamento implícito de passar o objeto como o primeiro argumento foi evitado ao usar um método estático. De modo geral, os métodos estáticos se comportam como as antigas funções simples (já que todos os objetos de uma classe compartilham métodos estáticos).
>>> type(A.stat_meth) >>> type(a.stat_meth)
Eu está aqui para ficar
O explícito self
não é exclusivo do Python. Esta ideia foi emprestada do Modula-3 . A seguir está um caso de uso em que se torna útil.
Não há declaração de variável explícita em Python. Eles entram em ação na primeira tarefa. O uso de self
torna mais fácil distinguir entre atributos de instância (e métodos) de variáveis locais.
No primeiro exemplo, self.x é um atributo de instância enquanto x é uma variável local. Eles não são iguais e estão em namespaces diferentes.
Muitos propuseram tornar-se uma palavra-chave em Python, como this
em C ++ e Java. Isso eliminaria o uso redundante de explícito self
da lista formal de parâmetros nos métodos.
Embora essa ideia pareça promissora, ela não vai acontecer. Pelo menos não no futuro próximo. O principal motivo é a compatibilidade com versões anteriores. Aqui está um blog do próprio criador do Python explicando por que o eu explícito precisa permanecer.
__init __ () não é um construtor
Uma conclusão importante que pode ser tirada das informações até agora é que o __init__()
método não é um construtor. Muitos programadores Python ingênuos se confundem com ele, pois __init__()
é chamado quando criamos um objeto.
A closer inspection will reveal that the first parameter in __init__()
is the object itself (object already exists). The function __init__()
is called immediately after the object is created and is used to initialize it.
Technically speaking, a constructor is a method which creates the object itself. In Python, this method is __new__()
. A common signature of this method is:
__new__(cls, *args, **kwargs)
When __new__()
is called, the class itself is passed as the first argument automatically(cls
).
Again, like self, cls is just a naming convention. Furthermore, *args and **kwargs are used to take an arbitrary number of arguments during method calls in Python.
Some important things to remember when implementing __new__()
are:
__new__()
is always called before__init__()
.- First argument is the class itself which is passed implicitly.
- Always return a valid object from
__new__()
. Not mandatory, but its main use is to create and return an object.
Let's take a look at an example:
class Point(object): def __new__(cls,*args,**kwargs): print("From new") print(cls) print(args) print(kwargs) # create our object and return it obj = super().__new__(cls) return obj def __init__(self,x = 0,y = 0): print("From init") self.x = x self.y = y
Now, let's now instantiate it.
>>> p2 = Point(3,4) From new (3, 4) () From init
This example illustrates that __new__()
is called before __init__()
. We can also see that the parameter cls in __new__()
is the class itself (Point
). Finally, the object is created by calling the __new__()
method on object base class.
In Python, object
is the base class from which all other classes are derived. In the above example, we have done this using super().
Use __new__ or __init__?
You might have seen __init__()
very often but the use of __new__()
is rare. This is because most of the time you don't need to override it. Generally, __init__()
is used to initialize a newly created object while __new__()
is used to control the way an object is created.
We can also use __new__()
to initialize attributes of an object, but logically it should be inside __init__()
.
One practical use of __new__()
, however, could be to restrict the number of objects created from a class.
Suppose we wanted a class SqPoint
for creating instances to represent the four vertices of a square. We can inherit from our previous class Point
(the second example in this article) and use __new__()
to implement this restriction. Here is an example to restrict a class to have only four instances.
class SqPoint(Point): MAX_Inst = 4 Inst_created = 0 def __new__(cls,*args,**kwargs): if (cls.Inst_created>= cls.MAX_Inst): raise ValueError("Cannot create more objects") cls.Inst_created += 1 return super().__new__(cls)
Um exemplo de execução:
>>> p1 = SqPoint(0,0) >>> p2 = SqPoint(1,0) >>> p3 = SqPoint(1,1) >>> p4 = SqPoint(0,1) >>> >>> p5 = SqPoint(2,2) Traceback (most recent call last):… ValueError: Cannot create more objects