面向对象的核心是对象,世间万物都可以看作对象,任何一个对象都可以通过一系列属性和行为来描述,可以包含任意数量和类型的数据或操作。类是用来描述具有相同属性和方法的所有对象的集合。类通常是抽象化的概念,而对象表示具体的事物。
要想熟悉并灵活运用类和对象解决项目中的实际问题,首先需要透彻了解面向对象的三大特性:封装、继承和多态。
本系列是总结python的设计模式,所以本篇的内容为基于python的、与设计模式强相关的对象特性,如无特殊说明默认版本为python3,默认新式类。
封装
封装本身包含“封”和“装”的过程,把希望对外隐藏、对内开放的变量或方法实现“封”起来,把变量及对应的数据值、方法及对应的方法实现“装”到命名空间。封装并不是单纯意义上的隐藏,而是为了增强安全性和简化编程,调用者不必了解具体的实现细节,只要通过相应的接口、特定的访问权限即可使用类的变量及方法。
封装的好处
- 隐藏类的内部细节
- 限制(直接)访问
- 隔离变化
- 方便复用
封装的类型
- 数据封装:保护隐私
定义变量或属性数据的目的就是方便调用,同时又不想被类外部直接使用,所以在类内部开辟相应的接口,让类外部的调用者通过接口间接地操作隐藏的变量或属性数据。
- 方法封装:隔离变化,方便复用
定义方法的目的也是方便调用,相同的实现过程可以抽取出来封装成方法,供类内外部的使用者调用,这样既隔离了变化,又方便复用。
封装的约定
类的封装中支持类外部的使用者直接调用某些变量、属性或方法,也支持把某些变量、属性或方法隐藏起来,也就是说定义成私有的只在类的内部使用,外部无法访问,或者留出相应的接口供外部间接调用。
我们一直在封装特性中强调保护隐私(定义为私有),间接调用等,如何在类中实现这些目的呢?变量、属性或方法的调用都有哪些约定或限制呢?其实python中私有化的实现也非常简单,在准备私有化的变量、属性或方法名字前加上双下划线(__)即可。可参考python中属性"xxx", "_xxx", "__xxx"及"__xxx__"在使用上的区别。
- 前后无下划线(xxx): 调用时使用"类.属性","实例.属性"或"self.属性"的方式
在类内部可以通过self.属性访问,类外部可以通过“类.属性”或"实例.属性"的方式访问属性。
class Student(object): school = "ABC University" def __init__(self, name, age): self.name = name self.age = age def study(self, course="English"): print ("{} is studying {}.".format(self.name, course)) >>> s = Student("Tom", 18) >>> s.study() Tom is studying English. >>> s.name 'Tom' >>> Student.school 'ABC University' >>>
- 单下划线(_xxx):虽然语法上该属性可以被访问,但是,请把此属性视为私有变量,不要随意访问
如自定义模块my_module,包含带单下划线的属性和方法,内容如下
1 _name = "Tom" 2 def external_test(): 3 return "Invocation without prefix '_'" 4 def _internal_test(): 5 return "Invocation with prefix '_'"
通过from my_module import * 导入,然后调用
- 双下划线(__xxx):类外部不能直接访问__xxx,类外部可以通过转换后的_类名__xxx访问__xxx
在类外部,未加双下划线的属性"name"可以正常访问,加了双下划线的属性age报AttributeError错误,需转换成_Student__age才可正常访问。
class Student(object): school = "ABC University" def __init__(self, name, age): self.name = name self.__age = age def study(self, course="English"): print ("{} is studying {}.".format(self.name, course)) >>> s = Student("Tom", 18) >>> s.name 'Tom' >>> s.__age Traceback (most recent call last): File "<pyshell#161>", line 1, in <module> s.__age AttributeError: 'Student' object has no attribute '__age' >>> s._Student__age 18 >>>
注意:在子类中定义的__xxx不会覆盖父类定义的__xxx,因为子类中的__xxx变形成_子类名__xxx,而父类中的相同的__xxx变形成_父类名__xxx,不是同一个属性,所以子类无法覆盖父类。所以如果在设计上父类不希望子类覆盖自己的方法,可将该方法设置为私有。
- 前后双下划线(__xxx__):python内置属性
python系统自带,可以直接拿来使用,自定义属性时不应与内置属性重名。
事实上,python中的这种约定并没有真正限制我们对属性和方法的访问,语法上并没有限制对_xxx的访问,通过_类名__属性名即可访问相应的属性__xxx,从项目开发的角度来说,这种突破约定强行访问的方式终究不太规范,如果我们希望类外部能够访问隐藏信息或私有属性,可以留出相应的接口供类外部间接调用。
Property属性封装
通常我们写代码时,不希望外部代码能够轻易地修改内部属性,同时又希望自己能够很方便地从外部获取数据。而且在我们从外部改变参数值时,又希望通过内部函数去检验参数的健壮性,以确保程序正常运行。
如何实现这种“既要...又要...也要...呢”?python通过装饰器方式和静态方法方式给我们提供了这种便利性。参考如下:
- 装饰器方式提供三种属性封装:@property, @func.setter, @func.deleter,三者的不同组合代表不同的含义
- @property:表示只读
- @property & @func.setter:表示可读可写
- @property & @func.setter & @func.deleter:表示可读可写可删除
下面就在实际例子中看一下具体用法,我们不希望外部能够随意访问学生的得分,所以属性设置为_score.
class Student(object): def __init__(self, name, score): self.name = name self.__score = score @property def score(self): return self.__score @score.setter def score(self, value): if isinstance(value, int): if value < 0 or value > 100: raise ValueError("The value of score must be between 0~100.") else: self.__score = value else: raise TypeError("The type of score must be int") @score.deleter def score(self): del self.__score >>> s = Student("Tom", 80) >>> s.score 80 >>> s.score = 60 >>> s.score 60 >>> del s.score >>> s.score Traceback (most recent call last): File "<pyshell#190>", line 1, in <module> s.score File "<pyshell#184>", line 7, in score return self.__score AttributeError: 'Student' object has no attribute '_Student__score' >>>
- 静态方法方式封装属性:property构造方法提供四个参数,格式为property([fget[, fset[, fdel[, doc]]]])
- fget为方法名,调用"对象.属性"时自动触发执行
- fset为方法名,调用"对象.属性=xxx"时自动触发执行
- fdel为方法名,调用"del 对象.属性"时自动触发执行
- doc为字符串,调用用"类.属性.__doc__"时自动打印该字符串信息
class Student(object): def __init__(self, name, score): self.name = name self.__score = score def get_score(self): return self.__score def set_score(self, value): if isinstance(value, int): if value < 0 or value > 100: raise ValueError("The value of score must be between 0~100.") else: self.__score = value else: raise TypeError("The type of score must be int") def del_score(self): del self.__score SCORE = property(get_score, set_score, del_score, "My Score") >>> s = Student("Tom", 80) >>> s.SCORE 80 >>> s.SCORE = 60 >>> s.SCORE 60 >>> Student.SCORE.__doc__ 'My Score' >>> del s.SCORE >>> s.SCORE Traceback (most recent call last): File "<pyshell#214>", line 1, in <module> s.SCORE File "<pyshell#207>", line 6, in get_score return self.__score AttributeError: 'Student' object has no attribute '_Student__score' >>>
继承
继承是面向对象的重要特征之一,是类与类之间或接口与接口之间的父子关系。python中不支持interface,所以也就无所谓接口与接口之间的继承 了,但是python支持多重继承,即新建的类可以继承一个或多个父类,该新建类称为子类(Subclass)或派生类,被继承的类称为基类(Base class)、父类或超类(Super class)。
继承的好处
通过继承,子类可以获得父类所有的属性和功能,又可以根据需要进行修改和扩展,比如说在子类中增加新的属性或方法,具体好处如下:
- 实现代码复用
- 多态的应用
- 归一化设计
单继承
python中的单继承是指继承一个父类,具体语法就不详细说了,此处比较关心有以下几点:
- 子类能访问到的父类中的变量、属性和方法
基类的静态变量、静态方法,基类实例的方法、成员变量,基类的构造函数、析构函数都可以被子类访问,但是类的私有属性和私有变量是只能在类内部调用,不能被子类访问。
class Person(object): public_var = "公有" # 类的静态变量,由类调用,子类可访问 __private_var = "私有" # 类的私有变量,只能由类内部调用,类外部及子类不能访问 def __init__(self, name, age): self.name = name # 实例的成员变量,由对象调用self.name,子类可访问 self.__age = age # 实例的私有变量,只能由类内部调用,类外部及子类不能访问 @classmethod def class_func(cls): # 类的方法,由类调用Person.class_func(),子类和子类的实例都可以访问 print ("class method") @staticmethod def static_func(): # 类的静态方法,由类调用Person.static_func(),子类和子类的实例都可以访问 print ("static method") def comm_func(self): # 实例的方法,由对象调用self.comm_func() print ("{} is {} years old.".format(self.name, self.__age)) def __private_func(self): # 类的私有方法,只能由类内部调用,类外部及子类不能访问 print ("The value of __private_var is {}.".format(self.__private_var)) def proxy_private(self): self.__private_func() @property def age(self): # 属性封装,由对象调用self.age return self.__age class Student(Person): pass >>> s = Student("Tom", 18) >>> s.public_var '公有' >>> s.__private_var # 类的私有变量,只能由类内部调用,类外部及子类不能访问 Traceback (most recent call last): File "<pyshell#89>", line 1, in <module> s.__private_var AttributeError: 'Student' object has no attribute '__private_var' >>> s.name 'Tom' >>> s.__age # 实例的私有变量,只能由类内部调用,类外部及子类不能访问 Traceback (most recent call last): File "<pyshell#91>", line 1, in <module> s.__age AttributeError: 'Student' object has no attribute '__age' >>> s.comm_func() Tom is 18 years old. >>> s.age 18 >>> s.__private_func() # 类的私有方法,只能由类内部调用,类外部及子类不能访问 Traceback (most recent call last): File "<pyshell#94>", line 1, in <module> s.__private_func() AttributeError: 'Student' object has no attribute '__private_func' >>> s.proxy_private() The value of __private_var is 私有. >>> Student.class_func() class method >>> s.class_func() class method >>> Student.static_func() static method >>> s.static_func() static method >>>
- 子类的变量、属性或方法与父类重名时的重写
如果父类方法的功能不能满足项目需求,可以考虑在子类中重写父类的方法。当然对于不同访问限制的变量或方法,重写时的处理也会不同,比如子类和父类的初始化,公有变量 VS 私有变量,公有方法 VS 私有方法。
- __init__()方法的重写
如果子类中没有定义__init__()方法,子类初始化时会自动隐式调用父类的__init__()方法;如果子类定义了__init__()方法,子类实例化时就会调用自己的__init__()方法。如果想使用或者扩展父类的初始化,最好显式调用父类的__init__().
class Person: def __init__(self, name, age): self.name = "Tom" self.age = 18 def get_name(self): print ("name: {}".format(self.name)) class Student(Person): def get_name(self): print ("The student is {}.".format(self.name)) class Teacher(Person): def __init__(self, name, age, score): super(Teacher, self).__init__(name, age) # 如果想使用或者扩展父类的初始化,最好显式调用父类的__init__() self.score = score def get_name(self): print ("The teacher is {}.".format(self.name)) def get_info(self): print ("{} is {} years old, his/her score is {}.".format(self.name, self.age, self.score)) >>> s = Student("John", 20) # 子类初始化时会自动隐式调用父类的__init__()方法 >>> s.get_name() The student is Tom. >>> t = Teacher("Joe", 30, 90) # 如果子类定义了__init__()方法,子类实例化时就会调用自己的__init__()方法 >>> t.get_info() Tom is 18 years old, his/her score is 90. >>>
- 公有变量的重写
class Person(object): public_var = "基类_公有变量" def baseclass_func(self): print ("public_var: {}".format(self.public_var)) class Student(Person): public_var = "子类_公有变量" def subclass_func(self): print ("public_var: {}".format(self.public_var)) >>> s = Student() >>> s.public_var '子类_公有变量' >>> s.baseclass_func() public_var: 子类_公有变量 >>> s.subclass_func() public_var: 子类_公有变量
- 私有变量的重写
在子类中定义的__xxx不会覆盖父类定义的__xxx,因为子类中的__xxx变形成_子类名__xxx,而父类中的相同的__xxx变形成_父类名__xxx,不是同一个属性,所以子类无法覆盖父类。
class Person(object): __private_var = "基类_私有变量" def baseclass_func(self): print ("__private_var: {}".format(self.__private_var)) class Student(Person): __private_var = "子类_私有变量" def subclass_func(self): print ("__private_var: {}".format(self.__private_var)) >>> s = Student() >>> s.baseclass_func() __private_var: 基类_私有变量 >>> s.subclass_func() __private_var: 子类_私有变量 >>> s._Person__private_var '基类_私有变量' >>> s._Student__private_var '子类_私有变量' >>>
- 公有方法的重写
子类的公有方法重写父类的公有方法时,子类调用的是自己的公有方法。
class Person(object): def test(self): self.public_func() def public_func(self): print ("baseclass function") class Student(Person): def public_func(self): print ("subclass function") >>> s = Student() >>> s.test() subclass function >>>
- 私有方法的重写
子类的私有方法重写父类的私有方法时,子类调用的还是父类的私有方法。
class Person(object): def test(self): self.__private_func() def __private_func(self): print ("baseclass private function") class Student(Person): def __private_func(self): print ("subclass private function") >>> s = Student() >>> s.test() baseclass private function >>>
- 子类调用父类中同名的变量、属性或方法
- 子类调用父类的构造方法:子类调用父类的构造方法有三种方式,初始化的参数根据实际情况填写
- 类名.__init__(self, args):更直观,但多重继承时很麻烦,需要使用不同类名分别调用
- super().__init__(args):类定义中调用本类的父类方法,推荐使用
- super(子类名,子类的实例).__init__(args): 多重继承时,只需要在子类中使用一次super初始化所有父类
class Person(object): def __init__(self, name): self.name = name print ("baseclass initialization, {} is coming.".format(self.name)) class Student(Person): def __init__(self): Person.__init__(self, "Tom") # super().__init__("Tom") # super(Student, self).__init__("Tom") print ("subclass initialiazation, {} is here.".format(self.name)) >>> s = Student() baseclass initialization, Tom is coming. subclass initialiazation, Tom is here.
class Student(Person): def __init__(self): # Person.__init__(self, "Tom") super().__init__("Tom") # super(Student, self).__init__("Tom") print ("subclass initialiazation, {} is here.".format(self.name)) >>> s = Student() baseclass initialization, Tom is coming. subclass initialiazation, Tom is here.
class Student(Person): def __init__(self): #Person.__init__(self, "Tom") # super().__init__("Tom") super(Student, self).__init__("Tom") print ("subclass initialiazation, {} is here.".format(self.name)) >>> s = Student() baseclass initialization, Tom is coming. subclass initialiazation, Tom is here. >>>
- 子类调用父类中的同名变量
子类调用父类中的同名变量分两种情况:调用父类中的私有变量 & 调用父类中的公有变量,前者可以使用“_父类名__变量名”的方式调用,后者父类和子类使用的同名公有变量具有相同的id。
- 子类调用父类中的私有变量
class Person(object): __score = "80" def test(self): print (self.__score) class Student(Person): __score = "60" def test(self): print (self.__score) def test_super(self): print (self._Person__score) >>> s = Student() >>> s.test() 60 >>> s.test_super() 80 >>>
- 子类调用父类中的公有变量
子类调用父类中的公有变量时操作的是同一个id,不论是通过self.变量 = value还是通过super(子类,self).func(args)的形式。
- 子类初始化中采用self.变量 = value
class Student(Person): def __init__(self): print ("Student Initialization Start") #print ("Student Initialization - Super() Invocation Start") #super(Student, self).__init__() #print ("score: {}".format(self.score)) #print ("the id of score: {}".format(id(self.score))) #print ("Student Initialization - Super() Invocation End") self.score = 10 print ("score: {}".format(self.score)) print ("the id of score: {}".format(id(self.score))) print ("Student Initialization End") def get(self): print ("Student Get Start") print ("score: {}".format(self.score)) print ("the id of score: {}".format(id(self.score))) print ("Student Get End") return id(self.score) def get_super(self): print ("Student Get Super") return super(Student,self).get() >>> s = Student() Student Initialization Start score: 10 the id of score: 1669413264 Student Initialization End >>> s.get() Student Get Start score: 10 the id of score: 1669413264 Student Get End 1669413264 >>> s.get_super() Student Get Super Person Get Start score: 10 the id of score: 1669413264 Person Get End 1669413264 >>>
- 子类初始化中采用super(子类,self).func(args)
class Student(Person): def __init__(self): print ("Student Initialization Start") print ("Student Initialization - Super() Invocation Start") super(Student, self).__init__() print ("score: {}".format(self.score)) print ("the id of score: {}".format(id(self.score))) print ("Student Initialization - Super() Invocation End") # self.score = 10 print ("score: {}".format(self.score)) print ("the id of score: {}".format(id(self.score))) print ("Student Initialization End") def get(self): print ("Student Get Start") print ("score: {}".format(self.score)) print ("the id of score: {}".format(id(self.score))) print ("Student Get End") return id(self.score) def get_super(self): print ("Student Get Super") return super(Student,self).get() >>> s = Student() Student Initialization Start Student Initialization - Super() Invocation Start Person Initialization Start score: 0 the id of score: 1669413104 Person Initialization End score: 0 the id of score: 1669413104 Student Initialization - Super() Invocation End score: 0 the id of score: 1669413104 Student Initialization End >>> s.get() Student Get Start score: 0 the id of score: 1669413104 Student Get End 1669413104 >>> s.get_super() Student Get Super Person Get Start score: 0 the id of score: 1669413104 Person Get End 1669413104 >>>
- 子类初始化中先super(子类,self).func(arg),再self.变量 = value,则前者会被后者覆盖
class Person(object): def __init__(self): print ("Person Initialization Start") self.score = 0 print ("score: {}".format(self.score)) print ("the id of score: {}".format(id(self.score))) print ("Person Initialization End") def get(self): print ("Person Get Start") print ("score: {}".format(self.score)) print ("the id of score: {}".format(id(self.score))) print ("Person Get End") return id(self.score) class Student(Person): def __init__(self): print ("Student Initialization Start") print ("Student Initialization - Super() Invocation Start") super(Student, self).__init__() print ("score: {}".format(self.score)) print ("the id of score: {}".format(id(self.score))) print ("Student Initialization - Super() Invocation End") self.score = 10 print ("score: {}".format(self.score)) print ("the id of score: {}".format(id(self.score))) print ("Student Initialization End") def get(self): print ("Student Get Start") print ("score: {}".format(self.score)) print ("the id of score: {}".format(id(self.score))) print ("Student Get End") return id(self.score) def get_super(self): print ("Student Get Super") return super(Student,self).get() >>> s = Student() Student Initialization Start Student Initialization - Super() Invocation Start Person Initialization Start score: 0 the id of score: 1669413104 Person Initialization End score: 0 the id of score: 1669413104 Student Initialization - Super() Invocation End score: 10 the id of score: 1669413264 Student Initialization End >>> s.get() Student Get Start score: 10 the id of score: 1669413264 Student Get End 1669413264 >>> s.get_super() Student Get Super Person Get Start score: 10 the id of score: 1669413264 Person Get End 1669413264 >>>
根据以上分析,子类调用父类的公用变量时一般应采用super(子类,self).func(arg)的方式操作。
- 子类调用父类的重写方法
类似子类调用父类的构造方法。
class Person(object): def __init__(self): print ("Person Initialization Start") self.score = 100 print ("score: {}".format(self.score)) print ("the id of score: {}".format(id(self.score))) print ("Person Initialization End") def set(self, score): print ("Person Set Start") self.score = score print ("score: {}".format(self.score)) print ("the id of score: {}".format(id(self.score))) print ("Person Set End") class Student(Person): def __init__(self): print ("Student Initialization Start") print ("Student Initialization - Super() Invocation Start") super(Student, self).set(123) print ("score: {}".format(self.score)) print ("the id of score: {}".format(id(self.score))) print ("Student Initialization - Super() Invocation End") # self.score = 123 print ("score: {}".format(self.score)) print ("the id of score: {}".format(id(self.score))) print ("Student Initialization End") def set(self, score): print ("Student Set Start") self.score = score print ("value: {}".format(self.score)) print ("the id of score: {}".format(id(self.score))) print ("Student Set End") def set_super(self, score): print ("Student Set Super") return super(Student,self).set(score) >>> s = Student() Student Initialization Start Student Initialization - Super() Invocation Start Person Set Start score: 123 the id of score: 1669415072 Person Set End score: 123 the id of score: 1669415072 Student Initialization - Super() Invocation End score: 123 the id of score: 1669415072 Student Initialization End >>> s.set(456) Student Set Start value: 456 the id of score: 31980288 Student Set End >>> s.set_super(789) Student Set Super Person Set Start score: 789 the id of score: 31980384 Person Set End >>>
多重继承
多重继承是指继承自多个父类,这也是python与java面向对象机制的不同特性之一。多重继承相对于单继承来说要复杂一些,首先遇到的问题就是如何处理潜在的命名冲突,如果两个或多个父类有相同的方法名或变量名时,python会自动按搜索顺序选择包含该方法名或变量名的第一个父类,同时忽略其他父类中的同名方法或同名变量。
在处理多重继承时,经典类和新式类会有不同,采用不同__init__方法调用时多重继承的初始化顺序也不同等等,所以实际项目中不建议使用多重继承,推荐使用Mixin。
- 经典类和新式类区别
- 是否默认派生基类:经典类默认没有派生自某个基类;新式类默认派生自object
- 适用版本:经典类是python2.2之前的东西,python2.7中还在兼容;新式类在python2.2之后的版本都可用,python3.x的版本就只承认新式类的
- 搜索顺序:经典类是按照“从左至右,深度优先”的方式去查找属性;新式类是“从左至右,广度优先”
- 多重继承的初始化顺序
- 初始化方法__init__()中采用"父类名.__init__(self, args)"方式
按照初始化方法中多个父类的调用顺序初始化。
class BaseClass(object): def __init__(self): print ("Init BaseClass">>> class ExtBaseClass(BaseClass): def __init__(self): BaseClass.__init__(self) print ("Init ExtBaseClass") class PlusBaseClass(BaseClass): def __init__(self): BaseClass.__init__(self) print ("Init PlusBaseClass") class SubClass(ExtBaseClass, PlusBaseClass): def __init__(self): ExtBaseClass.__init__(self) PlusBaseClass.__init__(self) >>> sc = SubClass() Init BaseClass Init ExtBaseClass Init BaseClass Init PlusBaseClass >>>
- 初始化方法__init__(args)中采用"super(subclass, self).__init__(args)"方式
按照跟子类中__init__()相反的顺序调用多个父类的初始化,可使用内置函数mro()查看。
class BaseClass(object): def __init__(self): print ("Init BaseClass") class ExtBaseClass(BaseClass): def __init__(self): super(ExtBaseClass, self).__init__() print ("Init ExtBaseClass") class PlusBaseClass(BaseClass): def __init__(self): super(PlusBaseClass, self).__init__() print ("Init PlusBaseClass") class SubClass(ExtBaseClass, PlusBaseClass): def __init__(self): super(SubClass, self).__init__() >>> sc = SubClass() Init BaseClass Init PlusBaseClass Init ExtBaseClass
>>> from pprint import pprint
>>> pprint(SubClass.mro())
[<class '__main__.SubClass'>,
<class '__main__.ExtBaseClass'>,
<class '__main__.PlusBaseClass'>,
<class '__main__.BaseClass'>,
<class 'object'>]
>>>
- 多重继承的搜索顺序
经典类是按照“从左至右,深度优先”的方式去查找属性;新式类是“从左至右,广度优先”。这里只关注新式类多重继承的搜索顺序。
class Animal(): def run(self): print ("Animals are running.") def eat(self): print ("Animals eat something.") class Pet(Animal): def run(self): print ("Pets are running.") class Cat(Animal): def run(self): print ("Cats are running.") def eat(self): print ("Cats eat something.") class Ketty(Pet, Cat): pass >>> k = Ketty() >>> k.run() Pets are running. >>> k.eat() Cats eat something. >>>
- 方法解析顺序(MRO: Mothod Resolution Order)
Python的多重继承虽然增强了扩展性,但同时也引发了很多问题,比如说二义性,如果父类和子类中存在同名方法或变量,则在调用过程中会由于版本、初始化方式、搜索顺序等的不同给程序运行结果带来冲突或不确定性。python中处理这些冲突或不确定性的方法就是MRO。
MRO用于在多继承是判断调用属性或方法的路径,即来自于哪个类,所以MRO对于单继承来说非常简单,但对于多重继承由于单调性和只能继承不能重写的问题就复杂了。这里不做MRO的专题讲解,主要提一下新式类MRO应用的规则及查看方式。
新式类的搜索顺序采用“广度优先”的方式去查找属性。
class Base1(object): def test(self): print ("Base1 Test") class Base2(object): def test(self): print ("Base2 Test") def run(self): print ("Base2 Run") class Sub1(Base1, Base2): pass class Sub2(Base1, Base2): def run(self): print ("Sub2 Run") class Foo(Sub1, Sub2): pass
>>> f = Foo()
>>> f.run()
Sub2 Run
>>> f.test()
Base1 Test >>> Foo.mro() [<class '__main__.Foo'>, <class '__main__.Sub1'>, <class '__main__.Sub2'>, <class '__main__.Base1'>, <class '__main__.Base2'>, <class 'object'>]
- Mixin模式
Mixin表示"混入"、”混合“,是一种在python里经常使用的模式,可以将多个类的功能单元进行组合。通常Mixin并不作为任何类的基类,也不关心与什么类一起使用,只是在运行时动态地同其他类组合使用,改变类的基类或类的方法,使得类的表现可以根据实际需求发生变化。
Mixin模式的好处:
- 在不修改任何源码的情况下对类进行扩展
- 保证各组件功能单一、相对独立、责任明确
- 根据实际需求把已有的功能组合起来实现”新类“
- 避免类继承尤其是多重继承的复杂性和局限性
Mixin的应用场景:
在设计类时,由于多重继承的复杂性和局限性,我们优先考虑使用Mixin来组合多个功能特性,而不是设计多层次的继承关系。Mixin的适用场景如下:
- 给一个基类组合一个或多个特性
- 在很多不同基类组合某个特性
- 给基类的特定对象扩展一个或多个特性
Mixin的实现
因为python本身就是动态语言,所以实现Mixin的方式非常灵活,因为应用场景的不同实际上Mixin的实现分为两种:一种是给基类组合Feature类,另一种是给基类的特定对象扩展特性。第二种的应用场景实际上是考虑到有时候我们希望扩展基类某个对象的特性,而不希望所有依赖该基类的所有引用都受到影响,因为运行时修改类的继承关系会带来同步问题。 所以我们使用Mixin是需要注意灵活运用其优势,变通性地屏蔽或扩展其劣势。
第一种给基类组合Feature类:分别采用直接定义、闭包动态定义、type动态构造的方式Mixin一个或多个Feature
假定我们有一个基类Base和两个Feature类需要做Mixin。
class Base(object): pass class Feature1Mixin(object): pass class Feature2Mixin(object): pass # 方式一: 直接定义一个类Mixin所有的feature class MySubClass1(Base, Feature1Mixin, Feature2Mxin): pass # 验证 Base.__bases__ MySubClass1.__bases__ # 方式二: 通过闭包动态定义类的方式实现Mixin def mixin(base, name, *args): class MixinClass(base, *args): pass MixinClass.__name__ = name return MixinClass MySubClass2 = mixin(Base, "MySubClass2", Feature1Mixin, Feature2Mixin) # 验证 Base.__bases__ MySubClass2.__bases__ # 通过type动态构造类的方式实现Mixin MySubClass3 = type("MySubClass3", (Base, Feature1Mixin, Feature2Mixin), {}) # 验证 Base.__bases__ MySubClass3.__bases__
第二种是给基类的特定对象扩展特性:根据__dict__特性扩展对象的行为
class PlugIn(object): def __init__(self): self._extended_methods = [] def plugin(self, target): for func in self._extended_methods: target.__dict__[func.__name__] = func def plugout(self, target): for func in self._extended_methods: del target.__dict__[func.__name__]>>> class Feature1(PlugIn): def __init__(self): super(Feature1, self).__init__() self._extended_methods.append(self.feature1_func) def feature1_func(self): print ("feature1 function") class Feature2(PlugIn): def __init__(self): super(Feature2, self).__init__() self._extended_methods.append(self.feature2_func) def feature2_func(self): print ("feature2 function") class ExtendedClass(): pass >>> s = ExtendedClass() >>> Feature1().plugin(s) >>> Feature2().plugin(s) >>> s.feature1_func() feature1 function >>> s.feature2_func() feature2 function >>> Feature1().plugout(s) >>> Feature2().plugout(s) >>> s.feature1_func() Traceback (most recent call last): File "<pyshell#105>", line 1, in <module> s.feature1_func() AttributeError: 'ExtendedClass' object has no attribute 'feature1_func' >>> s.feature2_func() Traceback (most recent call last): File "<pyshell#106>", line 1, in <module> s.feature2_func() AttributeError: 'ExtendedClass' object has no attribute 'feature2_func' >>>
Super用法
在类的继承中,如果子类重写了父类的方法,那么该子类方法会覆盖父类方法。有时候我们希望在子类中调用父类的同名方法,此类需求可以使用super()来实现。super()常见的用法有:
- 单继承时子类调用父类的初始化方法__init__
- 单继承时子类调用父类的同名成员方法
- 多重继承时子类调用多个父类同名方法时的类继承顺序
其实这些用法在“单继承”和“多重继承”中都有说明,所以这里只阐述一下super的使用原理,研究super的调用顺序和子类mro()/__mro__打印信息对照会比较容易理解。
super的底层实现如下:
def super(cls, inst): mro = inst.__class__.__mro__() return mro[mro.index(cls) + 1]
此处,cls代表当前类,inst代表当前类的实例。以上代码实现的是先获取当前类实例inst的MRO列表,然后查找当前类cls在当前MRO列表中的index,返回mro[index+1],即当前类cls的下一个类。
super()调用结合MRO列表信息会非常明显。
class Base(object): def __init__(self): print ("enter Base") print ("leave Base") class A(Base): def __init__(self): print ("enter A") super(A, self).__init__() print ("leave A") class B(Base): def __init__(self): print ("enter B") super(B, self).__init__() print ("leave B") class C(A, B): def __init__(self): print ("enter C") super(C, self).__init__() print ("leave C") >>> c = C() enter C enter A enter B enter Base leave Base leave B leave A leave C >>> c.__class__.mro() [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Base'>, <class 'object'>] >>>
继承的查看
python中有两个查看继承关系的属性和两个判断继承的函数:
- __base__ & __bases__:查看子类的父类
- __mro__/mro():查看所有基类的线性继承关系顺序,只有新式类中才有
- isinstance(obj, type):检查实例类型
- issubclass(subclass, class):检查类继承
多态
多态是指同一个事物具有多种形态, 就面向对象机制来说,多态表示同一个接口或方法可以处理多种形态的输入,调用不同的子类实例会产生不同的行为,无需无需明确知道这个实例的type是什么。多态依赖于继承,没有继承就没有多态,子类通过重写父类的方法实现多态。
就python的多态来说,因为是动态语言,所以python自带多态性,比如鸭子类型,又由于python不像Java那样具备的interface和abstract关键字,所以python的多态可以由开发约定的未实现普通类,或abc模块支持的抽象类和抽象方法实现。
多态的好处
- 归一化:不论对象有多少个,只要实现了同一个接口或抽象类,使用者都可以用同一种方式去调用
- 易扩展:通过继承同一接口类或抽象类可以根据创建一个或多个新类,使用者无需更改已有的代码或调用方式,这也是开闭原则的应用
鸭子类型
鸭子类型是python动态语言的一个特性,某个类或实例属于哪种类型并不是由继承自特定的类或实现了特定的接口约定,而是由该类或实例具备哪些“方法和属性的集合”决定。
动态语言调用实例的方法时不检查类型,只要方法存在,参数正确,就可以调用。这就是动态语言的“鸭子类型”,一个对象只要“长得像鸭子,走路像鸭子,又能像鸭子一样游泳”,那它就可以被看作是一个鸭子,而不是因为它是由鸭蛋孵出的。
鸭子类型在python中有很多应用,比如iterator的创建,只要实现了__iter__()和__next__()的类就可以成为迭代器;实现了__getitem__方法的类,就可以当作collection,在该类的实例上调用切片、获取子项等方法。
- 自定义迭代器:实现__iter__()和__next__()
class MyIterator(object): def __init__(self, start=0, end=0): self.start = start self.end = end def __iter__(self): return self; def __next__(self): value = self.start if (value > self.end): raise StopIteration self.start += 3 return value for item in MyIterator(0, 20): print (item,) 0 3 6 9 12 15 18 for item in MyIterator(2, 20): print (item,) 2 5 8 11 14 17 20
- 自定义collection:实现__getitem__()
class UniqueList(object): def __init__(self, items): self.items = [] for item in items: self.add(item) def add(self, item): if item not in self.items: self.items.append(item) def __getitem__(self, index): return self.items[index] >>> ulist = UniqueList([1,2,3,4,4,5,6,7,7,8,9,9,9,10]) >>> print (ulist[:]) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> print (ulist[2:5]) [3, 4, 5] >>> print (ulist[3]) 4 >>>
abc模块,抽象类和接口类
python本身并没有提供原生的接口类和抽象类,接口和抽象类更多的一种设计模式和开发约定。python中的抽象类需要借助abc模块实现。
abc模块
python本身不提供抽象类和接口机制,要想实现抽象类或接口机制,可以借助abc模块中的ABC,ABCMeta,@abstractmethod, @abstractproperty, @abstractmethod,@classmethod, __subclasshook__, @abstractclassmethod等。各项含义如下:
- ABC:Abstract Base Class(抽象基类)的缩写,相当于Java中的接口或抽象类
- ABCMeta:抽象基类的元类,是abc模块的核心,用于在python中创建抽象基类
- @abstractmethod:声明抽象方法
- @classmethod和__subclasshook__:联合使用改变issubclass()和isinstance()方法的行为
- @abstractclassmethod, @abstractstaticmethod,@abstractproperty: python3.3后已过时
抽象类
在python中抽象类借助abc模块中的ABCMeta类和abstractclassmethod实现,抽象类是一种特殊的类,其中有未实现的抽象方法,只可被继承、不能被实例化。
抽象类既包含未实现的抽象方法也包含已实现的具体方法,可以包含成员属性,而且属性没有是否抽象的分别,抽象类自身不能直接实例化,需要通过子类继承后由子类实例化。
抽象类是一个介于类和接口类之间的概念,同时具备类和接口的部分特性,一般用来实现归一化设计。抽象类未实现的抽象方法在其子类中具有语法上的强制实现性,接口类中未实现的方法在其子类中不具有语法上的强制实现性。
具体化抽象类的方法有两种:一种是继承,另一种是注册。继承方式中抽象类会出现在具体类的MRO中, 注册方式中抽象类不会出现在具体类的MRO中。
from abc import ABCMeta, abstractmethod
class Animal(metaclass=ABCMeta): @abstractmethod def run(self): pass def play(self): print ("Animal is playing") # 继承方式 class Tiger(Animal): def run(self): print ("Tiger is running") >>> class Dog(Animal): def run(self): print ("Dog is running") >>> tiger = Tiger() >>> issubclass(Tiger, Animal) True >>> isinstance(tiger, Animal) True >>> Tiger.__mro__ # 继承方式中抽象类会出现在具体类的MRO中 (<class '__main__.Tiger'>, <class '__main__.Animal'>, <class 'object'>) >>> dog = Dog() >>> issubclass(Dog, Animal) True >>> isinstance(dog, Animal) True >>> Dog.__mro__ (<class '__main__.Dog'>, <class '__main__.Animal'>, <class 'object'>) # 注册方式 >>> class Cat(object): def run(self): print ("Cat is running") >>> Animal.register(Cat) <class '__main__.Cat'> >>> issubclass(Cat, Animal) True >>> isinstance(Cat(), Animal) True >>> Cat.__mro__ # 注册方式中抽象类不会出现在具体类的MRO中 (<class '__main__.Cat'>, <class 'object'>)
重写__subclasshook__(cls, subclass)改变issubclass()或isinstance()的行为,此时__subclasshook__(cls, subclass)必须为@classmethod
from abc import ABCMeta, abstractmethod
class Animal(metaclass=ABCMeta): @abstractmethod def run(self): pass def play(self): print ("Animal is playing") @classmethod def __subclasshook__(cls, subclass): if cls is Animal: if any("play" in item.__dict__ for item in subclass.__mro__): return True
else: raise NotImplementedError
class Monkey(object): def play(self): print ("Monkey is running.") >>> issubclass(Monkey, Animal) True >>> isinstance(Monkey(), Animal) True >>>
接口类
接口就是对外提供抽象方法的集合,提供接口功能的类就叫接口类。
接口类中都是未实现的方法,子类继承接口类,并且实现接口类中未实现的方法。接口继承实际上是抽象出一个兼容性接口,使得外部调用者无需关心具体细节,可以归一化地处理实现了该兼容接口的所有对象。
from abc import ABCMeta, abstractmethod
class FileOperInterface(metaclass=ABCMeta):
@abstractmethod
def read(self):
pass
@abstractmethod
def write(self):
pass
class TxtFile(FileOperInterface):
def read(self):
print ("Read Text File")
def write(self):
print ("Write Text File")
class XMLFile(FileOperInterface):
def read(self):
print ("Read XML File")
def write(self):
print ("Write XML File")
>>> txt_file = TxtFile()
>>> xml_file = XMLFile()
>>> txt_file.read()
Read Text File
>>> xml_file.write()
Write XML File
>>> txt_file.write()
Write Text File
>>> xml_file.write()
Write XML File
>>>
多态与扩展性
面向对象机制中的多态更多地和易扩展、易维护联系起来,对于项目中的增量需求来说,在源代码中增加新特性是常见的场景,对于持开闭原则的框架设计来说,最好的方式就是在原结构的基础上进行扩展,既简便又容易维护。多态特性恰恰能够帮助我们实现这个过程。
举个简单又实用的例子,我们解析的配置文件格式可能有很多种,如ini, conf, cfg, json, yaml等,在项目开始时不太可能把所有配置文件的类型都涵盖进来并且生成对应的具体解析类,更多的做法初期覆盖常用的配置文件类型,并预留相应的接口,以备后续扩展。如下就以配置文件解析为例,项目初期已具备ini, json的配置文件解析类,现在希望增加对于yaml格式配置文件的解析。简洁起见,配置文件中的参数结构只限两层,只提供items列表及键值查询及简单健壮性检查功能。
配置文件内容
# 已存在对ini & json格式配置文件解析
config.ini [default] host = 127.0.0.1 port = 3306 [user] username = user123 password = passwd123 config.json { "default": [{"host": "127.0.0.1", "port": "3306"}], "user": [{"username": "user123", "password": "passwd123"}] } # 新增支持yaml配置文件的需求 config.yaml default: host: 127.0.0.1 port: 3306 user: username: user123 password: passwd123
配置文件接口类
class BaseConfigFileParser(object): def __init__(self): self.config_keys_list = [] self.config_items_dict = {} def get_all_keys(self): pass def get_single_value(self, search_section, search_key): pass
已存在的ini和json配置文件解析类
import configparser class IniConfigFileParser(BaseConfigFileParser): def __init__(self, config_file_path): super(IniConfigFileParser, self).__init__() self.config = configparser.ConfigParser() if config_file_path.endswith(".ini"): self.config.read(config_file_path) else: raise ValueError("Please input the corrent config file.") def get_all_keys(self): for section in self.config.sections(): for value in self.config.options(section): self.config_keys_list.append(value) return self.config_keys_list def get_single_value(self, search_section, search_key): if search_section in self.config.sections(): return self.config.get(search_section, search_key) else: raise ValueError("The key does not exist.")
import json import os class JsonConfigFileParser(BaseConfigFileParser): def __init__(self, config_file_path): super(JsonConfigFileParser, self).__init__() if config_file_path.endswith(".json"): with open(config_file_path, 'rb') as fp_json: self.config = json.load(fp_json) else: raise ValueError("Please input the corrent config file.") def get_all_keys(self): for key in self.config.keys(): item = self.config.get(key) if isinstance(item, (dict, list)): for item_key in item.keys(): self.config_keys_list.append(item_key) else: self.config_keys_list.append(key) return self.config_keys_list def get_single_value(self, search_section, search_key): if search_section in self.config.keys(): return self.config[search_section][search_key] else: raise ValueError("The key does not exist.")
扩展的yaml配置文件解析类
import yaml import os class YamlConfigFileParser(BaseConfigFileParser): def __init__(self, config_file_path): super(YamlConfigFileParser, self).__init__() file_ext = (".yam", ".yaml") if config_file_path.endswith(file_ext): with open(config_file_path, 'rb') as fp_yaml: self.config = yaml.load(fp_yaml) else: raise ValueError("Please input the corrent config file.") def get_all_keys(self): for item in self.config: item_value = self.config.get(item) if isinstance(item_value, (dict, list)): for key in item_value: self.config_keys_list.append(key) else: self.config_keys_list.append(item) return self.config_keys_list def get_single_value(self, search_section, search_key): if search_section in self.config: return self.config[search_section][search_key] else: raise ValueError("The key does not exist.")
客户端测试调用
if __name__ == '__main__': ini_config = IniConfigFileParser("D:DocumentForTestconfig.ini") json_config = JsonConfigFileParser("D:DocumentForTestconfig.json") yaml_config = YamlConfigFileParser("D:DocumentForTestconfig.yaml") print ("all keys list in config.ini : {} ".format(ini_config.get_all_keys())) print ("all keys list in config.json : {} ".format(json_config.get_all_keys())) print ("all keys list in config.yaml : {} ".format(yaml_config.get_all_keys())) print ("===========================================================") print ("value of specific key in config.ini : {} ".format(ini_config.get_single_value("default", "port"))) print ("value of specific keyt in config.json : {} ".format(json_config.get_single_value("default", "port"))) print ("value of specific key in config.yaml : {} ".format(yaml_config.get_single_value("default", "port"))) print ("===========================================================")