Detailed explanation of self usage in Python

Before introducing the usage of self in Python, let's introduce classes and instances in Python... 
We know that the most important concepts of object orientation are classes and instances. Classes are abstract templates, such as the abstraction of students. things can be represented by a Student class. An instance is a specific "object" created according to a class. Each object inherits the same methods from the class, but its data may be different. 
1. Take the Student class as an example. In Python, the definition class is as follows:

class Student(object):
    pass
  • 1
  • 2

(Object) indicates which class the class is inherited from. The Object class is a class that all classes inherit.

2. Instance: After defining the class, you can create an instance of Student through the Student class. Creating an instance is achieved through the class name + ():

student = Student()
  • 1

3. Since the class acts as a template, when creating an instance, we can forcefully fill in the attributes that we think must be bound. Here, a built-in method in Python is used __init__. For example, in the Student class, attributes such as name and score are bound:

class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score
  • 1
  • 2
  • 3
  • 4

Note here: (1) __init__The first parameter of the method is always the selfcreated class instance itself . Therefore, within the __init__method, various attributes can be bound to self, because self points to the created instance itself. (2) With the __init__method, when creating an instance, you cannot pass in empty parameters, you must pass __init__in parameters that match the method, but self does not need to be passed, the Python interpreter will pass the instance variable in by itself:

>>>student = Student("Hugh", 99)
>>>student.name
"Hugh"
>>>student.score
99
  • 1
  • 2
  • 3
  • 4
  • 5

In addition, here selfrefers to the class itself, self.namewhich is Studentthe attribute variable of the Studentclass, and is owned by the class. It isname an external parameter, not Studentthe one that comes with the class. Therefore, self.name = namethe meaning is to nameassign the value of the parameter passed from the outside to the attribute variable of the Student class self.name.

4. Compared with ordinary numbers, there is only one difference in defining functions in a class, that is, the first parameter is always the instance variable of the class itselfself , and this parameter does not need to be passed when calling. In addition, class methods (functions) are no different from ordinary functions. You can use default parameters, variable parameters or keyword parameters ( *args is a variable parameter, args receives a tuple , **kw is a keyword argument, and kw receives a dict ).

5. Since the Student class instance itself owns the data, to access the data, it is not necessary to access the data from the outside function, but the function (method) for accessing the data can be defined directly inside the Student class, so that you can put "Data" is encapsulated. These functions that encapsulate data are associated with the Student class itself and are called class methods:

class Student(obiect):
    def __init__(self, name, score):
        self.name = name
        self.score = score
    def print_score(self):
        print "%s: %s" % (self.name, self.score)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
>>>student = Student("Hugh", 99)
>>>student.print_score
Hugh: 99
  • 1
  • 2
  • 3

In this way, when we look at the Student class from the outside, we only need to know that we need to give a name and score to create an instance. How to print is defined inside the Student class. These data and logic are encapsulated, and the call is easy, but the details of the internal implementation are unknown.

If you want to prevent internal attributes from being accessed externally, you can add two underscores before the name of the attribute . In Python, if the variable name of an instance starts with a variable name, it becomes a private variable (private), which can only be accessed internally. It is not accessible from the outside, so let's change the Student class:

class Student(object):

    def __init__(self, name, score):
        self.__name = name
        self.__score = score
    def print_score(self):
        print "%s: %s" %(self.__name,self.__score)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

After the change, there is no change to the external code, but the instance variables .__nameand instance variables cannot be accessed from the outside .__score:

>>> student = Student('Hugh', 99)
>>> student.__name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'
  • 1
  • 2
  • 3
  • 4
  • 5

This ensures that external code cannot arbitrarily modify the internal state of the object, so that the code is more robust through the protection of access restrictions.

But what if the external code wants to get the name and score? You can add methods like get_name and get_score to the Student class:

class Student(object):
    ...

    def get_name(self):
        return self.__name

    def get_score(self):
        return self.__score
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

What if you want to allow external code to modify the score? You can add a set_score method to the Student class:

class Student(object):
    ...

    def set_score(self, score):
        self.__score = score
  • 1
  • 2
  • 3
  • 4
  • 5

It should be noted that in Python, variable names are similar __xxx__, that is, those starting with a double underscore and ending with a double underscore are special variables, and special variables can be accessed directly, not private variables, so they cannot be used __name__, __score__like this variable name.

Sometimes, you will see instance variable names that start with an underscore, such as _name. Such instance variables are accessible from outside, but, by convention, when you see such a variable, it means, " Although I can be accessed, please treat me as a private variable and do not access it at will".

Another benefit of encapsulation is that you can add new methods to the Student class at any time , such as: get_grade:

class Student(object):
    ...
    def get_grade(self):
        if self.score >= 90:
            return 'A'
        elif self.score >= 60:
            return 'B'
        else:
            return 'C'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Likewise, get_grademethods can be called directly on instance variables without knowing internal implementation details:

>>> student.get_grade()
'A'
  • 1
  • 2

6. selfThe careful usage 
of (1), self represents an instance of a class, not a class.

class Test:
    def ppr(self):
        print(self)
        print(self.__class__)

t = Test()
t.ppr()
执行结果:
<__main__.Test object at 0x000000000284E080>
<class '__main__.Test'>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

从上面的例子中可以很明显的看出,self代表的是类的实例。而self.__class__则指向类。 
注意:把self换成this,结果也一样,但Python中最好用约定俗成的self。 
(2)、self可以不写吗? 
在Python解释器的内部,当我们调用t.ppr()时,实际上Python解释成Test.ppr(t),也就是把self替换成了类的实例。

class Test:
    def ppr():
        print(self)

t = Test()
t.ppr()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

运行结果如下:

Traceback (most recent call last):
  File "cl.py", line 6, in <module>
    t.ppr()
TypeError: ppr() takes 0 positional arguments but 1 was given
  • 1
  • 2
  • 3
  • 4

运行时提醒错误如下:ppr在定义时没有参数,但是我们运行时强行传了一个参数。

由于上面解释过了t.ppr()等同于Test.ppr(t),所以程序提醒我们多传了一个参数t。

这里实际上已经部分说明了self在定义时不可以省略。

当然,如果我们的定义和调用时均不传类实例是可以的,这就是类方法。

class Test:
    def ppr():
        print(__class__)

Test.ppr()

运行结果:
<class '__main__.Test'>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

(3)、在继承时,传入的是哪个实例,就是那个传入的实例,而不是指定义了self的类的实例。

class Parent:
    def pprt(self):
        print(self)

class Child(Parent):
    def cprt(self):
        print(self)
c = Child()
c.cprt()
c.pprt()
p = Parent()
p.pprt()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

运行结果:

<__main__.Child object at 0x0000000002A47080>
<__main__.Child object at 0x0000000002A47080>
<__main__.Parent object at 0x0000000002A47240>
  • 1
  • 2
  • 3

解释: 
运行c.cprt()时应该没有理解问题,指的是Child类的实例。 
但是在运行c.pprt()时,等同于Child.pprt(c),所以self指的依然是Child类的实例,由于self中没有定义pprt()方法,所以沿着继承树往上找,发现在父类Parent中定义了pprt()方法,所以就会成功调用。

(4)、在描述符类中,self指的是描述符类的实例

class Desc:
    def __get__(self, ins, cls):
        print('self in Desc: %s ' % self )
        print(self, ins, cls)
class Test:
    x = Desc()
    def prt(self):
        print('self in Test: %s' % self)
t = Test()
t.prt()
t.x
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

运行结果如下:

self in Test: <__main__.Test object at 0x0000000002A570B8>
self in Desc: <__main__.Desc object at 0x000000000283E208>
<__main__.Desc object at 0x000000000283E208> <__main__.Test object at 0x0000000002A570B8> <class '__main__.Test'>
  • 1
  • 2
  • 3

这里主要的疑问应该在:Desc类中定义的self不是应该是调用它的实例t吗?怎么变成了Desc类的实例了呢? 
因为这里调用的是t.x,也就是说是Test类的实例t的属性x,由于实例t中并没有定义属性x,所以找到了类属性x,而该属性是描述符属性,为Desc类的实例而已,所以此处并没有顶用Test的任何方法。

那么我们如果直接通过类来调用属性x也可以得到相同的结果。

下面是把t.x改为Test.x运行的结果。

self in Test: <__main__.Test object at 0x00000000022570B8>
self in Desc: <__main__.Desc object at 0x000000000223E208>
<__main__.Desc object at 0x000000000223E208> None <class '__main__.Test'>

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324517750&siteId=291194637