这两天在刷题,看到链表的反转,在翻解体思路时看到有位同学写出循环中一句搞定三个变量的交换时觉得挺6的,一般用的时候都是两个变量交换(a,b=b,a),这种三个变量的交换还真不敢随便用,而且这三个变量都是可变类型.

心存疑惑然后就多方查找,于是有了下面的测试代码和解体思路.这里需要了解dis查看字节码了解变量的大致交换过程,顺带也延申了反转链表时哪几种是可用的,以及为什么?好了,废话不多说,代码中注释也比较充分,应该能帮助理解.

  1. __doc__ = """
  2. Python的变量并不直接存储值,而只是引用一个内存地址,交换变量时,只是交换了引用的地址。
  3. 在 2、3 个值分配的时候是直接运用栈,在 3 个以上值分配的时候才是用了拆包的原理。
  4. https://www.v2ex.com/t/483347 # 一些讨论
  5. https://stackoverflow.com/questions/21047524/how-does-swapping-of-members-in-tuples-a-b-b-a-work-internally # 一行交换变量的两种原理
  6. https://www.cnblogs.com/aydenwang/p/9398826.html # 四种交换变量的方法
  7. """
  8. import dis
  9. def swap2(a, b):
  10. a, b = b, a # ROT_TWO
  11. print(a, b)
  12. def swap3(a, b, c):
  13. a, b, c = c, b, a # ROT_THREE ROT_TWO
  14. print(a, b, c)
  15. def swap4(a, b, c, d):
  16. a, b, c, d = d, c, b, a # BUILD_TUPLE UNPACK_SEQUENCE
  17. print(a, b, c, d)
  18. def swap5(a, b, c, d, e):
  19. a, b, c, d, e = e, d, c, b, a # BUILD_TUPLE UNPACK_SEQUENCE
  20. print(a, b, c, d, e)
  21. def swap55(a, b, c, d, e):
  22. a, b, c, d, e = d, e, a, c, b # BUILD_TUPLE UNPACK_SEQUENCE
  23. print(a, b, c, d, e)
  24. def swap():
  25. """交换变量,不涉及引用"""
  26. a, b, c, d, e = 10, 20, 30, 40, 50
  27. swap2(a, b)
  28. dis.dis(swap2)
  29. swap3(a, b, c)
  30. dis.dis(swap3)
  31. swap4(a, b, c, d)
  32. dis.dis(swap4)
  33. swap5(a, b, c, d, e)
  34. dis.dis(swap5)
  35. swap55(a, b, c, d, e)
  36. dis.dis(swap55)
  37. """
  38. python3.8
  39. ROT_TWO ROT_THREE ROT_FOUR 这样的指令可以直接交换两个变量,三个变量,四个变量,但是上面的例子中并没用到ROT_FOUR.
  40. Python 将右侧表达式与左侧赋值分开。
  41. 首先计算右侧,结果存储在堆栈上,然后使用再次从堆栈中引用值的操作代码从左到右分配左侧名称。
  42. 对于包含 2 个或 3 个项目的元组分配,Python 仅直接使用堆栈.
  43. """
  44. class Node(object):
  45. """单链表的结点"""
  46. def __init__(self, item):
  47. # item存放数据元素
  48. self.item = item
  49. # next是下一个节点的标识
  50. self.next = None
  51. # def __repr__(self):
  52. # return "{} -> ".format(self.item)
  53. # def __getattr__(self, item):
  54. # print('get')
  55. # return super().__getattr__(item)
  56. # # return self[item]
  57. # def __setattr__(self, key, value):
  58. # print('set', key, value)
  59. # super().__setattr__(key, value)
  60. class SingleLinkList(object):
  61. """单链表"""
  62. def __init__(self):
  63. self._head = None
  64. self._end = None
  65. # 创建单链表
  66. def create(self, node_list):
  67. for k, v in enumerate(node_list):
  68. if k == 0:
  69. self._head = Node(v)
  70. self._end = self._head
  71. else:
  72. p = self._end
  73. p.next = Node(v)
  74. self._end = p.next
  75. def print(self):
  76. """遍历打印链表"""
  77. # 获取head指针
  78. cur = self._head
  79. # 循环遍历
  80. while cur is not None:
  81. # 返回生成器
  82. print(cur.item, end=',')
  83. # 指针下移
  84. cur = cur.next
  85. print("\n--------------")
  86. # 不考虑操作顺序反转必须要操作的步骤有:cur.next=last,last=cur,cur=cur.next,这样一共有6中操作
  87. def reverse1(self):
  88. """交换变量,3种正常:左侧cur都是在cur.next之后改的,遍历顺序不会断裂"""
  89. last, cur = None, self._head
  90. while cur:
  91. cur.next, cur, last = last, cur.next, cur # 无中间变量交换,等式右边先计算,然后链式赋值从左到右.
  92. # cur.next, last, cur = last, cur, cur.next # 同上
  93. # last, cur.next, cur = cur, last, cur.next # 同上
  94. self._head = last
  95. """
  96. 124 16 LOAD_FAST 1 (last) # 从左到右,以此取值
  97. 18 LOAD_FAST 2 (cur)
  98. 20 LOAD_ATTR 1 (next)
  99. 22 LOAD_FAST 2 (cur)
  100. 24 ROT_THREE
  101. 26 ROT_TWO # 从左到右以此赋值
  102. 28 LOAD_FAST 2 (cur) # 获取cur引用
  103. 30 STORE_ATTR 1 (next) # 修改当前节点
  104. 32 STORE_FAST 2 (cur) # 变更cur应用
  105. 34 STORE_FAST 1 (last)
  106. """
  107. def reverse2(self):
  108. """交换变量,3种异常:左侧curl在curl.next前被赋值,导致遍历顺序断裂"""
  109. last, cur = None, self._head
  110. while cur:
  111. cur, cur.next, last = cur.next, last, cur
  112. # cur, last, cur.next = cur.next, cur, last
  113. # last, cur, cur.next = cur, cur.next, last
  114. self._head = last
  115. """
  116. 146 16 LOAD_FAST 2 (cur) # 从左到右,以此取值
  117. 18 LOAD_ATTR 1 (next)
  118. 20 LOAD_FAST 1 (last)
  119. 22 LOAD_FAST 2 (cur)
  120. 24 ROT_THREE
  121. 26 ROT_TWO # 从左到右以此赋值
  122. 28 STORE_FAST 2 (cur) # 变更cur
  123. 30 LOAD_FAST 2 (cur) # 获取最新cur引用
  124. 32 STORE_ATTR 1 (next) # 变更最新cur的next
  125. 34 STORE_FAST 1 (last)
  126. 36 JUMP_ABSOLUTE 12
  127. """
  128. def reverse_swap(self):
  129. """异常:AttributeError: 'NoneType' object has no attribute 'next'"""
  130. last, cur = None, self._head
  131. while cur:
  132. print('前:', cur, cur.next, last)
  133. # 异常,cur值先被修改,导致cur.next取的值已经是被修改后的cur,导致链表断裂
  134. # cur, cur.next, last = cur.next, last, cur
  135. # cur, last, cur.next = cur.next, cur, last
  136. # last, cur, cur.next = cur, cur.next, last
  137. # 正常,cur.next先被修改,再把cur引用被覆盖并不会影响之前已经被修改的cur节点以及cur.next
  138. cur.next, cur, last = last, cur.next, cur # 无中间变量交换,等式右边先计算,然后链式赋值从左到右.
  139. # cur.next, last, cur = last, cur, cur.next # 同上
  140. # last, cur.next, cur = cur, last, cur.next # 同上
  141. print('后:', cur, cur.next, last)
  142. # break
  143. self._head = last
  144. def reverse_swap2(self): # 通过翻转单个note节点达到链表翻转
  145. """翻转链表"""
  146. # 非递归实现
  147. if not self._head or not self._head.next:
  148. return self._head
  149. last = None # 指向上一个节点,以备后用
  150. cur = self._head # 当前节点,也可以不定义变量直接参与循环,此处为了方便理解,单独定义变量
  151. while cur: # 不会像上面那种有多种顺序,用这种零时变量的方式,顺序只有这一种.主要是因为链式交换等式的右侧已经入栈固定了在随后的网左侧赋值时不会改变.
  152. # 先用next_tmp保存head的下一个节点的信息,保证单链表不会因为失去head节点的next而就此断裂(内部循环使用)
  153. next_tmp = cur.next
  154. # 下一跳已经保存好,可以开始修改当前节点的下一跳了,也就是上一个节点last,初始头的上一个是没有的即None
  155. cur.next = last
  156. # 记录下修改后的当前节点,并保存跟新'上一个节点'给下次用.
  157. last = cur
  158. # 当前节点处理完毕,更新为备份好的原先的下一个节点
  159. cur = next_tmp
  160. # 最后一个节点变成了头节点
  161. self._head = last
  162. line = SingleLinkList()
  163. line.create(range(10))
  164. # print('-------------------1')
  165. # dis.dis(line.reverse1)
  166. # print('-------------------2')
  167. # dis.dis(line.reverse2)
  168. # line.reverse_swap()
  169. # line.print()
  170. line.reverse_swap2()
  171. line.print()
  172. """dis每一列的意思:
  173. https://docs.python.org/zh-cn/3/library/dis.html?highlight=dis#module-dis
  174. 第一列:对应的源代码行数。
  175. 第二列:对应的内存字节码的索引位置。
  176. 第三列:内部机器代码的操作。
  177. 第四列:指令参数。
  178. 第五列:实际参数。
  179. LOAD_FAST(var_num)
  180. 将指向局部对象 co_varnames[var_num] 的引用推入栈顶。
  181. STORE_FAST(var_num)
  182. 将 TOS 存放到局部对象 co_varnames[var_num]。
  183. BUILD_TUPLE(count)
  184. 创建一个使用了来自栈的 count 个项的元组,并将结果元组推入栈顶。
  185. UNPACK_SEQUENCE(count)
  186. 将 TOS 解包为 count 个单独的值,它们将按从右至左的顺序被放入堆栈。
  187. POP_TOP
  188. 删除堆栈顶部(TOS)项。
  189. ROT_TWO
  190. 交换两个最顶层的堆栈项。
  191. ROT_THREE
  192. 将第二个和第三个堆栈项向上提升一个位置,顶项移动到位置三。
  193. ROT_FOUR
  194. 将第二个,第三个和第四个堆栈项向上提升一个位置,将顶项移动到第四个位置。
  195. """

由反转链表想到python链式交换变量的更多相关文章

  1. Python链式赋值执行顺序及执行方式的证明

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 在<关于Python链式赋值的赋值顺序问题& ...

  2. [Python]解决python链式extend的技巧

    众所周知python中的list是可以extend的,功能 旨在将两个list合并成一个.譬如[1,2,3].extend([4,5,6])=[1,2,3,4,5,6] 假如有一个list的list, ...

  3. 基于链式链表的栈链式存储的C风格实现

    链式链表的头文件与CPP文件见前文 头文件: #ifndef _LINKSTACK_H_ #define _LINKSTACK_H_ typedef void LinkStack; //创建一个栈 L ...

  4. python链式对比

    参考 https://www.cnblogs.com/shanghongyun/p/10519579.html 为什么Python中“2==2>1”结果为True在Python中,你可能会发现这 ...

  5. Python 笔试集(1):关于 Python 链式赋值的坑

    前言 Python 的链式赋值是一种简易型批量赋值语句,一行代码即可为多个变量同时进行赋值. 例如: x = y = z = 1 链式赋值是一种非常优雅的赋值方式,简单.高效且实用.但同时它也是一个危 ...

  6. 关于Python链式赋值的赋值顺序问题

    在<第4.7节 Python特色的序列解包.链式赋值.链式比较>一文中,老猿这样介绍的: 链式赋值是用一行语句将多个变量赋值为同一个值,语法如下: 变量1=变量2=变量n=赋值表达式 该语 ...

  7. 记python 链式比较的坑

    前两天,python交流群里有人问: “x”<"y"==True 这个表达式输出的是什么,脑子都没动,就觉得应该是True 居然被否定了!立马在命令行里敲了一下,准备用事实打 ...

  8. python链式调用REST API把参数放到URL中

    需求格式:GET /users/:user/repos 程序: class Chain(object): def __init__(self,path=''): self._path=path def ...

  9. C语言实现链表(链式存储结构)

    链表(链式存储结构)及创建 链表,别名链式存储结构或单链表,用于存储逻辑关系为 "一对一" 的数据.与顺序表不同,链表不限制数据的物理存储状态,换句话说,使用链表存储的数据元素,其 ...

随机推荐

  1. golang defer 以及 函数栈和return

    defer 作为延迟函数存在,在函数执行结束时才会正式执行,一般用于资源释放等操作 参考一段代码https://mp.weixin.qq.com/s/yfH0CBnUBmH0oxfC2evKBA来分析 ...

  2. 【转】PostgreSQL Index性能调优

    Index(索引)这个概念对于很多熟悉关系型数据库的人来说,不是一个陌生的概念.当表中数据越来越多时,在查询时,为了避免全表查询(sequence scan)可以在查询相关的条件字段上添加索引.举例来 ...

  3. 浅谈 ArrayList 及其扩容机制

    浅谈ArrayList ArrayList类又称动态数组,同时实现了Collection和List接口,其内部数据结构由数组实现,因此可对容器内元素实现快速随机访问.但因为ArrayList中插入或删 ...

  4. 运行时数据区--程序计数器(PC Register)

    程序计数器(PC Register) 这里的计数器(Program Counter Register)并非为广义上所指的物理寄存器,JVM中的PC寄存器(程序计数器)是对物理PC寄存器的一种抽象模拟, ...

  5. docker zookeeper 集群搭建

    #创建集群目录 mkdir /opt/cluster/zk cd /opt/cluster/zk #清理脏数据[可跳过] docker stop zk-2181 docker stop zk-2182 ...

  6. 普转提Day2

    T1 给定一个区间,求这个区间中只有一个数字与其他数组不相同的数的个数. 给出的区间范围较大,但是要求的数比较少.所以我的想法是这样的:因为这些数只有一个数字和每个数字都相同的数不同,所以考虑将所有数 ...

  7. Django-当前菜单激活状态-模版 request | slice

    如何满足这个需求? 1. view中传递过来一个当前页面的参数标识,通过模版语言进行判断 {% if current_page == 'index' %}active{% endif %} # 每一个 ...

  8. 温故知新----封装(struct)

    上次提到class是最常见的封装,今天发现别人开发的SDK里面有大量的结构体struct 转载: 1. https://blog.csdn.net/a_forever_dream/article/de ...

  9. >>8) & 0xFF中的 >> 和 &0xFF 的作用

    参考:https://blog.csdn.net/iamgamer/article/details/79354617 其中有两个位运算,一个是>>,一个是&. 0xff的作用一: ...

  10. matlab中colormap

    来源:https://ww2.mathworks.cn/help/matlab/ref/colormap.html?searchHighlight=colormap&s_tid=doc_src ...