Python 面向对象三大特性
继承
一、继承初识
我们先来看下面几段代码:
1 |
|
我们定义了猫的类、狗的类还有鸟的类,他们都有类似的对象属性和方法,如果往后还要定义更多的动物类,且这些类拥有的属性和方法都相同,又需要重新复写这些代码。所以我们就可以使用继承的思想来实现类似的这种需求。
1 |
|
我们先定义一个 Animal 类,再将定义的 Cat 类和 Dog 类加一个括号,括号里传一个 Animal 类名,这就代表着我定义的 Cat 类和 Dog 类是继承于 Animal 类的,可以使用 Animal 类中的相关属性和方法。括号里面的称为父类(基类或者超类),括号外面的称为子类(或者派生类)。
使用继承思想有哪些好处:
- 优化代码,节省代码
- 提高代码的复用性
- 提高代码的维护性
- 让类与类之间发生关系(组合是让对象之间发生关系)
二、对父类的调用
子类以及子类实例化的对象,可以访问父类的任何方法或变量。
1 |
|
- 子类的类名可以访问父类的所有内容
1 |
|
- 子类实例化的对象也可以访问父类所有内容
1 |
|
实例化对象查找相关的属性时,会先在实例空间内进行查找,找不到就会去本类中进行查找,还是找不到就再去父类中查找……
类名查找对应属性时,先从自身的名称空间进行查找,查找不到再去父类中查找,永远不可能从实例化的对象中查找。
三、只调用子类的方法
在子类创建这个方法,如果方法名与父类相同,按照执行顺序,会优先调用子类的方法
1 |
|
四、只调用父类的方法
子类中不要定义与父类同名的方法
1 |
|
五、同时调用父类的方法和子类的方法
- 方式一,通过类名
1 |
|
- 方式二,super()
1 |
|
六、新式类和经典类
- 新式类:凡是继承object类都是新式类。python3 所有的类都是新式类,因为 python3 中的类都默认继承 object。
- 经典类:不继承object类都是经典类。python2 既有新式类,又有经典类。所有的类默认都不继承 object类,所有的类默认都是经典类;可以让其继承 object 转变为新式类
1 |
|
七、单继承和多继承的顺序
**单继承:**单继承的查询顺序在新式类和经典类中没有区别,都是现在本类中查找,找不到再去父类中查找
1 |
|
**多继承:**新式类遵循广度优先,经典类遵循深度优先
1 |
|
1 |
|
经典类:深度优先,即一条路走到黑。按照F的继承顺序从左往右查找,即如果F中没有,就去D中查找,D中没有就去B中查找,B中没有就去A中查找,找到就结束,再找不到就报错。
新式类:广度优先,一条路走到倒数第二级,判断,如果其他路能走到终点,则返回走另一条路。如果不能,则走到终点。按照F的继承顺序从左往右查找,即如果F中没有,就去D中查找,D中没有就去B中查找,此时在B处判断,如果没有其他途径能到达A,就去A中查找,找到就结束,再找不到就报错;显然上述列子中是有其他途径的(FECA),因此要返回从E开始查询,E中没有就去C中查询,C没有就去A中查找,找到就结束,再找不到就报错。因此广度优先的查询顺序为:FDBECA
八、多继承C3算法
1 |
|
使用 类名.mro() 方式可以得到广度优先的查询顺序
1 |
|
九、抽象类(接口类)
在我们开发项目时,必须要有归一化设计的思想。比如,要实现一个支付的功能,我们可以这么写
1 |
|
虽然在代码和逻辑上都没有什么问题,但是从使用方式上来说,这两者都是支付的功能,但是需要通过不同的方式取调度,这一点是很不合理的,我们可以进行改良
1 |
|
无论通过何种方式进行支付,都只需要调用同一个pay方法即可,这就是最典型的归一化设计的思想。
然而,当我们的代码交给其他人接着进行开发的时候,其他人由于不熟悉原来的代码架构,可能不按照原来的代码方式进行续写,此时想要归一化功能就会有问题。
1 |
|
新增代码部分并未参考上面两个类的写法,自己定义了一个方法,倒是想要实现归一化的时候报错。针对上述情况,我们就可以定义一个抽象类(又称接口类),让后续的类都继承该类,并让后续的类都按照该类的格式进行定义,制定了一个规范。
1 |
|
抽象类中不需要定义方法具体的实现,它的功能就是为子类制定一个必须强制执行的规则。对于抽象类中添加了装饰器的方法来说,子类在定义时,必须要定义该方法,否则报错。这样一来,当别人拿到你的代码后,也可以保证必须按照原来的规则统一编写接口。
多态
多态是指同一类事物可以有多种形态。
多态性是指在不考虑实例类型的情况下使用实例。
1 |
|
1 |
|
多态性的好处:
1.增加了程序的灵活性
以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如 func(animal)
2.增加了程序额可扩展性
通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用 func(animal) 去调用
在python中,其实并没有多态的概念,或者说在python中处处是多态。这是由于python是弱语言决定的。在强语言类型,如 java 中,在声明一个变量时,必须规定其数据类型,即使后面对变量值进行修改,也必须时该数据类型下的。然而在python中,可以随意改变变量的类型和值,且不管什么数据类型,传入函数或者封装到对象中都可以的。
一、鸭子模型
Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’。python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象,也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。
1 |
|
上述例子中,三种类的三种方法均用于操作索引,但是方法名不一样。其实他们的功能类似,我们完全可以把方法名都定义成 index
1 |
|
这样这些类就互称为鸭子,而我们在操作不同数据类型索引的时候,只需要通过调用 index 方法名就行了,函数会根据自身调用的类的不同去执行不同的方法,即虽然接受的函数名一样,但是执行的效果不同。开发人员就不需要考虑是何种数据类型去执行不同的方法来实现同一种功能,简化了调用方式。(原来要分别执行 Str.index,List.abc,Tuple.rrr,现在统一都是 类名.index)
封装
广义的封装:实例化一个对象,给对象空间封装一些属性。
狭义的封装:私有制。
一、私有静态字段
- 对于私有静态字段,类的外部不能访问
1 |
|
- 对于私有静态字段,类的内部可以访问
1 |
|
- 对于私有静态字段来说,只能在本类中内部访问,类的外部、派生类均不可访问
1 |
|
其实私有静态字段在类的外部是可以访问的,这也是python的一个小bug,但是不建议这么去访问。
1 |
|
我们通过打印A类的名称空间的内容可以发现,其实当python解释器在读取类的定义代码时,读取到__age,就知道这是要定义一个私有静态字段。为了不让类的外部能够访问到,就会把原来存放到类的名称空间的__age前面多添加一个_类名,即_A__age,所以我们从外部想要调用__age时,就会找不到对应静态字段。这也是为什么从类的内部可以访问私有静态变量的原因了,因为在类的内部,当执行到 print(A.__age) 时,解释器会自动把__age变为_A__age,这样就能匹配上了。
所以当我们从外部想要调用私有静态变量时,只需要这样调用
1 |
|
但是记住千万不要这么干,千万不要,不要!
二、私有方法
- 类外部不能访问
1 |
|
- 类内部可以访问
1 |
|
- 派生类不能访问
1 |
|
三、私有对象属性
- 类外部不能访问
1 |
|
- 类内部可以访问
1 |
|
- 派生类不能访问
1 |
|