组合
组合:一个对象拥有一个属性, 属性的值必须是另外一个对象
继承满足的是:什么是什么的关系 # is-a
组合满足的是:什么有什么的关系 # has-a
- class Foo:
- def __init__(self, m):
- self.m = m
-
-
- class Bar():
- def __init__(self, n):
- self.n = n
-
-
- obj = Foo(10)
- print(obj.m)
-
-
- obj1 = Bar(20)
- # print(obj1.n)
-
- obj.x = obj1
- print(obj.x.n)
-
mixins机制
- 1. 分主类和辅类
- 2. 命名方式一般以 Mixin, able, ible 为后缀
- 3. 辅类位置一般在主类的左边
一个子类可以同时继承多个父类,这样的设计常被人诟病,一来它有可能导致可恶的菱形问题,二来在人的世界观里继承应该是个”is-a”关系。 比如轿车类之所以可以继承交通工具类,是因为基于人的世界观,我们可以说:轿车是一个(“is-a”)交通工具,而在人的世界观里,一个物品不可能是多种不同的东西,因此多重继承在人的世界观里是说不通的,它仅仅只是代码层面的逻辑。不过有没有这种情况,一个类的确是需要继承多个类呢?
答案是有,我们还是拿交通工具来举例子:
民航飞机、直升飞机、轿车都是一个(is-a)交通工具,前两者都有一个功能是飞行fly,但是轿车没有,所以如下所示我们把飞行功能放到交通工具这个父类中是不合理的
- class Vehicle: # 交通工具
- def fly(self):
- '''
- 飞行功能相应的代码
- '''
- print("I am flying")
-
-
- class CivilAircraft(Vehicle): # 民航飞机
- pass
-
-
- class Helicopter(Vehicle): # 直升飞机
- pass
-
-
- class Car(Vehicle): # 汽车并不会飞,但按照上述继承关系,汽车也能飞了
- pass
但是如果民航飞机和直升机都各自写自己的飞行fly方法,又违背了代码尽可能重用的原则(如果以后飞行工具越来越多,那会重复代码将会越来越多)。
怎么办???为了尽可能地重用代码,那就只好在定义出一个飞行器的类,然后让民航飞机和直升飞机同时继承交通工具以及飞行器两个父类,这样就出现了多重继承。这时又违背了继承必须是”is-a”关系。这个难题该怎么解决?
不同的语言给出了不同的方法,让我们先来了解Java的处理方法。Java提供了接口interface功能,来实现多重继承:
- // 抽象基类:交通工具类
- public abstract class Vehicle {
- }
-
- // 接口:飞行器
- public interface Flyable {
- public void fly();
- }
-
- // 类:实现了飞行器接口的类,在该类中实现具体的fly方法,这样下面民航飞机与直升飞机在实现fly时直接重用即可
- public class FlyableImpl implements Flyable {
- public void fly() {
- System.out.println("I am flying");
- }
- }
-
-
-
- // 民航飞机,继承自交通工具类,并实现了飞行器接口
- public class CivilAircraft extends Vehicle implements Flyable {
- private Flyable flyable;
-
- public CivilAircraft() {
- flyable = new FlyableImpl();
- }
-
- public void fly() {
- flyable.fly();
- }
- }
-
- // 直升飞机,继承自交通工具类,并实现了飞行器接口
- public class Helicopter extends Vehicle implements Flyable {
- private Flyable flyable;
-
- public Helicopter() {
- flyable = new FlyableImpl();
- }
-
- public void fly() {
- flyable.fly();
- }
- }
-
- // 汽车,继承自交通工具类,
- public class Car extends Vehicle {
- }
现在我们的飞机同时具有了交通工具及飞行器两种属性,而且我们不需要重写飞行器中的飞行方法,同时我们没有破坏单一继承的原则。飞机就是一种交通工具,可飞行的能力是飞机的属性,通过继承接口来获取。
回到主题,Python语言可没有接口功能,但Python提供了Mixins机制,简单来说Mixins机制指的是子类混合(mixin)不同类的功能,而这些类采用统一的命名规范(例如Mixin后缀),以此标识这些类只是用来混合功能的,并不是用来标识子类的从属"is-a"关系的,所以Mixins机制本质仍是多继承,但同样遵守”is-a”关系,如下
- class Vehicle: # 交通工具
- pass
-
-
- class FlyableMixin:
- def fly(self):
- '''
- 飞行功能相应的代码
- '''
- print("I am flying")
-
-
- class CivilAircraft(FlyableMixin, Vehicle): # 民航飞机
- pass
-
-
- class Helicopter(FlyableMixin, Vehicle): # 直升飞机
- pass
-
-
- class Car(Vehicle): # 汽车
- pass
-
- # ps: 采用某种规范(如命名规范)来解决具体的问题是python惯用的套路
可以看到,上面的CivilAircraft、Helicopter类实现了多继承,不过它继承的第一个类我们起名为FlyableMixin,而不是Flyable,这个并不影响功能,但是会告诉后来读代码的人,这个类是一个Mixin类,表示混入(mix-in),这种命名方式就是用来明确地告诉别人(python语言惯用的手法),这个类是作为功能添加到子类中,而不是作为父类,它的作用同Java中的接口。所以从含义上理解,CivilAircraft、Helicopter类都只是一个Vehicle,而不是一个飞行器。
使用Mixin类实现多重继承要非常小心
- 首先它必须表示某一种功能,而不是某个物品,python 对于mixin类的命名方式一般以 Mixin, able, ible 为后缀
- 其次它必须责任单一,如果有多个功能,那就写多个Mixin类,一个类可以继承多个Mixin,为了保证遵循继承的“is-a”原则,只能继承一个标识其归属含义的父类
- 然后,它不依赖于子类的实现
- 最后,子类即便没有继承这个Mixin类,也照样可以工作,就是缺少了某个功能。(比如飞机照样可以载客,就是不能飞了)
Mixins是从多个类中重用代码的好方法,但是需要付出相应的代价,我们定义的Minx类越多,子类的代码可读性就会越差,并且更恶心的是,在继承的层级变多时,代码阅读者在定位某一个方法到底在何处调用时会晕头转向,如下
- class Displayer:
- def display(self, message):
- print(message)
-
-
- class LoggerMixin:
- def log(self, message, filename='logfile.txt'):
- with open(filename, 'a') as fh:
- fh.write(message)
-
- def display(self, message):
- super().display(message) # super的用法请参考下一小节
- self.log(message)
-
-
- class MySubClass(LoggerMixin, Displayer):
- def log(self, message):
- super().log(message, filename='subclasslog.txt')
-
-
- obj = MySubClass()
- obj.display("This string will be shown and logged in subclasslog.txt")
-
-
- # 属性查找的发起者是obj,所以会参照类MySubClass的MRO来检索属性
- #[<class '__main__.MySubClass'>, <class '__main__.LoggerMixin'>, <class '__main__.Displayer'>, <class 'object'>]
-
- # 1、首先会去对象obj的类MySubClass找方法display,没有则去类LoggerMixin中找,找到开始执行代码
- # 2、执行LoggerMixin的第一行代码:执行super().display(message),参照MySubClass.mro(),super会去下一个类即类Displayer中找,找到display,开始执行代码,打印消息"This string will be shown and logged in subclasslog.txt"
- # 3、执行LoggerMixin的第二行代码:self.log(message),self是对象obj,即obj.log(message),属性查找的发起者为obj,所以会按照其类MySubClass.mro(),即MySubClass->LoggerMixin->Displayer->object的顺序查找,在MySubClass中找到方法log,开始执行super().log(message, filename='subclasslog.txt'),super会按照MySubClass.mro()查找下一个类,在类LoggerMixin中找到log方法开始执行,最终将日志写入文件subclasslog.txt
面向对象中内置方法
- __str__方法会在对象被打印时自动触发,print功能打印的就是它的返回值,我们通常基于方法来定制对象的打印信息,该方法必须返回字符串类型
-
- __del__ 删除对象属性的时候,自动触发 ,当所有代码执行完成之后,还会自动触发
-
- __call__ 当给对象加括号时候,自定触发的函数
-
-
反射
python是动态语言,而反射(reflection)机制被视为动态语言的关键。
反射机制指的是在程序的运行状态中
对于任意一个类,都可以知道这个类的所有属性和方法;
对于任意一个对象,都能够调用他的任意方法和属性。
这种动态获取程序信息以及动态调用对象的功能称为反射机制。
在python中实现反射非常简单,在程序运行过程中,如果我们获取一个不知道存有何种属性的对象,若想操作其内部属性,可以先通过内置函数dir来获取任意一个类或者对象的属性列表,列表中全为字符串格式
- >>> class People:
- ... def __init__(self,name,age,gender):
- ... self.name=name
- ... self.age=age
- ... self.gender=gender
- ...
- >>> obj=People('egon',18,'male')
- >>> dir(obj) # 列表中查看到的属性全为字符串
- [......,'age', 'gender', 'name']
接下来就是想办法通过字符串来操作对象的属性了,这就涉及到内置函数hasattr、getattr、setattr、delattr的使用了(Python中一切皆对象,类和对象都可以被这四个函数操作,用法一样)
- class Teacher:
- def __init__(self,full_name):
- self.full_name =full_name
-
- t=Teacher('Egon Lin')
-
- # hasattr(object,'name')
- hasattr(t,'full_name') # 按字符串'full_name'判断有无属性t.full_name
-
- # getattr(object, 'name', default=None)
- getattr(t,'full_name',None) # 等同于t.full_name,不存在该属性则返回默认值None
-
- # setattr(x, 'y', v)
- setattr(t,'age',18) # 等同于t.age=18
-
- # delattr(x, 'y')
- delattr(t,'age') # 等同于del t.age
异常
- # 什么是异常?
- 异常就是错误发生额的信号, 如果不对该信息进行处理, 那么, 之后的代码就不会运行
-
- 具体来说:
- 1. 语法错误
- # SyntaxError:
- print(123
- 2. 逻辑错误
- # 有些逻辑错误可以尽量写到完美
- NameError: name 'x' is not defined
- print(x)
-
- # 为什么用异常?
- 为了增强代码的健壮性
-
- # 怎么用异常?
- try:
- 被监测的代码1
- 被监测的代码2
- 被监测的代码3
- 被监测的代码4
- 被监测的代码5
- except 异常错误1 as e:
- pass
- except 异常错误2 as e:
- pass
- except 异常错误3 as e:
- pass
- else:
- print("当被检测的代码没有异常的时候触发")
- finally:
- print("不管有咩有异常,都会走")
如果我们想捕获所有异常并用一种逻辑处理,Python提供了一个万能异常类型Exception
- try:
- 被检测的代码块
- except NameError:
- 触发NameError时对应的处理逻辑
- except IndexError:
- 触发IndexError时对应的处理逻辑
- except Exception:
- 其他类型的异常统一用此处的逻辑处理
raise 主动抛出异常
在不符合Python解释器的语法或逻辑规则时,是由Python解释器主动触发的各种类型的异常,而对于违反程序员自定制的各类规则,则需要由程序员自己来明确地触发异常,这就用到了raise语句,raise后必须是一个异常的类或者是异常的实例
- class Student:
- def __init__(self,name,age):
- if not isinstance(name,str):
- raise TypeError('name must be str')
- if not isinstance(age,int):
- raise TypeError('age must be int')
-
- self.name=name
- self.age=age
-
- stu1=Student(4573,18) # TypeError: name must be str
- stu2=Student('egon','18') # TypeError: age must be int
断言 assert
Python assert(断言)用于判断一个表达式,在表达式条件为 false 的时候触发异常。
断言可以在条件不满足程序运行的情况下直接返回错误,而不必等python教程待程序运行后出现崩溃的情况
- >>> assert True # 条件为 true 正常执行
- >>> assert False # 条件为 false 触发异常
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- AssertionError
- >>> assert 1==1 # 条件为 true 正常执行
- >>> assert 1==2 # 条件为 false 触发异常
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- AssertionError
-
- >>> assert 1==2, '1 不等于 2'
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- AssertionError: 1 不等于 2
- >>>