Python学习笔记:面向对象高级编程(上)
前言
最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖子,廖雪峰的课程连接在这里:廖雪峰
Python的相关介绍,以及它的历史故事和运行机制,可以参见这篇:python介绍
Python的安装可以参见这篇:Python安装
Python的运行模式以及输入输出可以参见这篇:Python IO
Python的基础概念介绍,可以参见这篇:Python 基础
Python字符串和编码的介绍,可以参见这篇:Python字符串与编码
Python基本数据结构:list和tuple介绍,可以参见这篇:Python list和tuple
Python控制语句介绍:ifelse,可以参见这篇:Python 条件判断
Python控制语句介绍:循环实现,可以参见这篇:Python循环语句
Python数据结构:dict和set介绍Python数据结构dict和set
Python函数相关:Python函数
Python高阶特性:Python高级特性
Python高阶函数:Python高阶函数
Python匿名函数:Python匿名函数
Python装饰器:Python装饰器
Python偏函数:Python偏函数
Python模块:Python模块
Python面向对象编程(1):Python面向对象
Python面向对象编程(2):Python面向对象(2)
Python面向对象编程(3):Python面向对象(3)
Python面向对象编程(4):Pyhton面向对象(4)
目录:
- 前言
- 面向对象高级编程
- 使用slots
- 使用@property
- 多重继承
- MixIn
- 小结
面向对象高级编程
数据封装、继承和多态只是面向对象程序设计中最基础的3个概念。在Python中,面向对象还有很多高级特性,允许我们写出非常强大的功能。
我们会讨论多重继承、定制类、元类等概念。
使用slots
正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。先定义class:
class Student(object):pass
然后,尝试给实例绑定一个属性:
>>> s = Student()
>>> s.name = 'Mike' # 动态给实例绑定一个属性
>>> print(s.name)
Mike
还可以尝试给实例绑定一个方法:
>>> def set_age(self, age): # 定义一个函数作为实例方法
... self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
>>> s.set_age(25) # 调用实例方法
>>> s.age # 测试结果
25
但是,给一个实例绑定的方法,对另一个实例是不起作用的:
对于实例,方法私有
>>> s2 = Student() # 创建新的实例
>>> s2.set_age(25) # 尝试调用方法
Traceback (most recent call last):File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'set_age'
为了给所有实例都绑定方法,可以给class绑定方法:
>>> def set_score(self, score):
... self.score = score
...
>>> Student.set_score = set_score
给class绑定方法后,所有实例均可调用:
>>> s.set_score(100)
>>> s.score
100
>>> s2.set_score(99)
>>> s2.score
99
通常情况下,上面的set_score方法可以直接定义在class中,但动态绑定允许我们在程序运行的过程中动态给class加上功能,这在静态语言中很难实现。
使用__slots__
但是,如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加name和age属性。
为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的_slots_变量,来限制该class实例能添加的属性:
class Student(object):__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
然后,我们试试:
>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'
由于’score’没有被放到slots中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。
如果加入了方法,比如
def set_age(self, age): # 定义一个函数作为实例方法self.age = age
虽然没有增加,age这个属性,但因为有了set_age方法,还是会增加age这个属性。
使用slots要注意,slots定义限制属性仅对当前类实例起作用,对继承的子类是不起作用的:
>>> class GraduateStudent(Student):
... pass
...
>>> g = GraduateStudent()
>>> g.score = 9999
除非在子类中也定义_slots_,这样,子类实例允许定义的属性就是自身的_slots_加上父类的_slots_
使用@property
在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改:
s = Student()
s.score = 9999
这显然不合逻辑。为了限制score的范围,可以通过一个set_score()方法来设置成绩,再通过一个get_score()来获取成绩,这样,在set_score()方法里,就可以检查参数:
class Student(object):def get_score(self):return self._scoredef set_score(self, value):if not isinstance(value, int):raise ValueError('score must be an integer!')if value < 0 or value > 100:raise ValueError('score must between 0 ~ 100!')self._score = value现在,对任意的Student实例进行操作,就不能随心所欲地设置score了:>>> s = Student()
>>> s.set_score(60) # ok!
>>> s.get_score()
60
>>> s.set_score(9999)
Traceback (most recent call last):...
ValueError: score must between 0 ~ 100!
但是,上面的调用方法又略显复杂,没有直接用属性这么直接简单。
有没有既能检查参数,又可以用类似属性这样简单的方式来访问类的变量呢?对于追求完美的Python程序员来说,这是必须要做到的!
还记得装饰器(decorator)可以给函数动态加上功能吗?对于类的方法,装饰器一样起作用。Python内置的@property装饰器就是负责把一个方法变成属性调用的:
class Student(object):@propertydef score(self):return self._score@score.setterdef score(self, value):if not isinstance(value, int):raise ValueError('score must be an integer!')if value < 0 or value > 100:raise ValueError('score must between 0 ~ 100!')self._score = value
@property的实现比较复杂,我们先考察如何使用。把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:
>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):...
ValueError: score must between 0 ~ 100!
注意到这个神奇的@property,我们在对实例属性操作的时候,就知道该属性很可能不是直接暴露的,而是通过getter和setter方法来实现的。
这个@property实现了一个非常重要的功能,根据操作动态实现了不同的函数,函数对人是一样的,但程序对其进行了分类,实现了智能操作。
还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性:
class Student(object):@propertydef birth(self):return self._birth@birth.setterdef birth(self, value):self._birth = value@propertydef age(self):return 2015 - self._birth
上面的birth是可读写属性,而age就是一个只读属性,因为age可以根据birth和当前时间计算出来。
小结
@property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。
多重继承
继承是面向对象编程的一个重要的方式,因为通过继承,子类就可以扩展父类的功能。
在对一个实体进行分类的时候,会有各种判断标准,如果把每个标准都用上,类的设计就太麻烦了,因此,我们可以实现一个多重继承,主分类和特定属性分类。
回忆一下Animal类层次的设计,假设我们要实现以下4种动物:
Dog - 狗狗;Bat - 蝙蝠;Parrot - 鹦鹉;Ostrich - 鸵鸟。
如果按照哺乳动物和鸟类归类,我们可以设计出这样的类的层次:
┌───────────────┐│ Animal │└───────────────┘│┌────────────┴────────────┐│ │▼ ▼┌─────────────┐ ┌─────────────┐│ Mammal │ │ Bird │└─────────────┘ └─────────────┘│ │┌─────┴──────┐ ┌─────┴──────┐│ │ │ │▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Dog │ │ Bat │ │ Parrot │ │ Ostrich │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
但是如果按照“能跑”和“能飞”来归类,我们就应该设计出这样的类的层次:
┌───────────────┐│ Animal │└───────────────┘│┌────────────┴────────────┐│ │▼ ▼┌─────────────┐ ┌─────────────┐│ Runnable │ │ Flyable │└─────────────┘ └─────────────┘│ │┌─────┴──────┐ ┌─────┴──────┐│ │ │ │▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Dog │ │ Ostrich │ │ Parrot │ │ Bat │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
如果要把上面的两种分类都包含进来,我们就得设计更多的层次:
哺乳类:能跑的哺乳类,能飞的哺乳类;
鸟类:能跑的鸟类,能飞的鸟类。
这么一来,类的层次就复杂了:
┌───────────────┐│ Animal │└───────────────┘│┌────────────┴────────────┐│ │▼ ▼┌─────────────┐ ┌─────────────┐│ Mammal │ │ Bird │└─────────────┘ └─────────────┘│ │┌─────┴──────┐ ┌─────┴──────┐│ │ │ │▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ MRun │ │ MFly │ │ BRun │ │ BFly │
└─────────┘ └─────────┘ └─────────┘ └─────────┘│ │ │ ││ │ │ │▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Dog │ │ Bat │ │ Ostrich │ │ Parrot │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
如果要再增加“宠物类”和“非宠物类”,这么搞下去,类的数量会呈指数增长,很明显这样设计是不行的。
正确的做法是采用多重继承。首先,主要的类层次仍按照哺乳类和鸟类设计:
class Animal(object):pass# 大类:
class Mammal(Animal):passclass Bird(Animal):pass# 各种动物:
class Dog(Mammal):passclass Bat(Mammal):passclass Parrot(Bird):passclass Ostrich(Bird):pass
现在,我们要给动物再加上Runnable和Flyable的功能,只需要先定义好Runnable和Flyable的类:
class Runnable(object):def run(self):print('Running...')class Flyable(object):def fly(self):print('Flying...')
对于需要Runnable功能的动物,就多继承一个Runnable,例如Dog:
class Dog(Mammal, Runnable):pass
对于需要Flyable功能的动物,就多继承一个Flyable,例如Bat:
class Bat(Mammal, Flyable):pass
通过多重继承,一个子类就可以同时获得多个父类的所有功能。
MixIn
在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich继承自Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承Runnable。这种设计通常称之为MixIn。
为了更好地看出继承关系,我们把Runnable和Flyable改为RunnableMixIn和FlyableMixIn。类似的,你还可以定义出肉食动物CarnivorousMixIn和植食动物HerbivoresMixIn,让某个动物同时拥有好几个MixIn:
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):pass
MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。
Python自带的很多库也使用了MixIn。举个例子,Python自带了TCPServer和UDPServer这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixIn和ThreadingMixIn提供。通过组合,我们就可以创造出合适的服务来。
比如,编写一个多进程模式的TCP服务,定义如下:
class MyTCPServer(TCPServer, ForkingMixIn):pass
编写一个多线程模式的UDP服务,定义如下:
class MyUDPServer(UDPServer, ThreadingMixIn):pass
如果你打算搞一个更先进的协程模型,可以编写一个CoroutineMixIn:
class MyTCPServer(TCPServer, CoroutineMixIn):pass
这样一来,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。
小结
由于Python允许使用多重继承,因此,MixIn就是一种常见的设计。
只允许单一继承的语言(如Java)不能使用MixIn的设计。
Python学习笔记:面向对象高级编程(上)相关推荐
- Python学习之面向对象高级编程
Python学习目录 1. 在Mac下使用Python3 2. Python学习之数据类型 3. Python学习之函数 4. Python学习之高级特性 5. Python学习之函数式编程 6. P ...
- 侯捷C++学习记录-面向对象高级编程上
目标: 培养正规的.大气的编程习惯 以良好的方式编写C++ class [Object Based(基于对象)] 学习Classes 之间的关系 [Object Oriented(面向对象)] 继承( ...
- 一、C++面向对象高级编程(上) (侯捷)
侯捷 C++八部曲笔记汇总 - - - 持续更新 ! ! ! 一.C++ 面向对象高级开发 1.C++面向对象高级编程(上) 2.C++面向对象高级编程(下) 二.STL 标准库和泛型编程 1.分配器 ...
- Python学习笔记三之编程练习:循环、迭代器与函数
Python学习笔记三之编程练习 1. 编程第一步 # 求解斐波纳契数列 #/user/bin/python3#Fibonacci series:斐波那契数列 #两个元素的总和确定了下一个数 a,b= ...
- Python学习笔记·交互式图形编程
Python学习笔记·交互式图形编程 注:在校计算机学生一名,菜鸟一枚,最近开始学习Python的基础知识希望有什么不对的地方各位大佬能够不令赐教! 课程是在中国大学MOOC上学的,有兴趣的同学可以自 ...
- Python学习笔记:Io编程序列化
前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...
- Python学习笔记:IO编程StringIO和BytesIO
前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...
- Python基础之六面向对象高级编程
'''面向对象高级编程 ''' from enum import Enum'''__slots__限制实例属性定义的属性只对当前类实例起作用,对于继承的子类不起作用''' class Student( ...
- 【C++】侯捷C++面向对象高级编程(上)
C++面向对象高级编程 前言 C++ Programs代码基本形式 文件类型 头文件写法 头文件布局 class1--complex 类的声明 inline--内联函数 class访问级别(acces ...
- Python学习笔记(五.数据分析 ——上)
系列文章持续更新中- 文章目录 前言 一.相关性分析 A.获取股票价格 a.获取日K线的股票价格 b.获取每分钟的股票价格 B. 合并股票价格 C.股票价格相关性分析 二.假设检验 三.方差分析 A. ...
最新文章
- 我知道你会用Jupyter Notebook,但这些插件你都会了吗?
- lnmp 中的laravel出现白屏的处理办法
- 如何让鼠标跑不出来_洗衣机常年不清洗脏过马桶,教你一窍门,脏东西自己跑出来...
- eclipse手动添加SVN插件
- xpath的一些测试
- 解决 java线上问题_Java应用线上问题排查思路
- 模拟地铁乘车推荐c语言,模拟地铁新手攻略 新手必看三要素
- 计算机科学与技术综述文献,计算机科学与技术专业文献综述应该写些什么
- JavaSE11: 简单快速掌握枚举
- Chapter 3 - 作用域
- Num70 债权查询 债权审核
- Spark 理论简答
- HDB3码和AMI码通过Matlab实现编码和解码
- 如何从当前文件夹直接打开cmd快捷键
- 人生之路1.20代码 第三部分
- 计算机表格要学些什么,刚学电脑,不知道在电脑上怎么制作各种表格, – 手机爱问...
- 一着不慎被蛇咬,最近半月终出坑
- 通俗易懂的函数指针及函数指针数组
- Ecshop会员注册的Email 电子邮箱改成非必填项
- 软考架构设计师知识点
热门文章
- rejection from Cambridge Machine Learning and Machine Intelligence MPhil
- 吸收和实践的同时推进
- 不要把时间画在抽奖上。。。去学习吧。。。
- C# 接口持有结构体会导致装箱问题
- Hud 敌兵布阵 --线段树的插点问线
- 自定义装点博客的“门面”
- 打造LINUX系统安全(早期学习笔记)
- unity3d 的Quaternion.identity和transform.rotation区别是什么
- Zookeeper内部的简单细节(一)
- MySQL使用Amoeba作为Proxy时的注意事项