Week 9
本周学习主要内容包括序列化与反序列化(剩余部分),面向对象
序列化应用
- Python程序之间可以用pickle解决序列化、反序列化
- 但如果需要跨平台、跨语言或跨协议,pickle就不太适用了(不推荐使用pickle)
- 需要公共协议,例如XML、Json、Protocol Buffer
Json
JSON(JavaScript Object Notation, JS对象标记)是一种轻量级的数据交换格式。它基于ECMAScript(w3c组织指定的JS规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。
Json数据类型
- 值(value):双引号引起来的字符串、数值、true和false、null、对象、数组……这些都是值
- **字符串:**由双引号包围起来的任意自负的组合,可以有转义字符
- **数值:**有正负,整数、浮点数
- 对象(onject):
- 无需的键值对集合
- 格式:{key1:value1,key2:value2,…,keyn:valuen}
- key必须是一个字符串,需要双引号包围这个字符串
- value可以是任意合法的值
- **数组(array):**有序的值的集合,格式:[val1,val2,…,valn]
json模块
Python与Json
Python支持少量内建数据类型到json类型的转换
python类型 | Json类型 |
---|---|
True | true |
False | false |
None | null |
str | string |
int | integer |
float | float |
list | array |
dict | object |
常用方法
python类型 | Json类型 |
---|---|
dumps | json编码 |
dump | json编码并存入文件 |
loads | json解码 |
load | json解码,从文件读取数据 |
- 一般json编码的数据很少落地,数据都是通过网络传输,传输时要考虑压缩
- 本质上来说它就是个文本,是个字符串
- json很简单,几乎编程语言都支持Json,所以应用范围十分广泛
MessagePack
- 是一个基于二进制高效的对象序列化类库,可用于跨语言通信
- 可以向JSON那样在许多语言之间交换结构对象,比JSON更快速轻巧
- 支持Python、Ruby、Java、C/C++ 等众多语言
- 兼容json和pickle
- 简单易用,高校压缩,支持语言丰富
- 用它序列化很好
面向对象
语言分类
- 面向机器:
抽象成机器指令,机器容易理解;
代表:汇编语言 - 面向过程:
排出步骤,突发情况及解决方式
问题规模小,可以步骤化,按部就班处理;
代表:C语言 - 面向对象(OOP):
随着计算机需要解决的问题规模的扩大,情况越来越复杂,需要很多人、部门协作,面向过程不太合适。Python是支持面向对象编程范式的高级语言;
代表:C++、Java、Python等
面向对象
- 类(class):抽象概念,万事万物的抽象,是一类事物的共同特征的集合,用计算机语言来描述类,是属性和方法的集合
-** 对象(instance、object)**:对象是类的具象,是一个实体;对于我们每个人这个个体,都是抽象概念人类的不同的实体 - 属性:对对象状态的抽象,用数据结构来描述
- 操作:对对象行为的抽象,用操作名和实现该操作的方法来描述
一切皆对象
对象是数据和操作的封装
对象是独立的,但对象之间可以相互作用
目前OOP是最接近人类认知的编程范式
面向对象三要素
- 封装
- 组装:将数据和操作组装到一起
- 隐藏数据:对外只暴露一些接口,通过接口访问对象(不需要了解全部内部构造及原理)
- 继承
- 多复用,继承来的就不用自己写了
- 多继承少修改,OCP(Open-closed Principle),使用继承来改变,来体现个性
- 多态
- 面向对象编程最灵活的地方 – 动态绑定
封装
- 封装就是定义类,将属性和操作组织在类中
- 面向对象三要素之一,封装(Encapsulation)
- 将数据和操作组织到类中,即属性和方法
- 将数据隐藏起来,给使用者提供操作(方法),使用通过操作就可以获取或者修改数据(getter和setter)
- 通过访问控制,暴露适当的数据和操作给用户,该隐藏的隐藏,例如保护成员和私有成员
Python类定义
class ClassName:
语句块
- 必须使用class关键字
- 类名强烈建议使用大驼峰命名方式(每个单词首字母大写,如ClassName)其本质是一个标识符
- 类定义完成后就产生了一个类对象,绑定到了标识符ClassName上
class Person:
"""An Example Class"""
x = 'abc' #类属性
def showme(self): #方法,也是类属性
return self.__class__.__name__ #返回类的名称
print(Person)
print(Person.__name__) #类名字
print(Person.__doc__) #类文档
print(Person.showme) #类属性
------------------------------------------
<class '__main__.Person'>
Person
An Example Class
<function Person.showme at 0x000001C08791DA68>
类及类属性
- 类对象:类也是对象,类的定义执行后会生成一个类对象
- 类属性:类定义中的变量和类中定义的方法都是类的属性(类Person和x的showme)
- 类变量:属性也是标识符,也是变量(类Person的x和showme)
showme方法是类的属性,是方法method,其本质就是普通函数对象function,一般要求至少有一个参数
第一个形参可以是self,指代当前实例本身
实例化
a = Person() #实例化
tom = Person() #不同的实例
jerry = Person() #不同的实例
- 如上,在类对象名称后加一个括号,就调用类的实例化方法就是完成实例化
- 实例化就真正创建一个该类的对象(实例instance)
- 以上tom和jerry都是Person类的实例,通过实例化生成了两个不同的实例
- 通常每次实例化后获得的实例是不同的实例,即使是使用同样的参数实例化,也得到不一样的对象
- Python类实例化后,会自动调用__init__方法,其第一个形参必须留给self(其他形式参数随意)
构造的两个阶段
- 以上过程分为两个阶段:实例化和初始化
- __init__称为初始化方法
- __init__方法:也成为构造方法或构造器,作用是对实例进行初始化
- 调用__init__(self)方法进行初始化时可以不定义,如果没有定义会在实例化后隐式调用其父类的
class Person:
def __init__(self):
print('init~~~')
print(Person) #不会调用__init__
print(Person()) #调用__init__
tom = Person() #调用__init__
- 初始化函数可以多个参数,第一个位置必须是self(例如__init__(self,name,age)
class Person:
def __init__(self,name,age):
print('init~~~')
self.name = name
self.age = age
def showage(self):
print("{} is {}".format(self.name,self.age))
tom = Person('Tom',20) #实例化,会调用__init__并为实例进行属性的初始化
print(tom.name,tom.age)
tom.showage()
jerry = Person('Jerry',18)
print(jerry.name,jerry.age)
jerry.age += 1
print(jerry.name,jerry.age)
jerry.showage()
- init()方法不能有返回值,只能是return None
实例对象instance
- 类Person实例化后获得一个该类的实例,就是实例对象(如上例中tom、jerry就是Person类的实例)
- __init__方法的第一参数self就是指代某一个实例自身
- 保留在对象而不是类上的变量叫做实例变量(如上例中self.name就是tom对象的name,name保存在tom对象而不是类上,所以就是实例变量)
方法绑定
- 使用如tom.showme()方式调用,实例对象会绑定到方法上,这个self就是tom,指向当前调用该方法的实例本身
- 调用时,会把方法的调用者tom实例作为第一参数self的实参传入__init__()方法
class Person:
def __init__(self):
print(1,'self in init = {}'.format(id(self)))
def showme(self):
print(2,'self in showme = {}'.format(id(self)))
tom = Person()
print(3,'tom={}'.format(id(tom)))
print('-'*30)
tom.showme()
======================================
1 self in init = 2116983431944
3 tom=2116983431944
------------------------------
2 self in showme = 2116983431944
- 上例说明self是调用者,就是tom对应的实例对象
- self这个形参标识符的名字只是一个惯例,可以修改但最好不要,否则影响代码可读性
实例变量和类变量
class Person:
age = 3
def __init__(self,name):
self.name = name
#tom = Person('Tom',20) #错误,只能传一个实参
tom = Person('Tom')
jerry = Person('Jerry')
print(tom.name,tom.age) # - Tom 3
print(jerry.name,jerry.age) # - Jerry 3
#print(Person.name) #能访问吗?
print(Person.age) # - 3
Person.age = 30
print(Person.age,tom.age,jerry.age) #分别是多少? - 30 30 30
- 实例变量使每一个实例自己的变量,是自己独有的
- 类变量是类的变量,是类的所有实例共享的属性或方法
特殊属性
特殊属性 | 含义 |
---|---|
name | 对象名 |
class | 对象类型 |
dict | 对象的属性的字典 |
qualname | 类的限定名 |
- Python中每一种对象都拥有不同的属性,函数是对象,类是对象,类的实例也是对象
属性本质
class Person:
age=3
def __init__(self,name):
self.name = name
print('-----类-----')
print(Person.__class__,type(Person),Person.__class__ is type(Person)) #类型
print(sorted(Person.__dict__.items()),end='\n]n') #类字典
tom = Person('Tom')
print('-----通过实例访问类-----')
print(tom.__class__,type(tom),tom.__class__ is type(tom))
print(tom.__class__.__name__,type(tom).__name__)
print(sorted(tom.__class__.__dict__.items()))
print('-----实例自己的属性-----')
print(sorted(tom.__dict__.items())) #实例的字典
- 类属性保存在类的__dict__中
- 实例属性保存在实力的__dict__中
- 如果要从实例访问类的属性,也可以借助__class__找到所属的类,再通过类来访问类属性,如tom.class.age
class Person:
age = 3
height =170
def __init__(self,name,age=18):
self.name = name
self.age = age
tom = Person('Tom') #实例化、初始化
jerry = Person('Jerry',20)
Person.age = 30
print(1,Person.age,tom.age,jerry.age) #输出30 18 20
print(2,Person.height,tom.height,jerry.height) #输出170 170 170
jerry.height = 175
print(3,Person.height,tom.height,jerry.height) #输出170 170 175
tom.height += 10
print(4,Person.height,tom.height,jerry.height) #输出170 180 175
Person.height += 15
print(5,Person.height,tom.height,jerry.height) #输出185 180 175
Person.weight = 70
print(6,Person.weight,tom.weight,jerry.weight) #输出70 70 70
print(7,tom.__dict__['height']) #180
print(8,tom.__dict__['height']) #180
- 是类的,也是这个类苏有实力的,其实例都可以访问到;
- 是实例的,也就是这个实例自己的,通过类访问不到
- 类变量是属于类的变量,这个类的u送有实力可以共享这个变量
- 对象(实例或类)可以动态的给自己增加一个属性(赋值即定义一个新属性)。这也是动态语言的特性
- 实例.dict[变量名]和 实例.变量名 都可以访问到实例自己的属性(注意这两种访问是有本质区别的)
- 对实例访问来说,实例的同名变量会隐藏掉类变量,或者说是覆盖了这个类变量。【但其实类变量还在那里,并没有被真正覆盖】
实例属性的查找顺序: - 指的是实例使用【.点号】来访问属性,会先找自己的__dict__,如果没有,然后通过属性__class__找到自己的类,再去类的__dict__中找
- 如果实例使用__dict[变量名]访问变量,将不会按照上面的查找顺序找变量,这是指明使用字典的key查找,不是属性查找
- 一般来说,类变量可使用全大写命名
类方法和静态方法
前面的例子中定义的__init__等方法,这些方法本身都是类的属性,第一个参数必须是self,而self必须指向一个对象,也就是类实例化之后,由实例来调用这个方法
普通函数
#普通函数
class Person:
def normal_function():
print('普通函数')
def method(self):
print('方法')
#调用
Person.normal_function()
print(Person().normal_function)
#print(Person().normal_function()) #不可以
print(Person.__dict__)
- Person.normal_function():可以放在类中定义,因为这个方法只是被Person这个类管理的一个普通方法,normal_function是Person的一个属性而已
- 由于normal_function在定义的时候没有指定形参self,但不能用Person().normal_method()调用。因为Person()实施例,实例调用时由于做了实例绑定,需要normal_method的第一个形参来接受绑定的实例
- 语法对,但禁止这样写
类方法
#类方法
class Person:
@classmethod
def class_method(cls):
print('类方法')
print("{0}'s name = {0.__name__}".format(cls))
cls.HEIGHT = 170
#调用
Person.class_method()
Person().class_method()
print(Person.__dict__)
- 在类定义中,使用@classmethod装饰器修饰的方法
- 必须至少有一个参数,且第一个参数留给了cls,cls指代调用者即类对象自身
- cls这个标识符可以是任意合法名称,但是为了一睹,请不要修改
- 通过cls可以直接操作类的属性
- 通过类、实例都可以非常方便的调用类方法。classmethod装饰器内存将类或提取实例的类注入到类方法的第一个参数中。
- 无法通过cls操作类的实例
静态方法
#静态方法
class Person:
HEIGHT = 180
@staticmethod
def static_method():
print('静态方法')
print(Person.HEIGHT)
#调用
Person.static_method()
Person().static_method()
print(Person.__dict__)
- 在类定义中,使用@staticmethod装饰器修饰的方法
- 调用时,不会隐式的传入参数
通过类、实例都可以调用静态方法,不会像普通方法、类方法那样注入参数
静态方法,只是表明这个方法属于这个名词空间。函数归在一起,方便组织管理
方法的调用
- 类几乎可以调用所有内部定义的方法,但是调用普通方法时会报错,原因是第一参数必须是类的实例
- 实力也几乎可以调用所有的方法,普通的函数的调用一般不可能出现,因为原则上不允许这么定义
- 总结:
- 类除了普通方法都可以调用
- 普通方法需要对象的实例作为第一参数
- 实力可以调用所有类中定义的方法(包括类方法、静态方法),鸥亭方法传入实例自身,静态方法和类方法内部都要使用实例的类
访问控制
class Person:
def __init__(self,name,age=18):
self.name = name
self.age = age
def growup(self,age=1):
if age>0 and age<100:
self.age += age
tom = Person('Tom')
print(tom.age) #正常范围
#tom.growup(99)
#tom.growup(99)
#tom.growup(99)
tom.age += 3000 #超过正常范围
print(tom.age)
- 为了避免通过方法控制属性,却由于属性在外部可访问而直接修改属性,Python提供了私有属性来解决这个问题
私有属性(Private)
- 使用双下划线开头的属性名就是私有属性
class Person:
def __init__(self,name,age=18):
self.name = name
self.__age = age #属性使用了双下划线+属性名的方式
def growup(self,age=1):
if age>0 and age<100: #控制逻辑
self.__age += age
def getage(self):
return self.__age
tom = Person('Tom')
#print(tom.__age) #AttributeError: 'Person' object has no attribute 'age'
#print(tom.__age) #AttributeError: 'Person' object has no attribute '__age'
print(tom.getage())
私有成员
- 在Python中在类变量或实例变量前使用两个下划线的变量,称为私有成员,包括私有属性、私有方法。
- 前后各两个下划线是特殊属性,前面两个下划线是私有属性,后面加下划线没有用
class Person:
def __init__(self,name,age=18):
self.name = name
self.__age = age #属性使用了双下划线+属性名的方式(私有属性)
def __growup(self,age=1):
if age>0 and age<100:
self.__age += age
def getage(self): #public
self.__growup(20) #当前实例调这个方法,类的内部可以使用
return self.__age #私有成员 private
tom = Person('Tom')
print(tom.getage())
#tom.__growup() #AttributeError: 'Person' object has no attribute '__growup' #类的外部不能使用
私有变量的本质
- 类定义的时候,如果声明一个实例变量的时候,使用双下划线,Python解释取回将其改名,转换名称为_类名__变量名,所以原来的名字无法访问
- Python对象模型使用了字典
- 私有成员(__成员名),如果在类中定义,都会被解释器改名 --> _当前类 + 私有成员
- 使用上面的改名方案后,造成一种在类外无法使用 限定名.__成员名 访问到属性的现象,即隐藏(访问控制)
- 私有就是不公开的,离开了类定义环境就不能访问
- Python的类字典,实例字典是公开可访问的,所以能看到真实情况(改名规则),而其他高级语言私有的则无法访问
- 所以Python私有的可以绕过去(可以访问),除非万不得已请不要使用这种方式访问
- 这是Python隐藏数据的方式
#Python对象模型使用了字典
#私有成员(__成员名),如果在类中定义,都会被解释器改名 --> _当前类 + 私有成员
#使用上面的改名方案后,造成一种在类外无法使用 限定名.__成员名 访问到属性的现象,即隐藏(访问控制)
#私有就是不公开的,离开了类定义环境就不能访问
#Python的类字典,实例字典是公开可访问的,所以能看到真实情况(改名规则),而其他高级语言私有的则无法访问
#所以Python私有的可以绕过去(可以访问),除非万不得已请不要使用这种方式访问
#这是Python隐藏数据的方式
class Person:
def __init__(self,name,age=18):
self.name = name
self.__age = age #属性使用了双下划线+属性名的方式(私有属性)
def __growup(self,age=1):
if age>0 and age<100:
self.__age += age
def getage(self): #public
#self.__growup(20) #当前实例调这个方法,类的内部可以使用
return self.__age #私有成员 private 等价于self._Person__age
tom = Person('Tom')
tom.__age = 101 #赋值即定义,没有改名,因为在类定义外
print(tom.__age) #101
print(tom.getage()) #18
print(tom.__dict__)
print()
print(tom.__growup(50))
保护成员
- 在类变量或实例变量前使用一个下划线的变量,称为保护成员
class Person:
def __init__(self,name,age=18):
self.name = name
self.__age = age #属性使用了双下划线+属性名的方式(私有属性)
def __growup(self,age=1):
if age>0 and age<100:
self.__age += age
return self.__age
def getage(self): #public
#self.__growup(20) #当前实例调这个方法,类的内部可以使用
return self.__age #私有成员 private 等价于self._Person__age
tom = Person('Tom')
tom.__age = 101 #赋值即定义,没有改名,因为在类定义外
print(tom.__age) #101
print(tom.getage()) #18
print(tom.__dict__) #{'name': 'Tom', '_Person__age': 18, '__age': 101}
print(tom.__class__.__dict__)
print(tom._Person__growup(50)) #68 #除非万不得已不要使用
tom._Person__age = 3000
print(tom.getage()) #3000 #除非万不得已不要使用
总结:
- 在Python中使用单下划线或者双下划线来表示一个成员被保护或者被私有化隐藏起来
- 不管使用什么访问控制,都不能真正阻止用户修改类的成员
- Python中没有绝对安全的保护成员或私有成员
- 下划线只是一种警告或提醒,请遵守约定;除非真有必要,不要修改或使用保护成员或私有成员
- 在PyCharm中,在访问私有、保护成员的时候没有直接提示,就是一种提醒(尽量不要用啊!!)
补丁
- 可以通过修改或者替换类的成员。使用者调用的方式没有改变,但是类提供的功能可能已经改变了
- 猴子补丁(Monkey Patch):
- 在运行时,对属性、方法、函数等进行动态替换
- 其目的往往是为了通过替换、修改来增强、扩展原有代码的能力
- 黑魔法,慎用
属性装饰器
- 一般最好的设计:把实例的某些属性保护起来,不让外部直接访问,外部用getter读取属性和setter方式设置属性
- property装饰器:
- 后面跟的函数名就是以后的属性名,他就是getter(必须有,有了它至少是只读属性)
- setter装饰器:
- 与属性名同名,且接受两个参数,第一个是self,第二个是将要赋值的值,有了它属性可写
- deleter装饰器:
- 可以控制是否删除属性(很少用)
property装饰器必须在前,setter、deleter装饰器在后
property装饰器能通过简单的方法,把对方法的操作变成对属性的访问,并起到了一定的隐藏效果
class Person:
def __init__(self,name,age=18):
self._name = name #保护成员 protected
self.__age = age #属性使用了双下划线+属性名的方式(私有属性)
#def __growup(self,age=1): #那这个属性的方法称为getter
# return self.__age
@property #装饰器,内建函数,定义一个属性
def age(self): #age还是方法,但访问方式变了
print('get~~~~~~')
return self.__age
@age.setter
def age(self,value): #设置属性的方法称为setter
self.__age = value #self._Person__age = 30
@age.deleter #删除
def age(self):
print('del~~~~')
#del tom.__age
tom = Person('Tom')
#print(tome.age())
#tom.setage(30) #self tom;value
print(tom.age) #18
tom.age = 30
del tom.age
print(tom.age)
对象的销毁
- 类中可以定义__del__方法,称为析构函数(方法)
- 作用:销毁类的实例的时候调用,以释放占用的资源。其中就放些清理资源的代码,如释放连接
- 这个方法不能引起对象的真正销毁,只是对象销毁的时候会自动调用它
- 使用del语句删除实力,引用计数减一。当引用计数为0时,会自动调用__del__方法
- 由于Python实现了垃圾回收机制,不能确定对象和实质性垃圾回收。
- 由于
import time
#析构方法不建议手动调用(虽然手动调用也行)
#引用计数归0或垃圾回收或程序退出时,需要析构
class Person:
def __del__(self):#析构方法,__init__中开启资源,__del__释放资源
print('del~~~',self)
tom = Person()
print('='*30)
tom1 = tom
tom2 = tom1 #引用计数为3
del tom2
print('1111')
time.sleep(3)
del tom1
print('2222')
time.sleep(3)
del tom #引用计数为0
time.sleep(5)
print('-'*30)
#__del__ 解释器 自动调用,解释器发现无语句可以执行了
==============================
1111
2222
del~~~ <__main__.Person object at 0x000001C08792DE88>
------------------------------
方法重载(overload)
- 重载:同一个方法名,但形参个数类型不一样,就是同一个方法的重载
- Python不需要重载或说Python中没有重载
类的继承
定义
- 格式:
class 子类名(基类1[,基类2])
语句块
- 如果类定义是,没有积累列表,等同于继承自object。object类是所有对象的根基类
class A:
pass
#等价于
class A(object):
pass
- 在Python中支持多继承,继承也可以多级,查看继承的特殊属性和方法有:
特殊属性和方法 | 含义 |
---|---|
bases | 类的基类元组 |
base | 类的基类元组的第一项 |
mro | 显示方法查找顺序,基类的元组 |
mro()方法 | 同上,返回列表 |
subclasses() | 类的子类列表 |
class A:
pass
print(A.__base__)
print(A.__bases__)
print(A.__mro__)
print(int.__subclasses__())
print(bool.mro())
继承中的访问控制
class Animal:
__COUNT = 100
HIGHT = 0
def __init__(self,age,weight,height): # 3 5 15
self.__COUNT += 1 #101 赋值即定义,放在实例字典中
#self._Animal__COUNT + 1
self.age = age
self.__weight = weight
self.HEIGHT = height
def eat(self):
print('{} eat'.format(self.__class__.__name__))
def __getweight(self):
print(self.__weight)
@classmethod
def showcount1(cls):
print(cls)
print(cls.__dict__)
print(cls.__COUNT)
@classmethod
def __showcount2(cls):
print(cls.__COUNT)
def showcount3(cls):
print(cls.__COUNT) #是多少,为什么c._Animal__COUNT
class Cat(Animal):
NAME = 'CAT'
__COUNT = 200 #_Cat__ COUNT
c = Cat(3,5,15) #__new__创建实例 Cat -> Animal -> object
c.eat()
#c.eat 找c的 实例字典 中有没有eat属性,没有;找类(类字典),类属性,没有;继承mro,找父类Animal类属性(找的Animal类字典/父类字典)
print(c.HEIGHT) #15
#print(c.__COUNT) #可以访问吗? -- 私有属性,不可以 AttributeError: 'Cat' object has no attribute '__COUNT'
#print(c._Animal__COUNT) “黑科技”最好别用
c._Animal__getweight()
c.showcount1() #会输出什么结果?
#CAT 类字典 {'__module__': '__main__', 'NAME': 'CAT', '_Cat__COUNT': 200, '__doc__': None}
c._Animal__showcount2() #100
c.showcount3() #可以嘛?结果是什么? ## 可以,结果是101
print(c.__dict__) #{'_Animal__COUNT': 101, 'age': 3, '_Animal__weight': 5, 'HEIGHT': 15}
print(c._Cat__COUNT) #200
print(c._Animal__COUNT) #101
print(c.NAME) #CAT
-
从父类继承,自己没有的,就可以到父类中找
-
私有的都是不可以访问的,但本质上依然是改了名称(被隐藏)放在这个属性所在类或实例的__dict__中。知道这个新名称就可以找到这个隐藏的变量。【黑魔法,慎用!!】
-
**总结:**继承时,公有成员,子类和实例都可以随意访问;私有成员被隐藏,子类和实例不可以直接访问,但私有变量所在的类内的方法中可以访问这个私有变量。
-
实例属性查找顺序:
实例的 dict --> 类 dict --> 如果有继承 --> 父类 dict
如果搜索这些地方没有找到就会抛异常,先找到就立即返回
方法的重写、覆盖override
class Ainmal:
def shout(self):
print('Animal shouts')
class Cat(Animal): #子类中可以多数的从父类中继承,只有不满意的地方覆盖父类的
def shout(self): #继承并发扬
print(super())
print(super(__class__,self))
print(super(Cat,self))
#super().shout() #类型,实例 #安全实现,先调用父类的方法,执行自己的代码
#Cat.__class__ #错误
Cat.__base__.shout(self)
#super(Cat,self()).shout() #Cat,self
print('miao')
a = Animal
a.shout()
print('='*30)
c = Cat(3,15,5)
c.shout() #继承
print(Animal.__dict__)
print(Cat.__dict__)
- 静态方法和类方法,是特殊的方法,也是类属性,这些方法都可以覆盖,原理都一样,属性字典的搜索顺序
继承时使用初始化
class A:
def __init__(self,a):
self.a = a
class B(A):
def __init__(self,b,c): #override
super().__init__(b+c,b-c)
self.b = b
self.c = c
def printvalues(self):
print(self.b)
print(self.c)
print(self.a) #不行
print(self.d) #?
#a b c d都在哪里
b = B(200,300) #调用 [B,A,object] __new__(object有,实例化) __int__(self)
b.printvalues()
print(b.__dict__)
#print(A.__dict__)
#print(B.__dict__)
#A B 都没有init
- 如果类B定义时声明继承自类A,则在类B中__bases__中是可以看到类A
- 但是这和是否调用类A的构造方法是两码事
- 如果B中调用了父类A的构造方法,就可以拥有父类的属性了
- 一个原则,自己的私有属性,就该自己的方法读取和修改,不要借助其它类的方法,即使是父类或派生类的方法
单继承
- 类的继承列表中只有一个类,这种继承成为单一继承
- OCP原则:多用“继承”、少修改
- 继承的用途:在子类上实现对基类的增强,实现多态
多态
在面向对象中,父类、子类通过继承联系在一起,如果可以通过一套方法,就可以实现不同表现,就是多态。
多继承
一个类继承自多个类就是多继承,它将具有多个类的特征
多继承的弊端及缺点
- 多继承引入复杂性,带来冲突(继承谁多一些?)
- 多继承的实现会导致编译器设计的复杂度增加,所以有些高级编程语言舍弃了类的多继承(C++支持,Java舍弃)
- 多继承可能会带来二义性,要解决:深度优先或广度优先
- 当类很多且继承复杂的情况下,继承路径太多,很难说清什么样的继承路径
- Python语法允许多继承,但Python代码是解释执行,只有执行到的时候才能发现错误
- 团队协作开发,如果引入多继承,代码可能不可控
- 不管编程语言是否支持多继承,都应该避免多继承
- Python的面向对象太灵活开放,要团队守规矩
Python多继承实现
class ClassName(基类1,基类2[,....]):
类体
-
多继承带来路径选择问题(究竟继承哪个父类的特征)
-
Python使用MRO(method resolution order方法解析顺序)解决基类搜索顺序问题
- 历史原因,MRO有三个搜索算法:
- 经典算法:按照定义从左到右,深度优先策略
- 新式类算法:经典算法的升级,深度优先,重复的只保留最后一个
- C3算法:在类被创建出来的时候,就计算出一个MRO有序列表(Python3唯一支持的算法)C3算法解决多继承的二义性
- 历史原因,MRO有三个搜索算法:
-
经典算法的问题:如果C中有方法覆盖了A的,也不会访问到C的方法,因为先访问A的(深度优先)
-
新式类算法:依然采用了深度优先解决了重复问题,但同经典算法一样,没有解决继承的单调性
-
C3算法:解决了继承的单调性,他组织创建之前版本产生二义性的代码,球的MRO本质是为了线性化,且确定了顺序
-
单调性:假设有A、B、C三个类,C的MRO是[C,A,B],那么C的子类的MRO中,A、B的顺序一致就是单调的
Mixin
- 文档Document类是其他所有文档类的抽象基类,Word、PDF是Document的子类
例:为Document子类提供打印能力
- 在Document中提供print方法:
class Document:
def __init__(self,content):
self.content = content
def print(self): #抽象方法
raise NotImplementedError()
class Word(Document): pass #其他功能略去
class Pdf(Document): pass #其他功能略去
- 积累提供的方法可以不具体实现,因为他未必适合子类的打印,子类中需要覆盖重写
- 基类中只定义但不实现的方法叫“抽象方法”。Python中如果采用这种方式定义的抽象方法,子类可以不事先,直到子类使用该方法时才报错
- print时一种能力,但不是所有document的子类都需要
- 在需要打印的子类上增加
class Document:
def __init__(self,content):
self.content = content
class Word(Document): pass #第三方库不允许修改
class Pdf(Document): pass #第三方库不允许修改
#单继承
class PrintableWord(Word):
def print(self):
print(self.content)
print(PrintableWord.__dict__)
print(PrintableWord.mro())
pw = PrintableWord('test string')
pw.print()
- 比方法1好,但如果需要其他能力就要增加继承,类太多继承的方法就不好了
- 装饰器:
- 用装饰器增强一个类,把功能给类附加上去,哪个类需要装饰哪个
def printable(cls):
def _print(self):
print(self.content,'装饰器')
cls.print = _print
return cls
class Document: #第三方库不允许修改
def __init__(self,content):
self.content = content
class Word(Document): pass #第三方库不允许修改
class Pdf(Document): pass #第三方库不允许修改
@printable #先继承后装饰
class PrintableWord(Word): pass
print(PrintableWord.__dict__)
print(PrintableWord.mro())
pw = PrintableWord('test string')
pw.print()
@printable
class PrintablePdf(Pdf): pass
优点:
- 简单方便,在需要的地方动态增加,直接使用装饰器
- 可以为类灵活的增加功能
- Mixin
class Document:
def __init__(self,content):
self.content = content
class Word(Document): pass #第三方库不允许修改
class Pdf(Document): pass #第三方库不允许修改
class PrintableMixin:
def print(self):
print(self.content,'Mixin')
class PrintableWord(PrintableMixin,Word): pass
print(PrintableWord.__dict__)
print(PrintableWord.mro())
def printable(cls):
def _print(self):
print(self.content,'装饰器')
cls.print = _print
return cls
@printable
class PrintablePdf(Pdf): pass
print(PrintablePdf.__dict__)
print(PrintablePdf.mro())
- Mixin就是其他类混合进来,同时带来了类的属性和方法
- Mixin类和装饰器效果一样,但他是类,可以继承
class Document:
def __init__(self,content):
self.content = content
class Word(Document): pass #第三方库不允许修改
class Pdf(Document): pass #第三方库不允许修改
class PrintableMixin:
def print(self):
print(self.content,'Mixin')
class PrintableWord(PrintableMixin,Word): pass
print(PrintableWord.__dict__)
print(PrintableWord.mro())
pw = PrintableWord('test string')
pw.print()
class SuperPrintableMixin(PrintableMixin):
def print(self):
print('~' * 20) #打印增强
super().print()
print('~' * 20) #打印增强
#PrintableMixin类的继承
class SuperPrintablePdf(SuperPrintableMixin,Pdf): pass
print(SuperPrintablePdf.__dict__)
print(SuperPrintablePdf.mro())
spp = SuperPrintablePdf('super print pdf')
spp.print()
Mixin类
- Mixin本质上就是多继承实现的
- Mixin体现的是一种组合的设计模式
- 在很多面向对象的设计中,一个复杂的类,往往需要很多功能,而这些功能有来自不同的类提供,就需要很多类组合在一起
- 从设计模式的角度来说,多组合,少继承
Mixin类的使用原则:
- Miixin类中不应该显示出现__init__初始化方法
- Mixin类通常不能独立工作,因为他是准备混入别的类中的部份功能实现
- Mixin类的祖先也应该是Mixin类
使用时,Mixin类通常在继承列表的第一位置,例:
class PrintableWord(PrintableMixin,word): pass
Mixin类和装饰器:
- 两种都可以
- 如果需要继承还得使用Mixin类的方式