Python第七章-面向对象高级
平仄平仄平平仄 人气:0
# 面向对象高级
## 一、 特性
特性是指的`property`.
`property`这个词的翻译一直都有问题, 很多人把它翻译为属性, 其实是不恰当和不准确的. 在这里翻译成特性是为了和属性区别开来.
属性是指的`attribute`, 我们以前学习的实例变量和类变量是`attribute`, 所以也可以叫做实例属性和类属性.
---
**`property`(特性)到底是个什么东西?**
我们前面学习类属性和实例属性的时候知道, 访问他们的时候就可以直接获取到这些属性的值.
而特性可以看成一种特殊的属性, 为什么呢?
但从访问方式来看, 特性和属性看不出来差别, 但是特性实际上会经过计算之后再返回值. 所以每一个特性都始终与一个方法相关联.
---
### 1.1 定义特性
**定义特性和定义实例方法类似**, 只需要另外在方法上面添加一个内置装饰器:`@property`
**访问特性和访问实例变量完全一样, 不需要使用添加括号去调用.**
---
```python
import math
class Circle:
def __init__(self, r):
self.r = r
@property
def area(self):
"""
定义特性
这个特性是计算出来圆的面积
:return:
"""
return math.pi * (self.r ** 2)
c = Circle(10)
print(c.area)
```
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403124801245-683465522.png)
---
很明显, 特性背后的本质是一个方法的存在, 所以你不可能在外面去修改这个特性的值!
试图修改特性的值只会抛出一个异常.
```python
c.area = 100
```
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403124813931-630469034.png)
---
### 1.2 使用特性的设计哲学
这种特性使用方式遵循所谓的 _**统一访问原则**_.
实际上, 定义一个类总是保持接口的统一总是好的.
有了特性, 把访问属性和访问方法统一了, 都像在访问属性一样, 省得去考虑到底什么时候需要添加括号,什么时候不用添加括号.
---
### 1.3 特性的拦截操作
python 还提供了设置和删除属性.
通过给方法添加其他内置装饰器来实现
设置:`@特性名.setter`
删除:`@特性名.deleter`
```python
class Student:
def __init__(self, name):
self._name = name # name 是特性了, 所以用实例变量存储特性的值的是换个变量名!!!
@property
def name(self):
return self._name
@name.setter
def name(self, name):
if type(name) is str and len(name) > 2:
self._name = name
else:
print("你提供的值" + str(name) + "不合法!")
@name.deleter
def name(self):
print("对不起, name 不允许删除")
s = Student("李四")
print(s.name)
s.name = "彩霞"
print(s.name)
s.name = "张三"
print(s.name)
del s.name
```
---
## 二、三大特性之一-封装性
面向对象的三大特征:封装, 继承, 多态
###2.1什么是封装性
1.封装是面向对象编程的一大特点
2.面向对象编程的第一步,就是讲属性和方法封装到一个抽象的类中
3.外界使用类创建对象,然后让对象调用方法
4.对象方法的细节都被封装在类的内部
在类中定义属性, 定义方法就是在封装数据和代码.
###2.2 私有化属性
首先先明确一点, python 不能真正的对属性(和方法)进行私有, 因为 python 没有想 java 那样的`private`可用.
python 提供的"私有", 是为了怕在编程的过程中对对象属性不小心"误伤"提供的一种保护机制! 这种级别的私有稍微只要知道了规则, 是很容易访问到所谓的私有属性或方法的.
------
#### 2.2.1 为什么需要私有
封装和保护数据的需要.
默认情况下, 类的所有属性和方法都是公共的, 也就意味着对他们的访问没有做任何的限制.
意味着, 在基类中定义的所有内容都可以都会被派生类继承, 并可从派生类内部进行访问.
在面向对象的应用程序设计中, 我们通常不希望这种行为, 因为他们暴露基类的内部实现, 可能导致派生类中的使用的私有名称与基类中使用的相同的私有名称发生冲突.
属性或方法私有后就可以避免这种问题!
------
#### 2.2.2 "私有"机制
为了解决前面说的问题, python 提供了一种叫做***名称改写(name mangling)***的机制
如果给属性或者方法命名的时候, 使用两个下划线开头(`__`)的属性和方法名会自动变形为`_类名__方法名`, 这样就避免了在基础中命名冲突的问题.
```python
class Student:
def __init__(self):
pass
def __say(self):
print("我是私有方法你信吗?")
s = Student()
s.__say() # 双下划线开头的方法已经被形变, 此处访问不到
```
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403124836010-1606444133.png)
------
```python
s._Student__say()
```
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403124848245-688690301.png)
------
#### 2.2.3 不是真正的私有
尽管这种方案隐藏了数据, 但是并没有提供严格的机制来限制对私有属性和方法的访问.
虽然这种机制好像多了一层处理, 但是这种变形是发生在类的定义期间, 并不会在方法执行期间发生, 所以并没有添加额外的开销.
#### 2.2.4 不同的声音
有部分人认为这种使用双`__`的机制好辣鸡, 写两个下划线影响效率. 他们使用一个下划线, 并把这个作为一个约定.
好吧, 你喜欢哪种呢?
## 三、面向对象三大特性-继承性(Inheritance)
这一节我们来学习面向的对象的再一个特征: 继承
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403124901526-460680220.png)
### 3.1继承性的概念
继承(`extends`)是创建新类的一种机制, 目的是专门使用和修改先有类的行为.
原有类称为超类(`super class`), 基类(`base class`)或父类.
新类称为子类或派生类.
通过继承创建类时, 所创建的类将继承其基类所有的属性和方法, 派生类也可以重新定义任何这些属性和方法, 并添加自己的新属性和方法
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403124954501-1360999803.png)
### 3.2 继承性的意义
继承实现代码的重用,相同的代码不需要重复的编写
> 从子类的角度来看,避免了重复的代码。(子类继承父类后,子类可以直接使用父类的属性和方法)
>
> 从父类的角度来看,子类扩展了父类的功能。(因为子类也是一个特殊的父类)
1. 子类可以直接访问父类的属性和方法。
2. 子类可以新增自己的属性和方法。
3. 子类可以重写父类的方法。
### 3.3 继承的语法和具体实现
继承的语法如下:
```python
class 父类名:
pass
class 子类名(父类名):
pass
```
#### 3.3.1最简单的继承
python 的继承是在类名的后面添加括号, 然后在括号中声明要继承的父类.
```python
class Father:
def speak(self):
print("我是父类中的 speak 方法")
# Son继承 Father 类
class Son(Father):
pass
s = Son()
s.speak()
```
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403125011481-1627097341.png)
**说明:**
1. 从字面上我们看到`Son`没有定义任何的方法, 但是由于`Son`继承自`Father`, 则`Son`会继承`Father`的所有属性和方法
2. 调用方法时, 方法的查找规则: 先在当前类中查找, 当前类找不到想要的方法, 则去父类中查找, 还找不到然后继续向上查找. 一旦找到则立即执行. 如果找到最顶层还找不到, 则会抛出异常
------
示例代码
```python
# 创建人类
class Person:
# 定义吃东西方法
def eat(self):
print("吃窝窝头。。")
# 定义睡觉方法
def sleep(self):
print("睡着啦。。")
# 创建学生类
class Student(Person):
# 子类新增方法:学习
def study(self):
print("学生学习啦。。。把你爸乐坏了。。。。。")
# 创建父类对象,访问父类的方法
zhangsan = Person();
zhangsan.eat()
zhangsan.sleep()
# 创建子类对象,访问父类的方法和子类的方法
ergou = Student();
ergou.eat() # 访问父类的方法
ergou.sleep() # 访问父类的方法
ergou.study() # 访问子类的新增方法
```
##
####3.3.2 继承中的`__init__()`的调用规则
如果子类没有手动`__init__()`方法, 则 python 自动调用子类的`__init__()`的时候, 也会自动的调用基类的`__init()__`方法.
```python
class Father:
def __init__(self):
print("基类的 init ")
# Son继承 Father 类
class Son(Father):
def speak(self):
pass
s = Son()
```
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403125026961-609675782.png)
------
如果子类手动添加了`__init__()`, 则 python 不会再自动的去调用基类的`__init__()`
```python
class Father:
def __init__(self):
print("基类的 init ")
# Son继承 Father 类
class Son(Father):
def __init__(self):
print("子类的 init ")
def speak(self):
pass
s = Son()
```
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403125038891-1506310844.png)
------
如果想通过基类初始化一些数据, 则必须显示的调用这个方法, 调用语法是:
`基类名.__init__(self, 参数...)`
```python
class Father:
def __init__(self, name):
print("基类的 init ")
self.name = name
def speak(self):
print("我是父类中的 speak 方法" + self.name)
# Son继承 Father 类
class Son(Father):
def __init__(self, name, age):
# name 属性的初始化应该交给基类去完成, 手动调用基类的方法. 一般放在首行
Father.__init__(self, name) # 调动指定类的方法, 并手动绑定这个方法的 self
print("子类的 init ")
self.age = age
s = Son("李四", 20)
s.speak()
print(s.name)
print(s.age)
```
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403125052596-1821115968.png)
------
### 3.4方法的重写(override)
#### 3.4.1重写的概念
我们已经了解了调用方法时候的查找规则, 先在子类中查找, 子类查找不到再去父类中查找.
如果父类的方法不满足子类的需求, 利用这个查找规则, 我们就可以在子类中添加一个与父类的一样的方法, 那么以后就会直接执行子类的方法, 而不会再去父类中查找.
这就叫方法的覆写.(`override`)
```
>重写,就是子类将父类已有的方法重新实现。
```
父类封装的方法,不能满足子类的需求,子类可以重写父类的方法。在调用时,**调用的是重写的方法,而不会调用父类封装的方法。**
#### 3.4.2重写父类方法的两种情况
1. 覆盖父类的方法
父类的方法实现和子类的方法实现,完全不同,子类可以重新编写父类的方法实现。
> 具体的实现方式,就相当于在子类中定义了一个和父类同名的方法并且实现
2. 对父类方法进行扩展
子类的方法实现中包含父类的方法实现。(也就是说,父类原本封装的方法实现是子类方法的一部分)。
> 在子类中重写父类的方法
>
> 在需要的位置使用`super().父类方法`来调用父类的方法
>
> 代码其他的位置针对子类的需求,编写子类特有的代码实现。
如果在覆写的方法中, 子类还需要执行父类的方法, 则可以手动调用父类的方法:
`父类名.方法(self, 参数...)`
------
```python
class Father:
def __init__(self, name):
self.name = name
def speak(self):
print("我是父类中的 speak 方法" + self.name)
# Son继承 Father 类
class Son(Father):
def __init__(self, name, age):
Father.__init__(self, name)
self.age = age
# 子类中覆写了父类的方法
def speak(self):
Father.speak(self)
print("我是子类的 speak 方法" + self.name + " 年龄:" + str(self.age))
s = Son("李四", 20)
s.speak()
```
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403125110716-1086987139.png)
#### 3.4.3关于super
在Python中super是一个特殊的类(Python 3.x以后出现)
super()就是使用super类创建出来的对象
最常使用的场景就是在重写父类方法时,调用在父类中封装的方法实现
### 3.5、父类的私有属性和方法
- 子类对象不能在自己的方法内部,直接访问父类的私有属性或私有方法
- 子类对象可以通过父类的共有方法间接访问到私有属性或私有方法
> 私有属性和方法是对象的隐私,不对外公开,外界以及子类都不能直接访问
>
> 私有属性和方法通常用于做一些内部的事情
### 3.6、多继承
#### 3.6.1多继承的概念
多继承:子类可以拥有多个父类,并且具有所有父类的属性和方法
比如:孩子会继承自己的父亲和母亲的特性
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403125124061-1033896425.png)
#### 3.6.2多继承的语法
```
class 子类名(父类名1, 父类名2...):
pass
```
示例代码:
```python
# 父类A
class A:
def test1(self):
print("A类中的test1方法。。")
# 父类B
class B:
def test2(self):
print("B类中的test2方法。。")
# 子类C同时继承A和B
class C(A,B):
pass
# 创建C对象
c1 = C()
c1.test1()
c1.test2()
```
#### 3.6.3多继承的注意事项
提问:如果不同的父类中存在同名的方法,子类对象在调用方法时,会调用哪一个父类中的方法呢?
> 开发时,应该尽量避免这种容易产生混淆的情况。如果父类之间存在同名的属性或者方法,应该尽量避免使用多继承
#### 3.6.4 Python中的 MRO (方法搜索顺序)[扩展]
python中针对类提供了一个内置属性,`___mro__`可以查看方法搜索顺序
MRO是method resolution order,主要用于在多继承时判断方法,属性的调用路径
```python
print(C.__mro__)
```
输出结果:
```
(
加载全部内容