Als je al een tijdje in Python (objectgeoriënteerd programmeren) programmeert, dan ben je zeker methoden tegengekomen die self
als eerste parameter hebben.
Laten we eerst proberen te begrijpen wat deze terugkerende zelfparameter is.
Wat is zelf in Python?
Bij objectgeoriënteerd programmeren gebruiken we telkens als we methoden voor een klasse definiëren self
als de eerste parameter. Laten we eens kijken naar de definitie van een klasse genaamd 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 dit geval hebben alle methoden, inclusief __init__
, de eerste parameter als self
.
We weten dat klasse een blauwdruk is voor de objecten. Deze blauwdruk kan worden gebruikt om meerdere aantallen objecten te maken. Laten we twee verschillende objecten maken uit de bovenstaande klasse.
cat1 = Cat('Andy', 2) cat2 = Cat('Phoebe', 3)
Het self
sleutelwoord wordt gebruikt om een instantie (object) van de gegeven klasse te vertegenwoordigen. In dit geval zijn de twee Cat
objecten cat1
en cat2
hebben hun eigen name
en age
attributen. Als er geen zelf-argument was, zou dezelfde klasse de informatie voor beide objecten niet kunnen bevatten.
Omdat de klasse echter slechts een blauwdruk is, krijgt self
u toegang tot de attributen en methoden van elk object in python. Hierdoor kan elk object zijn eigen attributen en methoden hebben. Dus zelfs lang voordat we deze objecten maken, verwijzen we naar de objecten self
terwijl we de klasse definiëren.
Waarom wordt zelf altijd expliciet gedefinieerd?
Zelfs als we het gebruik van begrijpen self
, kan het nog steeds vreemd overkomen, vooral voor programmeurs die uit andere talen komen, dat dit self
expliciet als parameter wordt doorgegeven elke keer dat we een methode definiëren. Zoals The Zen of Python zegt: " Expliciet is beter dan impliciet ".
Dus waarom moeten we dit doen? Laten we om te beginnen een eenvoudig voorbeeld nemen. We hebben een Point
klasse die een methode definieert distance
om de afstand vanaf de oorsprong te berekenen.
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
Laten we nu deze klasse instantiëren en de afstand bepalen.
>>> p1 = Point(6,8) >>> p1.distance() 10.0
Definieert in het bovenstaande voorbeeld __init__()
drie parameters, maar we hebben er net twee gepasseerd (6 en 8). Evenzo distance()
vereist één, maar er zijn nul argumenten doorgegeven. Waarom klaagt Python niet over dit niet-overeenkomende argumentnummer?
Wat gebeurt er intern?
Point.distance
en p1.distance
in het bovenstaande voorbeeld zijn verschillend en niet precies hetzelfde.
>>> type(Point.distance) >>> type(p1.distance)
We kunnen zien dat de eerste een functie is en de tweede een methode. Bijzonder aan methoden (in Python) is dat het object zelf als eerste argument wordt doorgegeven aan de corresponderende functie.
In het geval van het bovenstaande voorbeeld is de methodeaanroep p1.distance()
eigenlijk gelijk aan Point.distance(p1)
.
Als we een methode met enkele argumenten aanroepen, wordt in het algemeen de overeenkomstige klassefunctie aangeroepen door het object van de methode voor het eerste argument te plaatsen. Dus zoiets obj.meth(args)
wordt Class.meth(obj, args)
. Het aanroepproces is automatisch terwijl het ontvangende proces dat niet is (het is expliciet).
Dit is de reden waarom de eerste parameter van een functie in de klasse het object zelf moet zijn. Het schrijven van deze parameter zoals het self
slechts een afspraak is. Het is geen trefwoord en heeft geen speciale betekenis in Python. We zouden andere namen kunnen gebruiken (zoals this
), maar het wordt sterk afgeraden. Het gebruik van andere namen dan self
wordt door de meeste ontwikkelaars afgekeurd en verslechtert de leesbaarheid van de code ( leesbaarheid telt ).
Zelf kan worden vermeden
Je bent nu duidelijk dat het object (instantie) zelf automatisch als eerste argument wordt doorgegeven. Dit impliciete gedrag kan worden vermeden bij het maken van een statische methode. Beschouw het volgende eenvoudige voorbeeld:
class A(object): @staticmethod def stat_meth(): print("Look no self was passed")
Hier @staticmethod
is een functie-decorateur die stat_meth()
statisch maakt . Laten we deze klasse instantiëren en de methode aanroepen.
>>> a = A() >>> a.stat_meth() Look no self was passed
Uit het bovenstaande voorbeeld kunnen we zien dat het impliciete gedrag van het doorgeven van het object als het eerste argument werd vermeden bij het gebruik van een statische methode. Al met al gedragen statische methoden zich als de gewone oude functies (aangezien alle objecten van een klasse statische methoden delen).
>>> type(A.stat_meth) >>> type(a.stat_meth)
Zelf is hier om te blijven
De expliciete self
is niet uniek voor Python. Dit idee is ontleend aan Modula-3 . Hieronder volgt een use-case waarin het nuttig wordt.
Er is geen expliciete variabele declaratie in Python. Ze komen in actie bij de eerste opdracht. Het gebruik van self
maakt het gemakkelijker om onderscheid te maken tussen instantiekenmerken (en methoden) en lokale variabelen.
In het eerste voorbeeld is self.x een instantiekenmerk terwijl x een lokale variabele is. Ze zijn niet hetzelfde en ze liggen in verschillende naamruimten.
Velen hebben voorgesteld om van zichzelf een trefwoord te maken in Python, zoals this
in C ++ en Java. Dit zou het overtollige gebruik van expliciet self
uit de formele parameterlijst in methoden elimineren .
Hoewel dit idee veelbelovend lijkt, gaat het niet gebeuren. In ieder geval niet in de nabije toekomst. De belangrijkste reden is achterwaartse compatibiliteit. Hier is een blog van de maker van Python zelf waarin hij uitlegt waarom het expliciete zelf moet blijven.
__init __ () is geen constructor
Een belangrijke conclusie die tot nu toe uit de informatie kan worden getrokken, is dat de __init__()
methode geen constructor is. Veel naïeve Python-programmeurs raken ermee in de war, omdat ze __init__()
worden gebeld wanneer we een object maken.
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)
Een voorbeeldrun:
>>> 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