前提知识点
本文以准备以“链表类”为例,一步步深入了解学习“类”的各种功能实现。关于链表的概念定义略,详见上一篇文章的介绍: 链接地址点这里快速到达
一个最基础的节点类
就一个初始化方法 __init__(),定义一个节点类
三个属性: 其中,val和value是等价的,表示节点存储的数据; next 表示指针,指向下一节点。
class Node(): def __init__(self, value=None, Next=None): self.val = self.value = value self.next = Next ''' # 测试: >>> node = Node(1) >>> node.next = Node(2) >>> node.val 1 >>> node.value 1 >>> node.next.val 2 >>> >>> node = Node(1, Node(2)) >>> node.val 1 >>> node.next.value 2 >>> '''
进一步尝试其他操作:
>>> node = Node() >>> help(Node) Help on class Node in module __main__: class Node(builtins.object) | Node(value=None, Next=None) | | Methods defined here: | | __init__(self, value=None, Next=None) | Initialize self. See help(type(self)) for accurate signature. | | ---------------------------------------------------------------------- | Data descriptors defined here: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined) >>> node <__main__.Node object at 0x00375B68> >>> node.__doc__ >>> node.__dict__ {'val': None, 'value': None, 'next': None} >>> node = Node(1,Node(2)) >>> node.__dict__ {'val': 1, 'value': 1, 'next': <__main__.Node object at 0x00375BB0>}
类的方法定义、数据描述符定义等就一两项,说明文档也为空。但就这么简单的类,已经可以用它解决实际问题了,来举个简单的例子:
将两个有序链表(升序)合并为一个新的有序链表并返回。
List1: 1->2>4->8 List2: 1->3->3->5->5
把可以反复利用的代码,新写两个函数拓展到这个类里:除了主要功能合并方法外,另写一个便于检查是否正确的遍历方法。
拓展一: 新增方法
>>> class Node(): def __init__(self, value=None, Next=None): self.val = self.value = value self.next = Next def merge(self, node): ret = cur = Node() while self is not None and node is not None: if self.val < node.val: cur.next = self self = self.next else: cur.next = node node = node.next cur = cur.next cur.next = self or node return ret.next def travel(self): ret,cur = [],self while cur is not None: ret.append(cur.val) cur = cur.next return ret
测试结果:
>>> NodeList1 = Node(1,Node(2,Node(4,Node(8)))) >>> NodeList2 = Node(1,Node(3,Node(3,Node(5,Node(5))))) >>> NodeList1.travel() [1, 2, 4, 8] >>> NodeList2.travel() [1, 3, 3, 5, 5] >>> NodeList = NodeList1.merge(NodeList2) >>> NodeList.travel() [1, 1, 2, 3, 3, 4, 5, 5, 8] >>>
经测试,完全正确!
拓展二:魔法方法
先看一下,自定义节点类与内置list列表类的对比结果:
>>> l = list([1,2,3]) >>> len(l) 3 >>> str(l) '[1, 2, 3]' >>> l [1, 2, 3] >>> type(l) <class 'list'> >>> type(l) is list True >>> >>> n = Node(1,Node(2,Node(3))) >>> len(n) Traceback (most recent call last): File "<pyshell#82>", line 1, in <module> len(n) TypeError: object of type 'Node' has no len() >>> str(n) '<__main__.Node object at 0x0000000002CD28B0>' >>> n '<__main__.Node object at 0x0000000002CD28B0>' >>> type(n) <class '__main__.Node'> >>> type(n) is Node True >>>
肯定败下阵来,看来需要请出__len__, __str__, __repr__等这些标准的类内置方法(命名两头均是双下划线,是类内置的内部方法,这种特殊方法也被称为“魔法方法”),来模拟一下内置类可以被len(), 被str()的功能。同时去掉merge方法,travel改名为values,重写原travel,最后新增全局变量__doc__,__version__,__all__等用于返回信息。
class Node(): '''class Node of Singlelinked List''' __version__ = '1.0.0' __all__ = ['size','travel','values','__version__'] def __init__(self, value=None, Next=None): self.val = self.value = value self.next = Next def __repr__(self): return f'Node({self.val}->{self.next})' def __str__(self): return f'{self.val}->{self.next}' def __len__(self): if self.val is None: return 0 size,cur = 0,self while cur is not None: size += 1 cur = cur.next return size def __add__(self, other): if self.val is None: self = other return self cur = self while cur.next is not None: cur = cur.next cur.next = other self.next = cur return self def values(self): ret,cur = [],self while cur is not None: ret.append(cur.val) cur = cur.next return ret def travel(self): ret = self.__str__() return ret[:-6] def size(self): return self.__len__()
测试结果:
>>> n = Node(1,Node(2,Node(3))) >>> n.__all__ ['size', 'travel', 'values', '__version__'] >>> n.__doc__ 'class Node of Singlelinked List' >>> n.__version__ '1.0.0' >>> n.__dict__ {'val': 1, 'value': 1, 'next': Node(2->3->None)} >>> n.size() 3 >>> len(n) 3 >>> n.__len__() # 就是用来被len()调用的,一般情况不会使用内部方法 3 >>> str(n) '1->2->3->None' >>> n.__str__() # 就是用来被str()调用的,一般情况不会使用内部方法 '1->2->3->None' >>> repr(n) 'Node(1->2->3->None)' >>> n.__repr__() # 就是用来被repr()调用的,一般情况不会使用内部方法 'Node(1->2->3->None)' >>> n # 变量本身也是repr的结果,但输出时没有引号 Node(1->2->3->None) >>> n.travel() '1->2->3' >>> n.values() [1, 2, 3] >>>
是不是很赞? 魔法方法不仅仅只有这几个基本的,它共有八大类好几十种呢。它的本质就是用来实现重载对象的方法,可以用于重载算术运算、逻辑比较、类型转换、容器迭代等。
注:这个类里的__repr__、__str__是以递归实现的,与python允许的最大值递归深度有关,大概340个元素以上的会报错。但 .values .travel用遍历得来,不受影响。
拓展三:重载比较运算符
以比较运算(>、<、==)为例,先看一下未重载之前的效果:
>>> a,b=Node(1,Node(2,Node(3))),Node(1,Node(2,Node(3))) >>> a==b False >>> a>b Traceback (most recent call last): File "<pyshell#123>", line 1, in <module> a>b TypeError: '>' not supported between instances of 'Node' and 'Node' >>> a<b Traceback (most recent call last): File "<pyshell#124>", line 1, in <module> a<b TypeError: '<' not supported between instances of 'Node' and 'Node' >>>
结果:大于或小于没法用,等于么即便是两条节点链(多个节点相接而成)的所有节点都一一相等也返回False。
重载时比较运算(>、<、==)分别对应(__gt__、__lt__、__nq__),各是: greater than, less than, equal to 的意思。重载约定,两个节点链哪个节点数多就哪个大,节点数一样时每个节点值都相等为“==全等”,代码如下:
class Node(): '''class Node of Singlelinked List''' __version__ = '1.0.0' __all__ = ['size','travel','values','__version__'] def __init__(self, value=None, Next=None): self.val = self.value = value self.next = Next def __repr__(self): return f'Node({self.val}->{self.next})' def __str__(self): return f'{self.val}->{self.next}' def __len__(self): if self.val is None: return 0 size,cur = 0,self while cur is not None: size += 1 cur = cur.next return size def __gt__(self, other): if len(self)>len(other): return True else: return False def __lt__(self, other): if len(self)<len(other): return True else: return False def __eq__(self, other): cur1,cur2 = self,other if len(cur1)!=len(cur2): return False while cur1 is not None: if cur1.val!=cur2.val: return False cur1 = cur1.next cur2 = cur2.next else: return True def values(self): ret,cur = [],self while cur is not None: ret.append(cur.val) cur = cur.next return ret def travel(self): ret = self.__str__() return ret[:-6] def size(self): return self.__len__()
测试效果:
>>> a,b = Node(1,Node(2,Node(3))), Node(1,Node(2,Node(3))) >>> a == b True >>> a > b False >>> a < b False >>> >>> a,b = Node(1,Node(2,Node(3))), Node(3,Node(2)) >>> a > b True >>> a < b False >>> a == b False >>> b > a False >>> b < a True >>>
已完美达到预期效果!
拓展四:属性装饰器
用类属性装饰器@property标注类方法,能使类的方法变为只读属性。以下代码把values()方法和另一个与size()方法相同功能的length()方法转变成两个属性: values和length:
class Node(): '''class Node of Singlelinked List''' __version__ = '1.0.0' __all__ = ['length','value','values','cat','size','travel','__version__'] def __init__(self, value=None, Next=None): self.val = self.value = value self.next = Next def __repr__(self): return f'Node({self.val}->{self.next})' def __str__(self): return f'{self.val}->{self.next}' def __len__(self): if self.val is None: return 0 size,cur = 0,self while cur is not None: size += 1 cur = cur.next return size def __gt__(self, other): if len(self)>len(other): return True else: return False def __lt__(self, other): if len(self)<len(other): return True else: return False def __eq__(self, other): cur1,cur2 = self,other if len(cur1)!=len(cur2): return False while cur1 is not None: if cur1.val!=cur2.val: return False cur1 = cur1.next cur2 = cur2.next else: return True @property def values(self): ret,cur = [],self while cur is not None: ret.append(cur.val) cur = cur.next return ret def travel(self): ret = self.__str__() return ret[:-6] def size(self): return self.__len__() @property def length(self): return self.__len__()
测试结果:
>>> a,b = Node(1,Node(2,Node(3))),Node(4,Node(5)) >>> a.length 3 >>> a.values [1, 2, 3] >>> b.length 2 >>> b.values [4, 5] >> >>> b.next.next = a >>> b Node(4->5->1->2->3->None) >>> b.length 5 >>> b.values [4, 5, 1, 2, 3] >>>
一个节点类的代码基本成型了,此时类的帮助返回如下:
>>> help(Node) Help on class Node in module __main__: class Node(builtins.object) | Node(value=None, Next=None) | | class Node of Singlelinked List | | Methods defined here: | | __eq__(self, other) | Return self==value. | | __gt__(self, other) | Return self>value. | | __init__(self, value=None, Next=None) | Initialize self. See help(type(self)) for accurate signature. | | __len__(self) | | __lt__(self, other) | Return self<value. | | __repr__(self) | Return repr(self). | | __str__(self) | Return str(self). | | size(self) | | travel(self) | | ---------------------------------------------------------------------- | Readonly properties defined here: | | length | | values | | ---------------------------------------------------------------------- | Data descriptors defined here: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined) | | ---------------------------------------------------------------------- | Data and other attributes defined here: | | __all__ = ['length', 'value', 'values', 'size', 'travel', '__version__... | | __hash__ = None >>> >>> Node.__all__ ['length', 'value', 'values', 'size', 'travel', '__version__'] >>> dir(Node) ['__add__', '__all__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__len__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__version__', '__weakref__', 'cat', 'length', 'size', 'travel', 'values'] >>>
拓展五:创建节点链方法
增加新增、追加、创建等方法,方便节点的赋值,没有设置这些方法时赋值也要写代码,如:
>>> n = Node(1) >>> t = n >>> for i in range(2,10): t.next = Node(i) t = t.next print(n) 1->2->None 1->2->3->None 1->2->3->4->None 1->2->3->4->5->None 1->2->3->4->5->6->None 1->2->3->4->5->6->7->None 1->2->3->4->5->6->7->8->None 1->2->3->4->5->6->7->8->9->None >>> >>> >>> n = Node(9) >>> for i in reversed(range(1,9)): n = Node(i, n) print(n) 8->9->None 7->8->9->None 6->7->8->9->None 5->6->7->8->9->None 4->5->6->7->8->9->None 3->4->5->6->7->8->9->None 2->3->4->5->6->7->8->9->None 1->2->3->4->5->6->7->8->9->None
有了以上经验,添加push,append,cat三个方法和一个函数build(),代码如下:
class Node(): '''class Node of Singlelinked List''' __version__ = '1.0.1' __all__ = ['length','value','values', 'push','append','cat', 'build', 'size','travel','__version__'] def __init__(self, value=None, Next=None): self.val = self.value = value self.next = Next def __repr__(self): return f'Node({self.val}->{self.next})' def __str__(self): return f'{self.val}->{self.next}' def __len__(self): if self.val is None: return 0 size,cur = 0,self while cur is not None: size += 1 cur = cur.next return size def __gt__(self, other): if len(self)>len(other): return True else: return False def __lt__(self, other): if len(self)<len(other): return True else: return False def __eq__(self, other): cur1,cur2 = self,other if type(cur1) is not Node: return False if type(cur2) is not Node: return False if len(cur1)!=len(cur2): return False while cur1 is not None: if cur1.val!=cur2.val: return False cur1 = cur1.next cur2 = cur2.next else: return True @property def values(self): ret,cur = [],self while cur: ret.append(cur.val) cur = cur.next return ret def travel(self): ret = self.__str__() return ret[:-6] def size(self): return self.__len__() @property def length(self): return self.__len__() def append(self, value): '''在尾节点后追加数据或节点,数据允许为可迭代对象''' if self.val is None: self = Node.build(value) return self node = self while node.next: node = node.next if type(value) is Node: node.next = value else: tmp = Node.build(value) node.next = tmp return self def push(self, value): '''在头节点前插入数据或节点,数据允许为可迭代对象''' tmp = Node(self.val,self.next) if type(value) is Node: ret = Node(value.val,value.next) else: ret = Node.build(value) if tmp.val: ret.append(tmp) self.val,self.next = ret.val,ret.next return self def cat(self, other, flag=True): '''节点拼接,默认为追加,flag=Flase时插在头节点前''' tmp = Node.build(other) if flag: ret = Node.build(self.values+tmp.values) else: ret = Node.build(tmp.values+self.values) self.val,self.next = ret.val,ret.next return self def build(*data): '''多参数创建节点链,参数允许为可迭代对象''' values = [] for d in data: if hasattr(d,'__iter__'): for i in d: values.append(i) elif type(d) is Node: values.extend(d.values) else: values.append(d) cur = Node(values[-1]) for value in values[:-1][::-1]: cur = Node(value, cur) return cur
其中build()允许多参数输入;其它三个方法均允许可迭代对象作参数:
>>> node = Node.build(range(1,5),5,(6,7),[8]) >>> node Node(1->2->3->4->5->6->7->8->None) >>> a = Node.build(1,2,3) >>> b = Node.build(4,5) >>> a.cat(b) Node(1->2->3->4->5->None) >>> a.cat(b,flag=False) Node(4->5->1->2->3->4->5->None) >>> a = Node.build(1,2,3) >>> a.append(4) Node(1->2->3->4->None) >>> a.append(range(5,8)) Node(1->2->3->4->5->6->7->None) >>> a.append([8,9]) Node(1->2->3->4->5->6->7->8->9->None) >>> a = Node.build(1,2,3) >>> a.push(4) Node(4->1->2->3->None) >>> a.push(range(5,8)) Node(5->6->7->4->1->2->3->None) >>> a.push([8,9]) Node(8->9->5->6->7->4->1->2->3->None) >>>
有了拼接的方法,就可以尝试一下加法和乘法的重载——
拓展六:重载算术运算符
重载的约定:加法为两个节点的链的拼接;乘法为数乘,即一个节点链与一个正整数n相乘等价于反复n次相接,与0乘则为清空。乘法要注意分左乘和右乘,没有右乘方法整数在左边运算时会出错,代码如下放到拓展五中代码中即可:
def __add__(self, other): if type(other) is not Node: raise 'another Node typeError' ret = Node.build(self.values+other.values) return ret def __mul__(self, other): if type(other) is not int: raise 'Integer typeError' if other==0: return Node() elif other>0: tmp = Node(self.val,self.next) return Node.build(tmp.values * other) else: raise 'Integer rangeError' def __rmul__(self,other): return self.__mul__(other)
拓展七:添加其他方法
有增加必有减少,常见的操作除了创建、追加、插入,还有弹出、删除、查找、反序等等。有了以上的经验,给这个类添加这些方法不是难事,在以后的续篇中再来添加这些方法。
拓展八:增加单链表类
单链表是多个节点相接的线性列表,基本就是调用Node()方法来操作数据。省略的代码如下:
class Node(): def __init__(self, value=None, Next=None): self.value = value self.next = Next def __repr__(self): return f'Node({self.value})' #......略...... class List(): def __init__(self, node=None): if not hasattr(node,'__iter__'): self.data = Node(node) else: self.data = None for val in node: self.append(val) def __str__(self): return f'List({self.__items__()})' __repr__ = __str__ def __items__(self): if self.is_empty(): return None else: res,cur = [],self.data while cur is not None: res.append(cur.value) cur = cur.next return res def __getitem__(self, key): lst = self.values return lst[key] def __len__(self): if self.data is None: return 0 return len(self.__items__()) def is_empty(self): return self.data is None size = __len__ def push(self, val): node = Node(val) node.next = self.data self.data = node def append(self, val): if self.is_empty(): return self.push(val) node = self.data while node.next is not None: node = node.next node.next = Node(val) def pop(self): if self.is_empty(): return None if self.size()==1: self.next = None node = self.data while node.next is not None: node = node.next node.next = Node(val) return def clear(self): self.data = None def pprint(self): if self.data==None: print('None') return for i,v in enumerate(self.__items__()): t = f"'{v}'" if type(v) is str else v print(f'{t}->' if i<self.size()-1 else f'{t}->None',end='') #......略......
大致的结构如上,未完善全部功能。如果把代码存入一个文本文件,如 linkedlist.py;以后只要导入这个就能用这些方法来编程了:
import linkedlist node = linkedlist.Node.build(1,2,3,4) print(node) #...... 或者: from linkedlist import Node node = Node() node.add([1,2,3]) print(node)
本文完,以上所有代码只求实现效果,没有太多讲究算法优化。有空再来继续研究吧......