面向对象编程
面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。
给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法(Method)
面向对象的设计思想是抽象出类Class,根据Class创建实例Instance
数据封装、继承和多态是面向对象的三大特点
类和实例
类是抽象的模板,比如student类
实例是根据类创造出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据相互独立,互不影响
通过class关键字定义类
class Student(object):pass #定义Student类
class 后面紧跟的是类名,即Student,类名通常是大写开头的单词
紧接着的(object),表示该类是从哪个类继承下来的
通常没有合适的继承类,就用object类,这是所有类最终都会继承的类
定义好了类,就可以根据该类创建出实例
创建实例都是通过类名+()实现的
bart = Student() #定义bart实例
可以自由地给一个实例变量绑定属性
bart.name = ‘bart simpson’ #给bart绑定一个name属性
类可以起到模板的作用,所以创建类的时候,可以把一些我们认为必须绑定的属性强制填写进去
通过定义一个特殊的__init__方法,在创建实例的时候,就可以把一些属性绑上去
注意特殊方法__init__,前后有两个下划线
__init__方法的第一个参数永远是self,代表创建的实例本身。因此在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身
有了__init__方法,在创建实例的时候就不能传入空的参数了,必须传入与__init__方法匹配的参数。但self不用传,python解释器会自己把实例变量传进去:
bart = Student(‘bart simpson’,80) #bart.name 和bart.score分别输出’bart simpson’和80
和普通的函数相比:
类中定义的函数,第一个参数永远是实例变量self,并且在调用时,不用传递该参数
其余类的方法和普通函数没有区别,仍然可以用各种类型参数
数据封装
我们可以通过函数访问实例的数据,比如打印学生成绩
在类的内部定义访问数据的函数,就把‘数据’给封装起来了
这些封装数据的函数是和类本身是关联起来的,我们称之为类的方法
方法:
要定义一个方法,除了第一个参数是self外,其他和普通函数一样
调用方法,只需要在实例变量上直接调用,除了self不用传递,其他参数正常传入
方法就是和实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据
python允许对实例变量绑定任何数据,对于两个实例变量,虽然都是同一个类的不同实例,但拥有的变量名称都可能不同
访问限制
如果要让内部属性不被外部访问,需要把属性的名称前加上两个下划线__
python中,实例的变量名如果以__开头,就变成了一个私有变量,只有内部可以访问,外部不能访问
这样确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,使代码更健壮
如果外部想获取私有变量的内容,可以在类里面增加返回私有变量的方法
如果允许外部代码修改私有变量,可以在类里面增加赋值的方法
这种在类里面增加赋值方法,可以对参数做检查,避免传入无效的参数
继承和多态
定义一个新的类的时候,可以从某一个现有的类继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class/Super class)
继承的好处
子类可以获得父类的全部功能
当子类和父类存在相同的方法时,子类的覆盖父类的,即运行时只会调用子类的这个方法(多态)
类其实是种数据类型
当定义了一个类的时候,就相当于定义了一种数据类型
类的实例其实就是,这个实例是这个类的数据类型(也是父类的数据类型)c = Dog() # c是Dog类型
判断一个变量是否是某个类型用isinstance()判断。isinstance(c, Dog) #True
在继承关系中,如果一个实例的数据类型是某个子类,那这个实例的数据类型也可以被看成是父类,反之不行
多态的好处
新增一个子类,对任何一个依赖父类数据类型作为参数的函数或者方法都可以不加修饰正常运行(因为多态)
当我们需要传入多个子类时,只需要接受父类数据类型就可以了,因为子类都是父类的数据类型(继承),然后按照父类数据类型进行操作。父类和子类的方法相同时,只要是父类或者其子类,都会自动调用实际类的该方法。对于一个变量,我们只需要知道它是父类数据类型,无需确切知道它的子类型,就可以放心运用方法,而调用的方法是作用的那个子类或者父类对象上,由运行时该对象的确切类型决定
开闭原则
对扩展开放:允许新增父类的子类
对修改封闭:不修改依赖父类数据类型的函数
继承有层级关系,可以直接继承下去,但任何类最终都可以归溯到跟类object
静态语言和动态语言
静态语言(java),如果需要传入父类的数据类型,则传入的对象必须是父类或子类的类型,否则无法调用定义的相同的方法
动态语言(python),则不一定需要父类类型,只要保证传入的对象有这个方法就可以了
动态语言‘鸭子类型’:
不要求严格的继承体系,一个对象只要‘看起来像鸭子,走起来像鸭子’,就可以当做鸭子。
Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个read()方法,返回其内容。但是,许多对象,只要有read()方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了read()方法的对象。
总结
继承可以把父类的所有功能都直接拿过来,这样就不必从零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。
动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。
获取对象信息
使用type()可以获取对象类型
type(需要判断的对象)
type(123) #
一个变量指向函数或者类,也可以用type()判断
type(abs) #
type(a) #
type()函数返回对应的class类型,如果我们要在if语句中判断,就需要比较两个变量的type类型是否相同
判断基本类型可以写直接写,也可以比较
type(123)==int #True
type(‘abc’)==type(‘123’) #True
判断对象是否是函数可以用types模块中定义的常量
type(fn)==types.FunctionType #True
type(abs)==types.BuiltinFunctionType #True
type(lambda x: x)==types.LambdaType #True
type((x for x in range(10)))==types.GeneratorType #True
使用isinstance()
对于class的继承关系来说,使用type()很不方便,判断class的类型,可以使用isinstance()函数:isinstance(对象,类)
能用type()判断的基本类型也可以用isinstance()判断:isinstance(‘a’,str) #True
并且还可以判断一个变量是否是某些类型中的一种:isinstance([1, 2, 3], (list, tuple)) #True
总是优先使用isinstance()判断类型,可以将指定类型及其子类一网打尽
使用dir()
可以获得一个对象的所有属性和方法,它返回一个包含字符串的list
获取一个str的属性和方法:dir(‘abc’) #[‘add’, ‘class’,…, ‘subclasshook’, ‘capitalize’, ‘casefold’,…, ‘zfill’]
其中类似__xxx__的属性和方法在python中都是有特殊作用的(例如__len__方法返回长度)。在python中调用len()函数获取对象长度,实际在len()内部自动调用该对象的__len__()方法。len(‘abc’) 与 ‘abc’.len()等价
剩下的是普通属性或方法
配合getattr()、setattr()、hasattr()可以直接操作一个对象的状态
hasattr(obj, ‘x’) # 有属性’x’吗?#True
setattr(obj, ‘y’, 19) # 设置一个属性’y’
getattr(obj, ‘y’) # 获取属性’y’ #19
如果获取的属性不存在,会报错,可以传入一个default参数,如果属性不存在就返回默认值
getattr(obj, ‘z’,404) # 获取属性’z’ 不存在返回404
通过内置的一系列函数,可以对一个python对象进行剖析,拿到内部数据,只有在不知道对象信息的时候,我们才会去获取对象信息
实例属性和类属性
由于python是动态语言,根据创建的实例可以任意绑定属性
给实例绑定属性的方法:通过实例变量或者通过self变量
如果类本身需要绑定一个属性,可以直接在class中定义属性。这种属性是类属性,归对应类所有,当我们定义了类属性后,类的所有实例都可以访问到
注意:
编写程序的时候,千万不要对实例属性和类属性使用相同的名字
相同名字的实例属性将屏蔽掉类属性
算出实例属性后,再次访问的时候类属性
总结:
实例属性输入各个实例所有,互不干扰
类属性属于类所有,所有实例共享一个属性
不要对实例属性和类属性使用相同的名字