面向对象编程的思想
类:把一类事物的相同的特征和动作整合到一起就是类,是一个抽象的概念.
对象:基于类而创建的一个具体的事物(具体存在的),也是特征和动作整合到一起.
老男孩这个视频讲的很有意思,用函数实现了一个面向对象设计,然后改用class语法来实现,就是通过字典的方式将属性和方法封装在一起,然后从函数过渡到class,也解释了传入self的意义.
当然,用本质的解释可以说是类的每个对象都是在调用类方法,然后传入自身以辨识是哪一个对象,对象的方法不会再内存里反复存储,self就是一个识别符号.
Python的类
class 类名 类名默认的习惯是首字母大写
然后紧接着是类的文档字符串
之后是类体:
类体主要由两部分构成,即类的数据属性和函数属性.函数属性也被称为方法.类和对象都用.来访问自己的属性.
数据属性和方法名其实就是变量,就是标识符,和函数内的变量作用域类似,类的属性也只在类内发生作用.
查看类属性,用内置函数dir方法会打印类内所有的内容:
class Data(): '''这是一个测试用数据类型''' part = 'tg' def show(self): print('show is called') for i in dir(Data): print(i) # 结果是: __class__ __delattr__ __dict__ __dir__ __doc__ __eq__ __format__ __ge__ __getattribute__ __gt__ __hash__ __init__ __init_subclass__ __le__ __lt__ __module__ __ne__ __new__ __reduce__ __reduce_ex__ __repr__ __setattr__ __sizeof__ __str__ __subclasshook__ __weakref__ part show
上边有一个内置方法__dict__,是用来查看类的属性字典:
for item in Data.__dict__.items(): print(item) #结果是: ('__module__', '__main__') ('__doc__', '这是一个测试用数据类型') ('part', 'tg') ('show',) ('__dict__', ) ('__weakref__', )
这个__dict__返回一个字典,是属性名和属性内容的字典,其中part对应的是值tg,而show对应的是一个函数地址
# 直接通过类的属性字典操作数据属性和函数属性 print(Data.__dict__['part']) # 取出了tg Data.__dict__['show']('any') # 这个时候传什么属性都没用,但是函数可以执行. # 类加.来调用属性和方法,python内部也是从类的属性字典里找对应的东西来运行.
特殊的类属性:
__name__ | 类的名称 |
__doc__ | 类的文档字符串 |
__base__ | 类的第一个父类 |
__bases__ | 类的父类构成的元组 |
__dict__ | 类的属性字典 |
__module__ | 类定义所在的模块 |
__class__ | 实例对应的类,仅新式类有效 |
实际验证一下特殊的类属性:
print(Data.__name__) # Data print(Data.__doc__) # 这是一个测试用数据类型 print(Data.__base__) # <class 'object'> print(Data.__bases__) # (<class 'object'>,) print(Data.__dict__) # {'__module__': '__main__', '__doc__': '这是一个测试用数据类型', 'part': 'tg', 'show': <function Data.show at 0x0000022E22D5DF28>, '__dict__': <attribute '__dict__' of 'Data' objects>, '__weakref__': <attribute '__weakref__' of 'Data' objects>} print(Data.__module__) # __main__ print(Data.__class__) # <class 'type'>
解释:python3里所有的类都是新式类,有一个内建的类类型,就是object,所有新建的类,都是object的子类,生成类的时候,正因为有父类,所以才会继承这些属性,不然只能看到用户自定义的属性.在python里,类就是类型,所以查看类所属的类,会发现就是type.
类的对象
对象又类实例化而来,可以看做符合抽象的类的特征的一个具体事物.由类可以衍生去一个个具体的对象.由于类和函数类似,在类名后边加上()就是运行类,就是产生了一个实例,class 类定义之后,每次运行,返回的就是一个该类实例化的对象.
在内存中实际建立对象的时候,可以看到对象的属性字典内仅包含数据属性,不包含方法属性,这是因为对象的属性字典内如果找不到标识符,会到类的属性字典内去寻找,而方法的本质是函数,没有必要每实例化一个对象就将方法重复放入内存空间,只要在调用方法的时候,告诉类的方法是由哪一个对象进行调用的就可以了.这样的话,类的方法就需要一个额外的参数,也就是对象本身,用于标记是执行了方法的对象.
那么如何建立对象,就涉及到类的初始化函数,每次生成对象的时候,都会调用初始化函数:
# 给类添加一个初始化函数 def __init__(self,name,value,type): self.name = name self.value = value self.type = type
初始化函数的参数有四个,第一个位置参数默认就是self(名称可以更改,但不推荐更改),后边的参数在类初始化的时候通过类名(参数)传入,然后看里边的内容就知道,init会将自定义的名称传入作为一个新对象,然后将传入的参数赋给那个对象的数据属性.注意,self.name里的name是可以自行定义名称的,不一定要和init的形参名称相同.
# 创建一个对象: data1 = Data('first_data',629,'int')
按照初始化函数定义的顺序,省略第一个参数self(由class Data执行的过程中自动传入),然后将后边的参数一一对应.
创建好了对象之后,来看看对象里有什么属性:
print(data1.__dict__) # 结果是 {'name': 'first_data', 'value': 629, 'type': 'int'}
可以发现属性字典内并没有方法名,这说明这个对象本身不存储方法.那么对象全部可以执行的属性有哪些呢?想到了dir方法:
for i in dir(data1): print(i) #结果是: __class__ __delattr__ __dict__ __dir__ __doc__ __eq__ __format__ __ge__ __getattribute__ __gt__ __hash__ __init__ __init_subclass__ __le__ __lt__ __module__ __ne__ __new__ __reduce__ __reduce_ex__ __repr__ __setattr__ __sizeof__ __str__ __subclasshook__ __weakref__ name part show type value
可以看到,相比之前dir(Data)的操作结果,对象可使用的属性里多了show type value 这三个属性名,其他都和类一样,包括方法名,但是我们在对象的属性字典里找不到,是为什么呢?
这是因为对象生成的过程中,类的方法也可以被对象使用,这符合类的语义,否则对象便失去了作为类的共同特征.使用对象的属性时,首先到对象自己的属性字典里查找,如果找不到,就到所属类的属性里查找,如果找到就调用,如果找不到,就报错.其实可以将属性名认为是在类的范围内的作用域的变量名,就很容易理解了.
#调用属性 print(data1.part) # tg 可以通过对象调用类的属性 print(data1.name) # first_data 可以调用对象的属性 data1.show() # 报错
为什么会报错,查看报错的原因:show() takes 0 positional arguments but 1 was given,说明调用的时候,python内部给show方法传了一个参数,这个参数联想到__init__函数,就知道是对象自己,也就是self,而类内定义的方法是没有参数的(后边会说,这种方法是类方法,对象无法调用),来修改一下类的方法,以使对象调用的时候能够显示对象相关的信息
# 修改类内的show方法 def show_obj(self): print("{}'s value is {}, type is {}.".format(self.name, self.value, self.type)) # 试验调用对象内不存在,而类内存在的方法 data1.show_obj() # first_data's value is 629, type is int. # 通过执行结果,可见默认确实传入了self参数,而且这个self,就是data1自己. # 调用一下特殊的类属性 print(data1.__doc__) # 能够成功,显示类的字符串 print(data1.__name__) # 失败,无此属性 print(data1.__base__) # 失败,无此属性 print(data1.__bases__) # 失败,无此属性 print(data1.__dict__) # 返回属性字典 print(data1.__module__) # 返回执行的模块名,由于是直接执行,结果是__main__ print(data1.__class__) # 返回<class '__main__.Data'>,说明对象属于Data类 # 可见也并非所有的类属性都可以由对象调用,先了解到这里.
通过上述操作,可以简单知道了实例化的对象与类之间的关系以及它们属性之间的关系.