jag själv i Python, avmystifierad

Om du har programmerat i Python (objektorienterad programmering) under en tid, har du definitivt stött på metoder som har selfsom sin första parameter.

Låt oss först försöka förstå vad denna återkommande självparameter är.

Vad är jag i Python?

I objektorienterad programmering använder vi varje gång vi definierar metoder för en klass selfsom den första parametern. Låt oss titta på definitionen av en klass som heter 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")

I detta fall har alla metoder, inklusive __init__, den första parametern som self.

Vi vet att klassen är en ritning för objekten. Denna ritning kan användas för att skapa flera antal objekt. Låt oss skapa två olika objekt från ovanstående klass.

 cat1 = Cat('Andy', 2) cat2 = Cat('Phoebe', 3)

Den selfnyckelordet används för att representera en instans (objekt) hos den givna klassen. I detta fall är de två Catobjekten cat1och cat2har sin egen nameoch ageattribut. Om det inte fanns något självargument kunde samma klass inte innehålla informationen för båda dessa objekt.

Eftersom klassen bara är en ritning selfger den åtkomst till attributen och metoderna för varje objekt i python. Detta gör att varje objekt kan ha sina egna attribut och metoder. Så länge innan vi skapar dessa objekt refererar vi till objekten som selfnär vi definierar klassen.

Varför definieras själv uttryckligen varje gång?

Även när vi förstår användningen av selfkan det fortfarande verka konstigt, särskilt för programmerare som kommer från andra språk, som selfskickas som en parameter uttryckligen varje gång vi definierar en metod. Som Zen of Python säger, " Explicit är bättre än implicit ".

Så varför behöver vi göra det här? Låt oss ta ett enkelt exempel till att börja med. Vi har en Pointklass som definierar en metod för distanceatt beräkna avståndet från ursprunget.

 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

Låt oss nu starta den här klassen och hitta avståndet.

 >>> p1 = Point(6,8) >>> p1.distance() 10.0

I exemplet ovan __init__()definierar vi tre parametrar men vi har precis passerat två (6 och 8). På samma sätt distance()kräver ett men noll argument skickades. Varför klagar inte Python på att det här argumentet inte stämmer överens?

Vad händer internt?

Point.distanceoch p1.distancei exemplet ovan är olika och inte exakt desamma.

 >>> type(Point.distance) >>> type(p1.distance) 

Vi kan se att den första är en funktion och den andra är en metod. En märklig sak om metoder (i Python) är att själva objektet skickas som det första argumentet till motsvarande funktion.

I fallet med ovanstående exempel p1.distance()motsvarar metodanropet faktiskt Point.distance(p1).

Generellt när vi kallar en metod med några argument kallas motsvarande klassfunktion genom att placera metodens objekt före det första argumentet. Så, vad som helst obj.meth(args)blir Class.meth(obj, args). Samtalsprocessen är automatisk medan mottagningsprocessen inte är (dess uttryckliga).

Detta är anledningen till att den första parametern för en funktion i klassen måste vara själva objektet. Att skriva denna parameter selfär bara en konvention. Det är inte ett nyckelord och har ingen speciell betydelse i Python. Vi kan använda andra namn (som this) men det är mycket avskräckt. Att använda andra namn än som de selfflesta utvecklare inte tycker om och försämrar kodens läsbarhet ( läsbarhet räknas ).

Själv kan undvikas

Nu är du tydlig att själva objektet (instansen) skickas vidare som det första argumentet, automatiskt. Detta implicita beteende kan undvikas när man gör en statisk metod. Tänk på följande enkla exempel:

 class A(object): @staticmethod def stat_meth(): print("Look no self was passed")

Här @staticmethodär en funktionsdekoratör som gör stat_meth()statisk. Låt oss starta den här klassen och kalla metoden.

 >>> a = A() >>> a.stat_meth() Look no self was passed

Från exemplet ovan kan vi se att det implicita beteendet att skicka objektet som det första argumentet undviks när man använder en statisk metod. Sammantaget beter sig statiska metoder som vanliga gamla funktioner (eftersom alla objekt i en klass delar statiska metoder).

 >>> type(A.stat_meth) >>> type(a.stat_meth) 

Själv är här för att stanna

Det explicita selfär inte unikt för Python. Denna idé lånades från Modula-3 . Följande är ett användningsfall där det blir till hjälp.

Det finns ingen uttrycklig variabeldeklaration i Python. De kommer till handling vid det första uppdraget. Användningen av selfgör det lättare att skilja mellan instansattribut (och metoder) från lokala variabler.

I det första exemplet är self.x ett instansattribut medan x är en lokal variabel. De är inte desamma och de ligger i olika namnområden.

Många har föreslagit att göra sig själv till ett nyckelord i Python, som thisi C ++ och Java. Detta skulle eliminera den överflödiga användningen av explicit selffrån den formella parameterlistan i metoder.

Även om denna idé verkar lovande kommer den inte att hända. Åtminstone inte inom en snar framtid. Den främsta anledningen är bakåtkompatibilitet. Här är en blogg från skaparen av Python själv som förklarar varför det uttryckliga jaget måste stanna.

__init __ () är inte en konstruktör

En viktig slutsats som kan dras av informationen hittills är att __init__()metoden inte är en konstruktör. Många naiva Python-programmerare blir förvirrade med det eftersom de __init__()kallas när vi skapar ett objekt.

En närmare granskning kommer att avslöja att den första parametern i __init__()är själva objektet (objektet finns redan). Funktionen __init__()anropas omedelbart efter att objektet har skapats och används för att initialisera det.

Tekniskt sett är en konstruktör en metod som skapar själva objektet. I Python är den här metoden __new__(). En vanlig signatur för denna metod är:

 __new__(cls, *args, **kwargs)

När __new__()anropas skickas klassen själv som det första argumentet automatiskt ( cls).

Återigen, precis som jag, är cls bara en namngivningskonvention. Dessutom används * args och ** kwargs för att ta ett godtyckligt antal argument under metodanrop i Python.

Några viktiga saker att komma ihåg när du implementerar __new__()är:

  • __new__()kallas alltid förut __init__().
  • Det första argumentet är själva klassen som passeras implicit.
  • Lämna alltid ett giltigt objekt från __new__(). Inte obligatoriskt, men dess huvudsakliga användning är att skapa och returnera ett objekt.

Låt oss ta en titt på ett exempel:

 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

Låt oss nu instansiera det.

 >>> p2 = Point(3,4) From new (3, 4) () From init

Detta exempel illustrerar det som __new__()kallas tidigare __init__(). Vi kan också se att parametern cls i __new__()är själva klassen ( Point). Slutligen skapas objektet genom att anropa __new__()metoden till objektbasklass .

I Python objectär basklassen från vilken alla andra klasser härrör. I ovanstående exempel har vi gjort detta med super ().

Använd __new__ eller __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)

En provkörning:

 >>> 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

Intressanta artiklar...