себе у Питхон-у, демистификовано

Ако већ неко време програмирате на Питхону (објектно оријентисано програмирање), онда сте дефинитивно наишли на методе којима је selfпрви параметар.

Покушајмо прво да схватимо шта је овај параметар који се понавља.

Шта је селф у Питхону?

У објектно оријентисаном програмирању, кад год дефинишемо методе за класу, користимо 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омогућава приступ атрибутима и методама сваког објекта у питхону. То омогућава сваком објекту да има своје атрибуте и методе. Стога, чак и много пре него што креирамо ове објекте, ми их референцирамо као selfприликом дефинисања класе.

Зашто се селф изричито дефинише сваки пут?

Чак и када разумемо употребу self, то и даље може изгледати чудно, посебно програмерима који долазе из других језика, који selfсе као параметар изричито прослеђују сваки пут када дефинишемо методу. Како каже Зен из Питхона , „ експлицитно је боље него имплицитно “.

Па, зашто то треба да радимо? Узмимо за почетак једноставан пример. Имамо 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()потребан је један, али није прослеђено нула аргумената. Зашто се Питхон не жали на неподударање овог броја аргумената?

Шта се дешава изнутра?

Point.distanceа p1.distanceу горњем примеру су различити и нису потпуно исти.

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

Можемо видети да је прва функција, а друга метода. Необична ствар у вези са методама (у Питхону) је та што се сам објекат преноси као први аргумент одговарајућој функцији.

У случају горњег примера, позив методе p1.distance()је заправо еквивалентан Point.distance(p1).

Генерално, када методу позивамо са неким аргументима, одговарајућа функција класе се позива стављањем објекта методе испред првог аргумента. Дакле, било шта слично obj.meth(args)постаје Class.meth(obj, args). Процес позивања је аутоматски, док процес пријема није (изричито).

То је разлог што први параметар функције у класи мора бити сам објект. Писање овог параметра selfсамо је конвенција. То није кључна реч и нема посебно значење у Питхону. Могли бисмо да користимо и друга имена (попут 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није јединствено за Питхон. Ова идеја је позајмљена од Модула-3 . Следи случај употребе када то постаје корисно.

У Питхону не постоји експлицитна декларација променљиве. Они ступају у акцију на првом задатку. Коришћење selfолакшава разликовање атрибута инстанце (и метода) од локалних променљивих.

У првом примеру, селф.к је атрибут инстанце, док је к локална променљива. Нису исти и леже у различитим именским просторима.

Многи су предложили да сами направе кључну реч у Питхону, као што thisсу Ц ++ и Јава. Ово би елиминисало сувишну употребу експлицитног selfса формалне листе параметара у методама.

Иако се ова идеја чини обећавајућом, то се неће догодити. Бар не у блиској будућности. Главни разлог је повратна компатибилност. Ево блога самог творца Питхона који објашњава зашто експлицитно ја мора да остане.

__инит __ () није конструктор

Један важан закључак који се може извући из досадашњих информација је да __init__()метода није конструктор. Многи наивни програмери на Питхону се збуне с тим откако их __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

Занимљиви Чланци...