读懂python中的self

  在Python类中规定,函数的第一个参数是实例对象本身,无论是显式创建类的构造方法,还是向类中添加实例方法,都要求将 self 参数作为方法的第一个参数,并且约定俗成,把其名字写为self。例如定义一个Chinese类:
class Chinese:
    country = 'China'
    def __init__(self, name, age):
        self.name = name 
        self.age = age 
    def talk(self):
        print(self, 'is talking Chinese')
if __name__ == '__main__':
   DY = Chinese("DY", "18")
   DY.talk()
  但Python中并没有规定该参数的具体名称,之所以将其命名为 self,只是程序员之间约定俗成的一种习惯,遵守这个约定,可以使我们编写的代码具有更好的可读性(大家一看到 self,就知道它的作用)。

1.类的实例方法中self存在必要性

从实例与类关系来说
  self 参数的具体作用是什么呢?打个比方,如果把类比作造房子的图纸,那么类实例化后的对象是真正可以住的房子。根据一张图纸(类),我们可以设计出成千上万的房子(类对象),每个房子长相都是类似的(都有相同的类变量和类方法),但它们都有各自的主人,那么如何对它们进行区分呢?
  当然是通过 self 参数,它就相当于每个房子的门钥匙,可以保证每个房子的主人仅能进入自己的房子(每个类对象只能调用自己的类变量和类方法)。
  也就是说同一个类可以产生多个对象,当某个对象调用类方法时,该对象会把自身的引用作为第一个参数自动传给该方法,换句话说,Python 会自动绑定类方法的第一个参数指向调用该方法的对象。如此,Python解释器就能知道到底要操作哪个对象的方法。
  因此得出结论:无论是创建类的构造方法还是实例方法,最少要包含一个参数self。
 
  通过实例的self参数与对象进行绑定,程序在调用实例方法和构造方法时,也不需要手动为第一个参数传值。所以我们在调用成员方法时可以写成:
class Chinese:
    country = 'China'
    def __init__(self, name, age):
        self.name = name 
        self.age = age 
    def talk(self):
        print(self, 'is talking Chinese')
if __name__ == '__main__':
   DY = Chinese("DY", "18")
   DY.talk()
 
从实例方法存储内存空间来讲
  对象在实例化的时候,构造函数会开辟一块内存空间,把属性存到DY那个对象那里,实例方法还是存储在类的方法区,对象直接去引用就行,原因在于copy出来函数执行代码效果是不变的。实例化出来的对象如果想调用类的方法,就需要到Chinese类中去取。
  但有个问题,如果不告诉类是谁在调用实例方法,那么调用方法产生的结果不知道返回给哪个对象。因此我们在调用Chinese. talk(self)的时候应该告诉类是哪个对象在调用,所以最简单的办法是把对象传进去。那么调用实例方法时还可以写成:
class Chinese:
    country = 'China'
    def __init__(self, name, age):
        self.name = name 
        self.age = age 
    def talk(self):
        print(self, 'is talking Chinese')
if __name__ == '__main__':
   DY = Chinese("DY", "18")
   Chinese.talk(DY)
所以在调用实例方法时,都需要把self代表的对象传进去,因此这个self就是谁调用这个方法,表示的就是哪个对象。

2. self是谁:指实际调用该方法的对象

上面说到类的实例方法(函数)中一定要有传入一个参数self,把实例方法与调用该方法的对象进行绑定,那么怎么证明self指的就是该对象呢?
class Chinese:
    country = 'China'
    def __init__(self, name, age):
        self.name = name 
        self.age = age 
    def talk(self):
        print(self, 'is talking Chinese')
if __name__ == '__main__':
    DY = Chinese("DY", "18")   # 把一个类变成一个具体对象的过程叫类的实例化
    zhang = Chinese("zhang", "18")
    print(DY)  # 打印对象,返回对象所在的内存地址
    print(zhang)
    DY.talk()  # 打印对象的实例方法,获取self的值    
    zhang.talk()
    
执行结果:
<__main__.Chinese object at 0x000001C718A1DA88> 
<__main__.Chinese object at 0x000001C718A1FF08> 
<__main__.Chinese object at 0x000001C718A1DA88> is talking Chinese 
<__main__.Chinese object at 0x000001C718A1FF08> is talking Chinese
通过执行结果我们可以看出:
①打印对象DY返回的结果<__main__.Chinese object at 0x00000269E594EEB8> ,0x00000269E594EEB8指的是该对象所在的内存地址,与通过对象调用实例方法(DY.talk()),打印的self的值0x00000269E594EEB8一致,可以看出他们指向的是同一内存地址,因此可得出结论:在当前实例化中self代表是对象DY。
②调用实例方法DY.talk()与zhang.talk()打印出self的内存地址分别为0x000001C718A1DA88和0x000001C718A1FF08,也就是说不同实例方法self的并不是同一个对象,DY.talk()中self指的是DY对象,zhang.talk()中self指的是zhang对象。
综合以上得出:self指的是实际调用该方法的对象。

3. self不必非写成self

上面代码self更改为this:
class Chinese:
    country = 'China'
    def __init__(this, name, age):
        this.name = name
        this.age = age
    def talk(this):
        print(this, 'is talking Chinese')
if __name__ == '__main__':
    DY = Chinese("DY", "18")
    DY.talk()
  改成this后,运行结果完全一样。我们只是使用self代指调用方法的对象,完成对象和实例方法的第一个参数进行绑定,至于实例方法的第一个参数写成a,b,c.....或者this完全不受影响。
当然,最好还是尊重约定俗成的习惯,使用self。

4. self可以不写吗

我们试下实例方法(函数)中不传入参数self:
class Chinese:
    def talk():
        print("我热爱我的祖国")

if __name__ == '__main__':
    DY = Chinese()
    DY.talk()
    
执行结果:
Traceback (most recent call last): 
  File "test.py", line 577, in <module> 
    DY.talk() 
TypeError: talk() takes 0 positional arguments but 1 was given
  执行结果的意思是:TypeError: talk()接受0个位置参数,但给出了1个。但是我们在调用实例方法的时候并没有给函数传入参数,给出的这1个参数是哪里来的?
  上面我们有说到实例方法与类的静态属性(实例变量)不同,实例变量会在实例化对象时存储在对象的内存区间,但实例方法是存储在类的方法区,只有实例方法被调用的时候才会被加载,所以我们在执行DY.talk()时,Python解释器解释为Chinese.talk(DY),把self替换成类的实例,这样就解释了上面的问题,给出的这一个位置参数就是self,也就是调用方法的对象。
所以,实例方法中必须要传入一个形参,不可以不写self。

5. 实例方法中的变量什么时候前缀加self

  类中的变量分为类变量、实例变量、局部变量。类变量就是定义在方法(函数)外的变量,在方法中可以使用类名进行调用,在类方法中可以使用cls.变量名进行调用,相对来说好做区分。实例变量和局部变量都是存在于方法中(函数内),那么大家肯定会有个疑问:变量前什么时候加self(实例变量),什么时候不加self(局部变量)?或者为了以防万一变量前全都加self,如下:
class TestLogin(unittest.TestCase):
    def test_login(self):
         self.url = xxx
         self.resp = self.session.get(self.url)
         self.text = self.resp.text
         self.status = self.resp.statuscode
  但这样很明显没有意义,url/resp/text/status这些变量都是局部的,别的方法里面不需要访问这些变量,只存在于test_login函数中,别的用例也不需要使用这些变量,因此除了session属性需要共用以外,其他变量前不需要加self。
  我们只需记住:前缀带self的变量,就是在整个类的代码块里面类似是作为全局变量,如果变量前面加了self,那么在任何实例方法(非staticmethod和calssmethod)就都可以访问这个变量了,如果没有加self,只有在当前函数内部才能访问这个变量。

总结

  • self在定义实例方法时需要传入该参数,但是在调用时会自动传入。
  • self的名字并不是规定死的,但是最好还是按照约定是用self。
  • self总是指调用该方法的实例。
  • 实例方法中需要在全局引用的变量需要加前缀self。
posted @ 2021-05-11 23:59  不吃鱼的猫大  阅读(2205)  评论(0编辑  收藏  举报