【Python】self详解
在Python中定义类如下:class Student (Object):
通过类来创建实例:student Student()
-
Python中self的含义
实例Instance本身 对象(Object),是当前类的实例 表示是调用的是self对象,即实例的函数
-
self存在的意义
类的代码(函数)中,需要访问当前的实例中的变量和函数 对应的变量:Instance.ProperyNam,去读取之前的值和写入新的值 调用对应函数(function):Instance.function(),即执行对应的动作
Python中规定了函数参数的第一个参数必须是实例对象本身(即self)
由于类起到模板的作用,因此,可以在创建实例的时候,把我们认为必须绑定的属性强制填写进去。这里就用到Python当中的一个内置方法__init__方法
例如在Student类时,把name、score等属性绑上去:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
注意:
- __init__方法的第一参数永远是self,表示创建的类实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身
- 有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器会自己把实例变量传进去
- 和普通数相比,在类中定义函数只有一点不同,就是第一参数永远是类的本身实例变量self,并且调用时,不用传递该参数。
- 既然Student类实例本身就拥有这些数据,那么要访问这些数据,就没必要从外面的函数去访问,而可以直接在Student类的内部定义访问数据的函数(方法),这样,就可以把”数据”封装起来。
这些封装数据的函数是和Student类本身是关联起来的,称之为类的方法:
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))
这样一来,我们从外部看Student类,就只需要知道,创建实例需要给出name和score。而如何打印,都是在Student类的内部定义的,这些数据和逻辑被封装起来了,调用很容易,但却不知道内部实现的细节。
如果要让内部属性不被外部访问,可以把属性的名称前加上**两个下划线**
实例的变量名如果"__"开头,就变成了私有变量(private),只有内部可以访问,外部不能访问
确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮,但是如果要获取name和score,可以通过使用get_name和get_score的方法,如果允许外部代码修改,则可以增加set_score,具体实现如下:
在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__这样的变量名。
_name 表示可以访问,但是不要随意访问
补充:
封装的另一个好处是可以随时给Student类增加新的方法
- self的详细用法
self代表类的实例,而不是类
class Test:
def ppr(self):
print(self)
print(self.__class__)
t = Test()
t.ppr()
执行结果:
<__main__.Test object at 0x000000000284E080>
<class '__main__.Test'>
self代表的是类的实例。而self.__class__则指向类
把self换成this,结果也一样,但Python中最好用约定俗成的self
self可以省略吗?
在Python解释器的内部,当我们调用t.ppr()时,实际上Python解释成Test.ppr(t),也就是把self替换成了类的实例。
运行时提醒错误如下:
ppr在定义时没有参数,但是我们运行时强行传了一个参数。
由于上面解释过了t.ppr()等同于Test.ppr(t),所以程序提醒我们多传一个参数t。
这里实际上已经部分说明了self在定义时不可以省略。
当然,如果我们的定义和调用时均不传类实例是可以的,这就是类方法
在继承时,传入的是哪个实例,self就是那个传入的实例,而不是指定义了self的类的实例
运行c.cprt()时应该没有理解问题,指的是Child类的实例。
但是在运行c.pprt()时,等同于Child.pprt©,所以self指的依然是Child类的实例,由于self中没有定义pprt()方法,所以沿着继承树往上找,发现在父类Parent中定义了pprt()方法,所以就会成功调用。
举几个例子:
class Person(object):
def __init__(self, name, lang, website):
self.name = name
self.lang = lang
self.website = website
print('self: ', self)
print('type of self: ', type(self))
'''
未实例化时,运行程序,构造方法没有运行
'''
p = Person('Tim', 'English', 'www.universal.com')
'''实例化后运行的结果
self: <__main__.Person object at 0x00000000021EAF98>
type of self: <class '__main__.Person'>
'''
class Person(object):
def __init__(self, newPersonName):
# self.name = newPersonName
'''
如果此处不写成self.name
那么此处的name,只是__init__函数中的局部临时变量name而已
和全局中的name,没有半毛钱关系
'''
name = newPersonName
'''
此处只是为了代码演示,而使用了局部变量name,
不过需要注意的是,此处很明显,由于接下来的代码也没有利用到此处的局部变量name
则就导致了,此处的name变量,实际上被浪费了,根本没有利用到
'''
def sayYourName(self):
'''
此处由于找不到实例中的name变量,所以会报错:
AttributeError: Person instance has no attribute 'name'
'''
print('My name is %s' %self.name)
def selfAndInitDemo():
personInstance = Person('Tim')
personInstance.sayYourName()
if __name__ == '__main__':
selfAndInitDemo()
name = 'whole global name'
'''
注:此处全局的变量名,写成name,只是为了演示而用
实际上,好的编程风格,应该写成gName之类的名字,
以表示该变量是Global的变量
'''
class Person(object):
def __init__(self, newPersonName):
self.name = newPersonName
'''
此处正确的,通过访问self.name的形式,实现了:
1.给实例中,增加了name变量
2.并且给name赋了初值,为newPersionName
'''
def sayYourName(self):
'''
此处由于开始正确的初始化了self对象,使得其中有了name变量,
所以此处可以正确访问了name值了
'''
print('My name is %s' %self.name)
def selfAndInitDemo():
personInstance = Person('Tim')
personInstance.sayYourName()
if __name__ == '__main__':
selfAndInitDemo()
'''My name is Tim'''