
首先我理解这样的写法并非最佳实践,只是想理解语言差异与背后的原因。
这是一道 LeetCode 题,讨论区里面每种语言都用了相同的解法,但只有 Python 以同样的写法陷入了死循环,问了 AI 都说两种写法等价,所以不应该存在 bug ,但实测显然是不同的,所以想问问原因。
注意看 while 循环里为两个指针连续赋值的地方。
ListNode *partition(ListNode *head, int x) { ListNode node1(0), node2(0); ListNode *p1 = &node1, *p2 = &node2; while (head) { if (head->val < x) p1 = p1->next = head; else p2 = p2->next = head; head = head->next; } p2->next = NULL; p1->next = node2.next; return node1.next; } public ListNode partition(ListNode head, int x) { ListNode smallerHead = new ListNode(0), biggerHead = new ListNode(0); ListNode smaller = smallerHead, bigger = biggerHead; while (head != null) { if (head.val < x) { smaller = smaller.next = head; } else { bigger = bigger.next = head; } head = head.next; } // no need for extra check because of fake heads smaller.next = biggerHead.next; // join bigger after smaller bigger.next = null; // cut off anything after bigger return smallerHead.next; } 我自己翻译的 Python 版,然而死循环导致超时:
class Solution: def partition(self, head: Optional[ListNode], x: int) -> Optional[ListNode]: lt, ge = ListNode(), ListNode() ltPtr, gePtr = lt, ge while head: if head.val < x: ltPtr = ltPtr.next = head else: gePtr = gePtr.next = head head = head.next ltPtr.next, gePtr.next = ge.next, None return lt.next 翻了一下讨论区,下面的是没问题的 Python 版:
class Solution: def partition(self, head: ListNode, x: int) -> ListNode: head1 = tail1 = ListNode() head2 = tail2 = ListNode() while head: if head.val < x: tail1.next = tail1 = head else: tail2.next = tail2 = head head = head.next tail2.next = None tail1.next = head2.next return head1.next 问题就出在了为两个指针连续赋值的地方,所以为什么在 Python 中,p = p.next = q 与 p.next = p = q 是不等价的呢?分别都是怎样的步骤进行赋值的?
1 gydi 337 天前 简单用 python 测试了一下,有下面这样的结果。 >>> b = {'next': 'b'} >>> c = {'next': 'c'} >>> b = b['next'] = c >>> print(b,c) {'next': {...}} {'next': {...}} >>> b = {'next': 'b'} >>> c = {'next': 'c'} >&t;> b['next'] = b = c >>> print(b,c) {'next': 'c'} {'next': 'c'} 于是猜测 python 的执行对于 a=b=c 来说应该是 a=c;b=c 这样。然后搜了一下,发现这个文章 https://chowyi.com/key-points-of-chained-assignment-in-python/ |
2 rrfeng 337 天前 via Android 我觉得是 c 的遗留 a = ( b = c ) ( b = c )这个赋值表达式是有值的,等于 c |
3 wang93wei 337 天前 |
5 wang93wei 337 天前 我之前写 SQL 的时候也犯过同样的问题。在 where 条件内弄了一个三个值的等式,取决于语言特性,导致 where 条件并不成立。 |
6 cmdOptionKana 337 天前 建议不要去搞懂 a=b=c ,同时不管哪种语言都不要使用 a=b=c ,一律拆开来写。 |
8 superhot OP @cmdOptionKana 我知道这种写法不好 只是邯郸学步 不小心踩了坑 想搞明白为啥摔了 真正工程中可不敢这么写 |
9 aijam 337 天前 a=b=c 在 python 里是 leftmost 所以相当于 |
10 aijam 337 天前 a=b=c 在 python 里是 leftmost 所以相当于 a=c b=c 但在其他 c 系语言里相当于 b=c a=b |
11 aijam 337 天前 @aijam 所以其他的语言里 p1 = p1->next = head; p1->next 先被指向了 head (此时 p1 还不是 head ),p1 才被指向 head 但在 Python 里,ltPtr = ltPtr.next = head ltPtr 先被指向了 head, ltPtr.next 才被指向 head ,此时 head.next 就被指向了 head 本身,是一个循环链表 |
12 cmdOptionKana 337 天前 @superhot 懂了就可能忍不住用,还不如不懂,更容易杜绝使用。而且这个也没啥技术因素,纯粹人为规定,属于“偏好”,偏好是不值得学的。 |
13 rrfeng 337 天前 触发个报错更明显了: ``` >>> b={'n':0} >>> >>> b = b['n'] = 1 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'int' object does not support item assignment ``` |
14 NoOneNoBody 337 天前 python 表达式计算顺序从左至右,且参看手册 Lexical analysis 最后部分 https://docs.python.org/3/reference/lexical_analysis.html#delimiters = 属于分隔符,而多个分隔符两侧优先级相同,则从左至右 最左边变量先得到值,中间变量后得到值 假设 p,q 都含有 next 属性,p.next 为整数,q.next 为 None p.next = p = q p 后赋值,p.next 最终为 None ,但它是两次变化:先是 q 对象,p 赋值为 q 对象时,p.next 也变为 q.next 的值 实际上只写 p=q 就够了,先执行的 p.next=q 被后面 p“覆盖”了 p = p.next = q p.next 后赋值,p.next 为 q 对象,p.next.next 才是 None 这个过程 p.next 两次变化:第一次为 None ,第二次是 q 对象 因为两次变化,实际上最终 p!=q *** 最主要原因是 python 大部分赋值为“引用” 懂的话也不用解释,不懂的话,很难一两句说清,请查阅手册和相关文章 但可以“傻瓜式”理解,就是除了 int/bool, float, complex, str, bytes 这些单值以外,复合的类型全部都要考虑引用问题 最简单例子( b 引用 a ): a=[1,2,3] b=a c=[1,2,3] a[1]=5 print(b,c) # 显示为[1,5,3], [1,2,3] 所以,如果上述 q 为单值的话(应该说没有 next 属性),p=p.next=q 会直接报错,此题是因为所赋值恰好也有 next 属性,所以没有报错而已 |
15 franklinyu 337 天前 via iPhone 正因些奇奇怪怪的歧,一些新言直接不支持式值,我得挺好 |