面对对象编程补充(3)
- 抽象基类
- 深拷贝和钱拷贝
-
- 浅拷贝
- 深拷贝
- 二维列表的创建
- 运算符重载和python的特殊方法
前面两篇内容,我们已经介绍了很多实用的面对对象编程技巧,这一节我们继续补充一些概念。对于这些概念大多只需要我们知道即可:
抽象基类 【数据结构|面对对象编程细则(三)】在定义一组类的继承层次结构时,避免重复代码的技术之一是设计一个基类,该基类可以被需要他的其他类所继承。然而,如果以各类唯一目的就是作为继承的基类,这个类既可以叫做抽象基类。更正式地说,一个抽象类不能直接实例化,因为它可以是没有任何实际意义的类。因为python由于对声明类型没有做强制要求,这种多态性也自然让python对定义正式的抽象基类没有强烈的要求。也因此我们编程中,对抽象基类的应用也不多见。但是python依然提供了一个abc模块定义了抽象基类:
文章图片
我们来看一个例子:
from abc import abstractmethod,ABCMeta
class Seq(metaclass=ABCMeta):
@abstractmethod
def __len__(self):
'''返回接受列表的长度'''
在这个例子中,我们定义了一个__len__()方法,可以看到我们注释了该方法所实现的功能,然而其内部却没有任何实现。这是因为在该方法声明前,我们使用了@abstractmethod声明了这个方法是抽象的,不需要再Seq类中提供实现,我们希望这个方法在继承该类的子类中提供实现。
深拷贝和钱拷贝 拷贝是一个非常常见的内容,在C语言中,我们只需要定义两个不同的变量名,再将一个标识符内容直接复制给另一个,就可以较好地完成拷贝,然而在python基础补充中我们提到过,如果按以下定义:
a=[1,2,3,4,5]
b=a
# a和b的实际地址是一样的,只是给一个内容起了两个别名,
# 并不是创立一个新的列表
b.append(6)
print(a)
# 输出为:[1, 2, 3, 4, 5, 6]
以上特点还可以继续拓展到二维列表的创建中。假如我们使用a=[[0]*n]*m的格式创建一个列表,然后赋值,会是什么结果呢:
a=[[0]*3]*3
for i in range(3):
a[0][i]=i+1
print(a)
# 输出为:[[1, 2, 3], [1, 2, 3], [1, 2, 3]]
虽然我们的指令是更改a[0]列表的内容,但是后面的列表也都跟着联动了。问题出现在这种创建二维列表的方式上,实际上a[0],a[1],a[2]并非三个列表,而只是一个列表的三个别称而已,因此改动一个,另外的两个也会跟着动。以上两个例子,都没有达到拷贝的效果,如果放在实际的编程中,会造成莫名其妙的错误。那么,python中如何拷贝一个列表呢?
浅拷贝 浅拷贝的表达方式很简单,我们只需要做以下操作:
a=[1,2,3,4,5]
b=list(a)
b.append(6)
print(a)
# 输出为:[1, 2, 3, 4, 5]
看起来这样就满足要求了。其原理如下:
文章图片
这是一维列表的情况,看起来已经满足了我们的要求。下面我们拟想另一种情况,我们建立一个二维列表,每个元素存储一个学生的三科成绩,并且不需要注明是哪位学生的成绩:
a=[[70,65,92],[88,84,73],[60,60,60]]
b=list(a)
大家猜猜拷贝的情况是什么样呢?
文章图片
如果我们想要给b列表再加一列,是不会影响a的:
a=[[70,65,92],[88,84,73],[60,60,60]]
b=list(a)
b.append([62,100,91])
print(a,'\n',b)
# 输出为:[[70, 65, 92], [88, 84, 73], [60, 60, 60]]
#[[70, 65, 92], [88, 84, 73], [60, 60, 60], [62, 100, 91]]
原因如下:
文章图片
因此,如果我们通过以下方式修改b[0]的内容:
a=[[70,65,92],[88,84,73],[60,60,60]]
b=list(a)
b.append([62,100,91])
del (b[0])[1] # 删除b[0]列表中的第二个元素
b[0].insert(1,72) # 将b[0]列表中第二个元素改为72
# 以上两行也可以直接写成:
# b[0][1]=72
print(a,'\n',b)
# 输出为:[[70, 72, 92], [88, 84, 73], [60, 60, 60]]
#[[70, 72, 92], [88, 84, 73], [60, 60, 60], [62, 100, 91]]
就会发现,通过b修改a和b列表重叠部分的元素时,依然会对a造成影响。
如果想要从根本上解决这个问题,就需要用到深拷贝。
深拷贝 python给我们提供了一个拷贝的模块即copy,这个模块可以帮助我们实现真正的拷贝即深拷贝。深拷贝可以让上例a和b成为完全独立的部分:
import copy
a=[[70,65,92],[88,84,73],[60,60,60]]
b=copy.deepcopy(a)
b.append([62,100,91])
b[0][1]=72
print(a,'\n',b)
# 输出为:[[70, 65, 92], [88, 84, 73], [60, 60, 60]]
#[[70, 72, 92], [88, 84, 73], [60, 60, 60], [62, 100, 91]]
此时a和b的关系就是独立的:
文章图片
二维列表的创建 说完了一维列表的拷贝,下面我们来介绍上文中遗留的另一个问题——二维列表的初始化。如果想要创建一个后期能够独立而非联动的修改具体值的二维列表,需要用以下方式:
a=[[0]*m for i in range(n)]
这样就可以让a[0]到a[n-1]是n个独立的列表啦:
a=[[0]*3 for i in range(5)]
for i in range(3):
a[3][i]=i+1
print(a)
# 输出为:[[0, 0, 0], [0, 0, 0], [0, 0, 0], [1, 2, 3], [0, 0, 0]]
当然,创建一个联动的二维列表在某些特定情况下也是有用的。
运算符重载和python的特殊方法 Python的内置类为许多操作提供了自然的语义。比如,a+b语句可以调用数值类型语句,也可以连接序列类型。当定义一个新类时,我们必须考虑到当a或者b是类中的实例时是否应该定义类似于a+b的语句。
默认情况下,对于新的类来说,“+”操作符是未定义的。然而,类的作者可通过操作符重载(operator overloading)技术来定义它。这个定义可通过一个特殊的命名方法来实现。特别的是,名为__add__的方法重载+操作符,__add__用右边的操作作为参数并返回表达式的结果。也就是说,a+b语句,被转换为一个调用a.__add(b)对象的方法。类似的特殊命名方法存在其他操作符中。表2-1提供了与这一方法类似的完整列表。
表2-1 用Python特殊方法实现的重载操作
常见语法 | 特别方法的形式 |
---|---|
a+b | a._add _(b)或 b._radd _(a) |
a-b | a._sub _(b)或b._rsub _(a) |
a*b | a._mul _(b)或b._rmul _(a) |
a/b | a._truediv _(b)或b._rtruediv _(a) |
a//b | a._floordiv _(b)或b._rfloordiv _(a) |
a%b | a.__ mod_ _(b); 或b._rmod _(a) |
a**b | a._pow _(b)或b._rpow _(a) |
a< | a._lshift _(b); 或b._rlshift _(a) |
a>>b | a._rshift _(b); 或b._rrshift _(a) |
a&b | a._and _(b); 或b._rand _(a) |
a^b | a._xor _(b); 或b._rxor _(a) |
a | b |
a+=b | a._iadd _(b) |
a-=b | a._isub _(b) |
a*=b | a._imul _(b) |
+a | a._pos _(b) |
-a | a._neg _(b) |
~a | a._inwert _(b) |
abs(a) | a._abs _(b) |
a | a._lt _(b) |
a<=b | a._le _(b) |
a>b | a._gt _(b) |
a>=b | a._ge _(b) |
a==b | a._eq _(b) |
a!=b | a._ne _(b) |
vina | a._contains _(v) |
a[k] | a._getitem _(k) |
a[k]=v | a._setitem _(k,v) |
dela[k] | a._delitem _(k) |
a(arg1,arg2,…) | a._call _(arg1,arg2,…) |
len(a) | a._len _() |
hash(a) | a._hash _() |
iter(a) | a._iter _() |
next(a) | a._next _() |
bool(a) | a._bool _() |
float(a) | a._float _() |
int(a) | a._int _() |
repr(a) | a._repr _() |
reversed(a) | a._reversed _() |
str(a) | a._str _() |
到此为止,面对对象编程的细则已经给大家补充了很多了,后面我们再介绍数据结构时可能还会补充另外一些细则,大家记得追更~
推荐阅读
- 数据结构|递归算法简介
- 数据结构|浅谈算法分析
- Windows核心编程|Windows提取环境变量
- python全栈开发|进军Python全栈开发--14.Python中的数据结构与算法
- b树|MySQL索引原理,设计原则
- 算法|KMP算法、计算器(一)、(二)
- JAVA|数组基本使用——java SE
- 后端|马老师力荐(腾讯 SpringBoot 高阶笔记,限时开源)
- #|数据结构-双向循环链表