全面理解Python中self的用法
参考:https://www.cnblogs.com/wangjian941118/p/9360471.html
全面理解pythong中self的用法
self代表类的实例,而非类
d:/learn-python3/学习脚本/全面了解python中self的用法/test.py
1 2 3 4 5 6 7 8 9 10 11 12 13 | # self代表类的实例,而非类 start class Test: def prt( self ): # 打印实例 print ( self ) # 打印实例的属性__class__即打印实例对应的类 print ( self .__class__) t = Test() t.prt() # <__main__.Test object at 0x000001F5DD79C6C8> # <class '__main__.Test'> # self代表类的实例,而非类 end |
执行结果如下
1 2 | <__main__.Test object at 0x000001F5DD79C6C8 > < class '__main__.Test' > |
从上面的例子可以很明显看出,self代表的是Test类的实例。而self.class则指向类Test
self不必非写成self
把上面的代码改一下,把self改成其他的比如this
1 2 3 4 5 6 7 8 9 10 11 | # self不必非写成self start class Test: def prt(this): # 打印实例 print (this) # 打印实例的属性__class__即打印实例对应的类 print (this.__class__) t = Test() t.prt() # self不必非写成self end |
改成this后,运行结果是一样的
当然,最好还是按照约定成俗的习惯,使用self
self可以不写吗?
在Python解释器内部,当我们调用t.prt()时,实际上Python解释成Test.prt(t),也就是说把self替换成类的实例作为参数调用类的函数方法。
把上面的t.prt()一行改写一下,运行后的实际结果完全相同。
实际上已经部分说明了self在定义时不可以省略
1 2 3 4 5 | class Test: def prt(): print ( self ) t = Test() t.prt() |
运行时提醒错误如下:prt在定义时没有参数,但是运行时强行传了一个参数。
由于上面解释过了t.prt()等同于Test.prt(t),所以程序提醒多传了一个参数t。
1 2 3 4 | Traceback (most recent call last): File "d:/learn-python3/学习脚本/全面了解python中self的用法/test.py" , line 36 , in <module> t.prt() TypeError: prt() takes 0 positional arguments but 1 was given |
如果定义和调用时均不传类实例是可以的,就是类方法
1 2 3 4 5 6 7 8 9 10 11 | # 定义和调用时均不传类实例是可以的,就是类方法 start class Test: def prt(): print (__class__) # 类方法,不是实例方法,实例方法的调用代码应该是Test().prt() # Test是类,Test()是实例 Test.prt() # <class '__main__.Test'> # 定义和调用时均不传类实例是可以的,就是类方法 end |
运行结果如下
1 | < class '__main__.Test' > |
直接使用类Test调用类函数prt()不报错是因为使用类方法不会默认传递实例作为参数
注意:Test是类,Test()是实例,如果上面代码改成Test().prt()则是使用实例调用同样因为在prt()内没有传递参数self会报错
在继承时,传入的是哪个实例,就是那个传入的实例,而不是指定义了self的类的实例。
先看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # 在继承时,传入的是哪个实例,就是那个传入的实例,而不是指定义了self的类的实例 start 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() # 在继承时,传入的是哪个实例,就是那个传入的实例,而不是指定义了self的类的实例 end |
运行结果如下
1 2 3 | <__main__.Child object at 0x0000026AC8D21708 > <__main__.Child object at 0x0000026AC8D21708 > <__main__.Parent object at 0x0000026AC8D21688 > |
解释:
运行c.part()时应该没有理解问题,指的是Child类的实例
但是在运行c.pprt()时,等同于Chile.pprt(c),所以self值的依然是Child类的实例,而不是Parent的实例,由于Child中没有定义pprt()方法,
所以沿着继承树继续往上找,发现父类Parent中定义了pprt()方法,所以就会调用成功。打印的self为Child类的一个实例
p.pprt()打印的是Parent的一个实例,没有问题
在描述符类中,self值的描述符类的实例
不太容易理解,先看实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # 在描述符类中,self指的是描述符类的实例 start 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 # 在描述符类中,self指的是描述符类的实例 end |
运行结果如下
1 2 3 | self in Test: <__main__.Test object at 0x000002255C8E1848 > self in Desc:<__main__.Desc object at 0x000002255C8E1788 > <__main__.Desc object at 0x000002255C8E1788 > <__main__.Test object at 0x000002255C8E1848 > < class '__main__.Test' > |
注意:这里dialysis的是t.x,也就是说是Test类的实例t的属性x,由于实例t中并没有属性x,所以找到了类属性x,而该属性是描述符属性,为Desc类的实例而已,所以此处并没有调用Test的任何方法。
解释:
描述符属性:在Desc中定义了__get__方法,在Test中定义了一个类属性x,是Desc的一个实例,我们访问t.x的时候就是访问了Desc的__get__方法,我们在__get__方法中的3个参数self,ins,cls分别对应描述符的实例,调用该描述符类的实例,调用类
使用输出顺序为,描述符类的实例(Desc的实例),调用该描述符类的实例(Test的实例),调用该描述符的类(Test类)
下面把t.x修改为Test.x即把实例的属性修改为类的属性
1 2 3 | t = Test() t.prt() Test.x |
运行输出如下
1 2 3 | self in Test: <__main__.Test object at 0x000002007FEE1888 > self in Desc:<__main__.Desc object at 0x000002007FEE17C8 > <__main__.Desc object at 0x000002007FEE17C8 > None < class '__main__.Test' > |
对比可以看到使用类调用属性和使用实例调用属性的输出第二项不同,如果使用实例调用属性则输出为调用它的实例,使用类属性调用,因为没有实例所以返回None
题外话:由于在很多时候描述符类中仍然需要知道调用该描述符的实例是谁,所以在描述符类中存在第二个参数ins,用来表示调用它的类实例,所以t.x时可以看到第三行中的运行结果中第二项为<main.Test object at 0x0000000002A570B8>。而采用Test.x进行调用时,由于没有实例,所以返回None。
关于描述符类参考:https://www.cnblogs.com/minseo/p/8515398.html
从本质理解Python中的self
下面从面相过程和面相对象的两种代码方式来理解self
假设要对用户的数据进行操作,用户的数据包含name和age。如果用面向过程的话,实现出来是下面这样子的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | # 面向过程的例子 start def user_init(user,name,age): user[ 'name' ] = name user[ 'age' ] = age def set_user_name(user,x): user[ 'name' ] = x def set_user_age(user,x): user[ 'age' ] = x def get_user_name(user): return user[ 'name' ] def get_user_age(user): return user[ 'age' ] myself = {} user_init(myself, 'kzc' , 17 ) print (get_user_age(myself)) set_user_age(myself, 20 ) print (get_user_age(myself)) # 面向过程的例子 end |
可以看到,对用户的各种操作,都要传user参数进去。
如果用面向对象的话,就不用每次把user参数传来传去,把相关的数据和操作绑定在一个地方,在这个类的各个地方,可以方便的获取数据。
之所以可以在类中的各个地方访问数据,本质就是绑定了self这个东西,它方法的第一个参数,可以不叫self,叫其它名字,self只不过是个约定。
下面是面向对象的实现,可以看到,结构化多了,清晰可读。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | # 面向对象的例子 start class User( object ): def __init__( self ,name,age): self .name = name self .age = age def SetName( self ,name): self .name = name def SetAge( self ,age): self .age = age def GetName( self ): return self .name def GetAge( self ): return self .age u = User( 'kzc' , 17 ) print (u.GetAge()) u.SetAge( 20 ) print (u.GetAge()) # 面向对象的例子 end |
实现的效果是一样的
总结:
- self在定义时需要定义,但是在调用时会自动传入
- self的名字并不是规定死的,但是最后还是按照约定使用self
- self总是值调用时类的实例
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
2020-11-13 Windows10远程桌面出现要求的函数不支持可能由于CredSSP加密数据库修正
2017-11-13 Centos7.2yum安装时候出现db5错误的解决办法