封装与继承
# 封装
# 介绍
- 封装是面向对象三大特性最核心的一个特性
- 封装就是整合的意思,将一类数据与功能整合
- 数据属性和函数属性
- 对象.属性
- 同时也可以存储对象
# property装饰器
# 介绍
property
是一个装饰器,但本质是一个类- 它能将一个函数属性,伪装成一个数据属性
# 作用
- 站在使用者的角度考虑,对于需要动态计算的变化数据,需要用函数来计算的数据值,虽然可以用函数调用来计算得出,但是该值听上去更像是一个数据属性,而非功能,这个时候,就可以使用`property`来将函数属性伪装成数据属性,也就是可以省略括号进行调用
# property
- 查询时会调用查询函数并返回返回值
- 修改时会将=右边的值作为参数传入修改函数
- 删除时会调用删除函数
- 作用
- 让使用者可以更方便的去使用属性
- 对于要控制隐藏数据属性的查改删,可以调用property并将查改删对应的控制函数传入
- 就可以用名称来控制隐藏数据属性,而不用每次查改删隐藏数据属性都要调用控制函数
# 基本查询
class cl:
@property
def bmi(self):
return 1
c = cl()
c.bmi
1
2
3
4
5
6
2
3
4
5
6
- 它会将被装饰函数的名称作为数据属性的名称、返回值作为数据属性的值
# 查改删控制
- 语法糖方式(推荐)
class cl:
def __init__(self,name):
self.__name = name
@property # 查询, 同等于name = property(name)
def name(self):
return self.__name
@name.setter # 修改
def name(self,name):
self.__name = name
@name.deleter # 删除
def name(self):
print('不给删')
c = cl('test')
# 查
print(c.name)
# 改
c.name = 123
# 删
del c.name
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- 非语法糖方式
class cl:
def __init__(self,name):
self.__name = name
def get_name(self):
return self.__name
def set_name(self,name):
self.__name = name
def del_name(self):
print('不给删')
# property(查询函数,修改函数,删除函数)
name = property(get_name,set_name,del_name)
c = cl('test')
print(c.name)
c.name= 123
del c.name
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 隐藏属性
# 介绍
- 将封装的属性进行隐藏操作
- 使其对外不能直接访问,但对内可以直接访问
- 对外会在将其属性名进行变形,变成形如
"_类名__属性名"
的的名字 - 也就是说如果知道类名和属性名,则就可以从外部进行访问,但一般不会去访问隐藏属性
- 对类体内则可以直接
self.__x
访问
- 对外会在将其属性名进行变形,变成形如
# 隐藏属性的作用
- 使外部无法直接访问隐藏属性
- 以严格控制类与对象的使用逻辑
- 如:必须通过接口函数去修改某个值等
- 隔离复杂度,降低使用难度
- 隐藏一些没必要暴露给使用者的数据或函数等
- 如:暴露大功能去调用隐藏的小功能等
# 隐藏语法
- 直接在属性名前加
__
前缀即可- 该变形只会在检查类体语法的时候发生一次
- 也就是只能在class类定义阶段,去定义隐藏属性
- 例如
class Foo:
__x = 1
1
2
2
# 约定俗成的写法
- 我们一般不会使用
__
去定义隐藏属性,而是约定俗成的使用_
来表示这是一个隐藏属性,不应该直接使用 - 但直接用
__
也无妨,没什么区别
# 继承
# 继承介绍
- 继承是一种创建新类的方式,新建的类可称为子类或派生类,父类又可称为基类或者超类
- Python支持多继承,新建的类可以继承一个或多个父类
# 继承作用
- 子类会遗传父类的所有属性
# 继承的查找顺序
- 先查找对象,再查找子类,再查找父类
- 先找对象,没有则去子类找,如果子类也没有,则会去父类找,父类找到了,但父类的函数又调用了其他函数属性,则也是从对象开始找,因为是对象调用函数属性时会自动将self传入
- 例如
class A:
one = 1
def p(self):
print(self.one)
class B(A):
one = 2
c = B()
c.p()
>> 输出: 2
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- 除非父类指名道姓的去调用,而非使用self
- 例如
class A:
__one = 1
def p(self):
print(A.__one)
class B(A):
__one = 2
c = B()
c.p()
>> 输出: 1
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- 或者父类是将函数隐藏变形,由于定义阶段调用的名称也会进行变形,所以子类调用会找不到变形后的名称,除非子类也使用父类变形后的名称去进行调用
- 例如
class A:
__one = 1
def p(self):
print(self.__one)
class B(A):
__one = 2
c = B()
c.p()
>> 输出: 1
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 多继承优缺点
- 优点
- 子类可以同时遗传多个父类的属性,可以将多个用的上的类,放到一个子类中
- 最大限度的重用代码,减少类之间的代码冗余
- 缺点
- 违背了人的思维习惯:一个类对应多个类
- 如:让人类对应动物类又继承爬虫类
- 不建议使用多继承,代码可读性会变差,扩展性会变差,且有可能会引发菱形问题
- 如果不可避免的要使用多继承,应该使用Mixins
- 违背了人的思维习惯:一个类对应多个类
# 继承语法
- 单继承
class Sub1(parent1): ...
- 多继承
class Sub2(parent1, parent2...): ...
# 在父类基础上扩展
# 介绍
- 调用父类的__init__,并且想在该__init__基础上传其他的参进子类,则可以使用该方法
- 类中的其他函数属性也是可以这使用,以达到在父类的基础上进行扩展,复用代码,减少冗余
# 实现方法
class Fat:
def __init__(self, fanum):
self.fanum = fanum
class Son(Fat):
def __init__(self, fanum, sonnum):
# 指名道姓去调用父类__init__,让其赋值给self也就是当前实例化的对象
Fat.__init__(self, fanum)
self.sonnum = sonnum
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 继承与抽象
# 介绍
- 要找出类与类之间的继承关系,需要先抽象再继承
- 抽象即是总结对象之间的相似之处,以得到类
- 如: 花猪和黑猪,抽象出猪类
- 再总结类与类之间的相似之处就可以得到父类
- 如: 猪类和猴类,抽象出动物类
# 经典类与新式类
# 介绍
- python2中才有经典类与新式类之分
- python3中如果没有继承任何类,那么会默认继承object类,所以python3中只有新式类
- object类封装了一些常用的基本功能
# 经典类
- 没有继承object类的子类,以及该子类、子子类...
- 例如
class Foo:
pass
class Son(Foo):
pass
1
2
3
4
2
3
4
# 新式类
- 继承了object类的子类,以及该子类、子子类...
- 例如
class Foo(object):
pass
class Son(Foo):
pass
1
2
3
4
2
3
4
# 菱形(钻石)问题
# 介绍
- A是一个object类,B和C继承了A,而D又继承了(B, C),这种结构就造成了菱形问题
- 如果A中有一个方法,B和/或C都重写了该方法,而D没有重写它,那么D继承的是哪个版本的方法:B的还是C的?
- 如下,这种继承结构下导致的问题称之为菱形问题
class A(object):
def test(self):
print('From G')
class B(G):
def test(self):
print('From G')
class C(G):
def test(self):
print('From G')
class D(A, B):
pass
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 继承查找原理(MRO)
# 介绍
- 对于我们定义的每一个类,Python都会计算出一个方法解析顺序(MRO)列表
- 该MRO列表就是一个简单的所有基类的线性顺序列表
- python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止
- 它实际上就是合并所有父类的MRO列表并遵循如下准则:
- 1.子类会先于父类被检查
- 2.多个父类会根据它们在列表中的顺序被检查
- 3.如果对下一个类存在两个合法的选择,选择第一个父类
- 4.如果有object类则永远是最后查找
# 查看类的MRO列表
D.mro()
- 新式类内置了
mro
方法可以查看线性列表的内容,经典类没有该内置该方法
# 查找顺序
- 1.由对象发起的属性查找,会从对象自身的属性里检索,没有则会按照
对象的类.mro()
规定的顺序依次找下去 - 2.由类发起的属性查找,会按照
当前类.mro()
规定的顺序依次找下去
# 深度优先和广度优先
# 经典类
- 深度优先,会在检索第一条分支的时候就直接一路走到黑,也就是最先检索共同的父类
# 新式类
- 广度优先,会在检索最后一条分支的时候再一路走到黑,也就是最后检索共同的父类
# 多继承的使用
# 多继承要不要用
- 要用,但要规避几点问题
- 1.继承结构尽量不要过于复杂
- 2.使用mixins机制:在多继承的背景下满足继承的什么"是"什么的关系
# 多继承之mixins规范
# 介绍
- 利用命名规范,让多继承更加符合人的思维习惯
- mixins规范的核心就是在多继承的背景下,尽可能地提升多继承的可读写
# 方法
- 在类名统一的命名规范即是用
Mixin
后缀,表示只是用来混入一些特定功能的类,而非作为真正的父类 - 此Mixin标识这些类只是用来混合功能,并不是用来标识子类的从属"is-a"关系的
- 这个并不影响功能,但是会告诉后来读代码的人,这个类是一个Mixin类
- 表示混入(mix-in),这种命名方式就是用来告诉别人,这个类是作为功能添加到子类中,而不是作为父类
- 例如
# 交通工具类
class Vehicle:
pass
class FlyableMixin:
pass
class Aircraft(FlyableMixin, Vehicle):
pass
class Car(vehicle):
pass
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# Mixin类实现多继承的注意事项
- Mixin类必须表示某一种功能,而不是某个物品
- 对于mixin类的命名方式一般以
Mixin, able, ible
为后缀
- 对于mixin类的命名方式一般以
- Mixin类必须只负责一类功能,如果有多类功能,则写多个Mixin类
- 一个类可以继承多个Mixin,为了保证遵循继承的"is-a"原则,只能继承一个标识其归属含义的父类
- Mixin类需要是不依赖于子类的实现
- 子类即便没有继承这个Mixin类,也应该照样可以工作,只是缺少了某个功能
# 子类重用并扩展父类的功能
# 方式一
- 指名道姓调用某一类下的函数,需要传入
self
,该方式不依赖于继承关系
class Fat:
def __init__(self, fanum, tnum):
self.fanum = fanum
self.num = tnum
class Son(Fat):
def __init__(self, fanum, sonnum):
# 指名道姓去调用父类__init__,让其赋值给self也就是对象
Fat.__init__(self, fanum, tnum)
self.sonnum = sonnum
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 方式二(建议使用)
super()
调用父类提供给自己的方法,不需要传入self,该方式严格依赖继承关系- 调用
super()
会得到一个特殊的对象,该对象会参照当前类的mro列表,去当前类的父类中找属性,也就是属性查找发起者的mro列表中,调用super()
位置的类的右边一个类作为父类 - 例如
class A:
def test(self):
super().test()
class B:
def test(self):
print('from B')
class C(A, B):
pass
obj = C()
obj.test()
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
- 由于对象没有,所以会找C的mro列表,然后按顺序在A找到了test(),所以执行A类的test(),但A的test又调用了super().test(),所以就会去A的父类找
- 参照的是本次查找发起者C的mro列表去找A的父类,最后找到B的test()并执行
- 例如
- class Fat:
def __init__(self, fanum, tnum):
self.fanum = fanum
self.num = tnum
class Son(Fat):
def __init__(self, fanum, sonnum):
# 调用super()获得当前类的父类的属性
super().__init__(fanum, tnum)
self.sonnum = sonnum
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 组合
# 介绍
- 是整合思想的一种
- 让一个对象有一个属性,该属性的值是另外一个对象,也就是类中定义需要传入对象
- 除了能用到对象自己的属性,也能调用传入到对象中的对象的属性,也就是进一步的整合
# 实例
- 比如将学生对象传入到班级对象中
- 又将班级对象传入学校对象中
# 多态与鸭子类型
# 介绍
- 同一类事物的多种形态,如水的多态(液体水、气体水、矿泉水等)、动物的多态(人、猪、狗等)
# 多态的特性
- 将多态的子类所共有的东西,放到基本的父类中,只要父类有了,子类一定也会有
- 多态性指的是,可以在不考虑对象具体类型的情况下,直接的去使用对象
- 就是只要父类有的,就可以大胆的去调用,使用者就可以不加思索的去调用父类有的功能
- 父类也可以用来制定子类的规范,所以定义好后甚至可以直接pass
# 继承的方式实现多态
- 多态例子
class Animal:
def say(self):
print('ennn')
class People(Animal):
def say(self):
print('yes~yes~')
class Dog(Animal):
def say(self):
print('wang~')
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
- 实现用接口函数去调用类的方法
- 比如
len()
,就是调用内置的[类型].__len__()
方法 - 例如
def animal_len(animal):
animal.say()
1
2
2
# 鸭子类型实现多态 (推荐使用)
- 介绍
- 不使用继承,而是用规范的方式去实现多态
- 比如:
- 不去定义父类,而是用统一的规范,去定义多态类的属性,也就是使其属性结构相似
- 让狗、猫,去做鸭子才会做的事,这样虽然没有直接说明他们是鸭子,但由于把他们做的像是鸭子,这样就能抽象的说他们是鸭子
- 又比如linux中的一切皆文件的概念,把CPU、内存做成文件一样,就能说他们是文件
- 例子
# 以下都是动物类
class People:
def say(self):
print('yes~yes~')
class Dog:
def say(self):
print('wang~')
1
2
3
4
5
6
7
2
3
4
5
6
7
# abcMeta抽象基类
# 介绍
- 如果父类不是用来实现功能,而是用来制定子类的规范,为了防止子类不去定义父类的规范
- 可以通过在父类引入抽象类的概念来硬性限制子类必须有某些方法名
# 例子
import abc
class Animal(metaclass=abc.ABCMeta):
# 该装饰器限制子类必须定义有一个名为talk的方法
@abc.abstractmethod
# 抽象方法中无需实现具体的功能,直接pass
def talk(self):
pass
class Cat(Animal):
def talk(self):
pass
# 若子类中没有一个名为talk的方法则会抛出异常TypeError,无法实例化
cat=Cat()
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 绑定给类的方法
# 介绍
- 调用者是类,自动传入的是类
- 利用classmethod装饰器提供一种新的造对象的方式
- 约定俗成类型参名为cls
# 例子
class Mysql:
def __init__(self, ip, port):
self.ip = ip
self.port = port
# 会将下面的函数装饰成绑定给类的方法
@classmethod
def get_conf(cls):
# 返回class的实例化对象
return cls(setting.IP, setting.PORT)
obj = Mysql.from_conf()
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 非绑定方法
# 介绍
- 特殊在于既不需要传入对象、又不需要传入类
# 实现方法
- 利用
staticmethod
装饰器将函数装饰成一个静态方法- 然后就可以不传参了,且也不会自动传参
- 且无论是用对象还是是类访问该方法,该方法都只是作为一个普通函数,而非作为方法
# 例子
class Mysql:
# 会将下面的函数装饰成一个静态方法
@staticmethod
def get_conf():
print('test')
obj = Mysql()
obj.get_conf()
1
2
3
4
5
6
7
2
3
4
5
6
7