Se programmi da tempo in Python (programmazione orientata agli oggetti), ti sei sicuramente imbattuto in metodi che hanno self
come primo parametro.
Proviamo prima a capire qual è questo parametro del sé ricorrente.
Cos'è il sé in Python?
Nella programmazione orientata agli oggetti, ogni volta che definiamo metodi per una classe, li usiamo self
come primo parametro in ogni caso. Diamo un'occhiata alla definizione di una classe chiamata 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")
In questo caso tutti i metodi, incluso __init__
, hanno il primo parametro come self
.
Sappiamo che la classe è un modello per gli oggetti. Questo progetto può essere utilizzato per creare più numeri di oggetti. Creiamo due oggetti diversi dalla classe precedente.
cat1 = Cat('Andy', 2) cat2 = Cat('Phoebe', 3)
La self
parola chiave viene utilizzata per rappresentare un'istanza (oggetto) della classe data. In questo caso, i due Cat
oggetti cat1
e cat2
hanno i propri attributi name
e age
. Se non ci fossero argomenti personali, la stessa classe non potrebbe contenere le informazioni per entrambi questi oggetti.
Tuttavia, poiché la classe è solo un progetto, self
consente l'accesso agli attributi e ai metodi di ogni oggetto in python. Ciò consente a ogni oggetto di avere i propri attributi e metodi. Pertanto, anche molto prima di creare questi oggetti, facciamo riferimento agli oggetti come self
durante la definizione della classe.
Perché il sé viene definito esplicitamente ogni volta?
Anche quando ne comprendiamo l'uso self
, può ancora sembrare strano, soprattutto ai programmatori provenienti da altri linguaggi, che self
viene passato come parametro esplicitamente ogni volta che definiamo un metodo. Come dice The Zen of Python , " Explicit è meglio di implicit ".
Allora, perché dobbiamo farlo? Facciamo un semplice esempio per cominciare. Abbiamo una Point
classe che definisce un metodo distance
per calcolare la distanza dall'origine.
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
Ora istanziamo questa classe e troviamo la distanza.
>>> p1 = Point(6,8) >>> p1.distance() 10.0
Nell'esempio sopra, __init__()
definisce tre parametri ma ne abbiamo appena passati due (6 e 8). Allo stesso modo distance()
richiede uno ma sono stati passati zero argomenti. Perché Python non si lamenta di questa mancata corrispondenza del numero di argomenti?
Cosa succede internamente?
Point.distance
e p1.distance
nell'esempio sopra sono diversi e non esattamente uguali.
>>> type(Point.distance) >>> type(p1.distance)
Possiamo vedere che la prima è una funzione e la seconda è un metodo. Una cosa peculiare dei metodi (in Python) è che l'oggetto stesso viene passato come primo argomento alla funzione corrispondente.
Nel caso dell'esempio precedente, la chiamata al metodo p1.distance()
è effettivamente equivalente a Point.distance(p1)
.
Generalmente, quando chiamiamo un metodo con alcuni argomenti, la funzione di classe corrispondente viene chiamata posizionando l'oggetto del metodo prima del primo argomento. Quindi, qualsiasi cosa come obj.meth(args)
diventa Class.meth(obj, args)
. Il processo di chiamata è automatico mentre il processo di ricezione non lo è (è esplicito).
Questo è il motivo per cui il primo parametro di una funzione in classe deve essere l'oggetto stesso. Scrivere questo parametro come self
è semplicemente una convenzione. Non è una parola chiave e non ha alcun significato speciale in Python. Potremmo usare altri nomi (come this
) ma è altamente sconsigliato. L'uso di nomi diversi da self
è disapprovato dalla maggior parte degli sviluppatori e riduce la leggibilità del codice (la leggibilità conta ).
Il sé può essere evitato
Ormai è chiaro che l'oggetto (istanza) stesso viene passato come primo argomento, automaticamente. Questo comportamento implicito può essere evitato durante la creazione di un metodo statico . Considera il seguente semplice esempio:
class A(object): @staticmethod def stat_meth(): print("Look no self was passed")
Ecco @staticmethod
un decoratore di funzioni che rende stat_meth()
statico. Istanziamo questa classe e chiamiamo il metodo.
>>> a = A() >>> a.stat_meth() Look no self was passed
Dall'esempio precedente, possiamo vedere che il comportamento implicito di passare l'oggetto come primo argomento è stato evitato durante l'utilizzo di un metodo statico. Tutto sommato, i metodi statici si comportano come le semplici vecchie funzioni (poiché tutti gli oggetti di una classe condividono metodi statici).
>>> type(A.stat_meth) >>> type(a.stat_meth)
Il sé è qui per restare
L'esplicito self
non è esclusivo di Python. Questa idea è stata presa in prestito da Modula-3 . Di seguito è riportato un caso d'uso in cui diventa utile.
Non esiste una dichiarazione di variabile esplicita in Python. Entrano in azione al primo incarico. L'uso di self
rende più facile distinguere tra attributi di istanza (e metodi) dalle variabili locali.
Nel primo esempio, self.x è un attributo di istanza mentre x è una variabile locale. Non sono la stessa cosa e si trovano in spazi dei nomi diversi.
Molti hanno proposto di rendere self una parola chiave in Python, come this
in C ++ e Java. Ciò eliminerebbe l'uso ridondante di esplicito self
dall'elenco di parametri formali nei metodi.
Anche se questa idea sembra promettente, non accadrà. Almeno non nel prossimo futuro. Il motivo principale è la compatibilità con le versioni precedenti. Ecco un blog del creatore di Python stesso che spiega perché il sé esplicito deve rimanere.
__init __ () non è un costruttore
Una conclusione importante che si può trarre dalle informazioni fino ad ora è che il __init__()
metodo non è un costruttore. Molti programmatori Python ingenui si confondono con esso poiché __init__()
viene chiamato quando creiamo un oggetto.
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)
Una corsa di esempio:
>>> 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