前面两篇内容,我们已经介绍了很多实用的面对对象编程技巧,这一节我们继续补充一些概念。对于这些概念大多只需要我们知道即可:
抽象基类
在定义一组类的继承层次结构时,避免重复代码的技术之一是设计一个基类,该基类可以被需要他的其他类所继承。然而,如果以各类唯一目的就是作为继承的基类,这个类既可以叫做抽象基类。更正式地说,一个抽象类不能直接实例化,因为它可以是没有任何实际意义的类。因为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
b.append(6)
print(a)
显然这样的结果显然不符合我们的预期。那么在Python中如何完成拷贝呢?
浅拷贝
浅拷贝的表达方式很简单,我们只需要做以下操作:
a=[1,2,3,4,5]
b=list(a)
b.append(6)
print(a)
看起来这样就满足要求了。其原理如下: 这是一维列表的情况,看起来已经满足了我们的要求。下面我们拟想另一种情况,我们建立一个二维列表,每个元素存储一个学生的三科成绩,并且不需要注明是哪位学生的成绩:
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)
原因如下: 因此,如果我们通过以下方式修改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].insert(1,72)
print(a,'\n',b)
就会发现,通过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)
此时a和b的关系就是独立的:
运算符重载和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<<b | 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<b | 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 _() |
当一个二元操作符应用于两个不同类型的实例中时,Python对根据左操作数的类进行判断。在这个例子中,对于使用__mul__方法把字符串与实例相乘,可以通过检查int类是否提供了相应的定义。然而,如果这个类没有实现这一行为,Python就会以一种名为 rmul(即“右乘”)的特殊方法来检查右操作数的类的定义。该方法为新用户定义的类提供了一个支持包含已存在类(所给的已存在的类可能没有定义引用该新类的行为)的实例的混合操作的方法。__mul__和__rmul__的区别也允许类根据情况定义不同的语义,如操作数在矩阵乘法中就是不可交换的。 到此为止,面对对象编程的细则已经给大家补充了很多了,后面我们再介绍数据结构时可能还会补充另外一些细则,大家记得追更~
|