себе си в Python, Demystified

Ако от известно време програмирате в Python (обектно-ориентирано програмиране), тогава определено сте попаднали на методи, които имат selfза първи параметър.

Нека първо се опитаме да разберем какъв е този повтарящ се самостоятелен параметър.

Какво е self в Python?

При обектно-ориентираното програмиране, когато дефинираме методи за клас, ние използваме selfкато първи параметър във всеки случай. Нека да разгледаме дефиницията на клас, наречен 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")

В този случай всички методи, включително __init__, имат първия параметър като self.

Знаем, че класът е план за обектите. Този план може да се използва за създаване на множество номера на обекти. Нека създадем два различни обекта от горния клас.

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

В selfдума се използва за представяне на съд (обект) на дадения клас. В този случай, двете Catобекти cat1и cat2имат свой собствен nameи ageатрибути. Ако нямаше само аргумент, същият клас не можеше да събере информацията за двата тези обекта.

Тъй като обаче класът е просто план, selfпозволява достъп до атрибутите и методите на всеки обект в python. Това позволява на всеки обект да има свои собствени атрибути и методи. По този начин, дори много преди да създадем тези обекти, ние се позоваваме на обектите, selfдокато определяме класа.

Защо самостоятелно изрично се дефинира всеки път?

Дори когато разбираме използването на self, може да изглежда странно, особено за програмисти, идващи от други езици, което selfсе предава като параметър изрично всеки път, когато дефинираме метод. Както се казва в Дзен на Python , " Изричното е по-добро от неявното ".

И така, защо трябва да правим това? Да вземем прост пример за начало. Имаме Pointклас, който определя метод distanceза изчисляване на разстоянието от началото.

 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

Нека сега да създадем пример за този клас и да намерим разстоянието.

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

В горния пример __init__()дефинира три параметъра, но току-що преминахме два (6 и 8). По същия начин се distance()изисква един, но са подадени нула аргументи. Защо Python не се оплаква от несъответствието на този аргумент?

Какво се случва вътрешно?

Point.distanceи p1.distanceв горния пример са различни и не са абсолютно еднакви.

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

Виждаме, че първият е функция, а вторият е метод. Особено при методите (в Python) е, че самият обект се предава като първи аргумент на съответната функция.

В случая на горния пример извикването на метода p1.distance()всъщност е еквивалентно на Point.distance(p1).

Като цяло, когато извикаме метод с някои аргументи, съответната функция на клас се извиква чрез поставяне на обекта на метода преди първия аргумент. И така, всичко подобно obj.meth(args)става Class.meth(obj, args). Процесът на извикване е автоматичен, докато процесът на получаване не е (изрично).

Това е причината първият параметър на функция в клас да бъде самият обект. Записването на този параметър selfе просто конвенция. Това не е ключова дума и няма специално значение в Python. Можем да използваме други имена (като this), но това е силно обезкуражено. Използването на имена, различни от които selfсе отхвърлят от повечето разработчици, влошава четливостта на кода ( броят на четливостта ).

Себето може да бъде избегнато

Вече сте наясно, че самият обект (екземпляр) се предава автоматично като първи аргумент. Това имплицитно поведение може да се избегне, докато се прави статичен метод. Помислете за следния прост пример:

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

Тук @staticmethodима функция за декориране, която прави stat_meth()статични. Нека да създадем пример за този клас и да извикаме метода.

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

От горния пример можем да видим, че неявното поведение на предаването на обекта като първи аргумент е избегнато при използване на статичен метод. Като цяло статичните методи се държат като обикновените стари функции (Тъй като всички обекти на клас споделят статични методи).

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

Себето е тук, за да остане

Изричното selfне е уникално за Python. Тази идея е заимствана от Modula-3 . Следва случай на употреба, когато става полезно.

В Python няма изрична декларация за променлива. Те започват да действат при първото задание. Използването на selfулеснява разграничаването на атрибутите на екземпляра (и методите) от локалните променливи.

В първия пример self.x е атрибут на екземпляр, докато x е локална променлива. Те не са еднакви и те лежат в различни пространства от имена.

Мнозина са предложили да направят самостоятелна ключова дума в Python, като thisв C ++ и Java. Това би премахнало излишното използване на експлицитно selfот списъка на официалните параметри в методите.

Въпреки че тази идея изглежда обещаваща, тя няма да се случи. Поне не в близко бъдеще. Основната причина е обратната съвместимост. Ето блог от самия създател на Python, обясняващ защо изричното аз трябва да остане.

__init __ () не е конструктор

Един важен извод, който може да се направи от досегашната информация, е, че __init__()методът не е конструктор. Много наивни програмисти на Python се бъркат с него, тъй като __init__()се извикват, когато създаваме обект.

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)

Примерен цикъл:

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

Интересни статии...