V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
superhot
V2EX  ›  Python

求教 Python 问题, GPT o1 跟 Gemini 2.0 都解决不了

  •  
  •   superhot · 17 小时 15 分钟前 · 1291 次点击

    首先我理解这样的写法并非最佳实践,只是想理解语言差异与背后的原因。

    这是一道 LeetCode 题,讨论区里面每种语言都用了相同的解法,但只有 Python 以同样的写法陷入了死循环,问了 AI 都说两种写法等价,所以不应该存在 bug ,但实测显然是不同的,所以想问问原因。

    注意看 while 循环里为两个指针连续赋值的地方。

    C++ 版

    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;
    }
    

    Java 版

    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 = qp.next = p = q 是不等价的呢?分别都是怎样的步骤进行赋值的?

    15 条回复    2024-12-27 19:48:26 +08:00
    gydi
        1
    gydi  
       16 小时 34 分钟前
    简单用 python 测试了一下,有下面这样的结果。
    >>> b = {'next': 'b'}
    >>> c = {'next': 'c'}
    >>> b = b['next'] = c
    >>> print(b,c)
    {'next': {...}} {'next': {...}}
    >>> b = {'next': 'b'}
    >>> c = {'next': 'c'}
    >>> 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/
    rrfeng
        2
    rrfeng  
       16 小时 28 分钟前 via Android
    我觉得是 c 的遗留

    a = ( b = c )

    ( b = c )这个赋值表达式是有值的,等于 c
    wang93wei
        3
    wang93wei  
       16 小时 25 分钟前
    zhouyin
        4
    zhouyin  
       16 小时 24 分钟前
    @rrfeng
    java 和 c++也是 c 系
    照理说 a=b=c a 也等于 c 啊 不是吗?
    wang93wei
        5
    wang93wei  
       16 小时 23 分钟前
    我之前写 SQL 的时候也犯过同样的问题。在 where 条件内弄了一个三个值的等式,取决于语言特性,导致 where 条件并不成立。
    cmdOptionKana
        6
    cmdOptionKana  
       16 小时 21 分钟前   ❤️ 3
    建议不要去搞懂 a=b=c ,同时不管哪种语言都不要使用 a=b=c ,一律拆开来写。
    superhot
        7
    superhot  
    OP
       16 小时 15 分钟前 via Android
    @wang93wei 这个回答非常清晰…看来我跟 AI 对话时的描述与问题不够好
    superhot
        8
    superhot  
    OP
       16 小时 14 分钟前 via Android
    @cmdOptionKana 我知道这种写法不好 只是邯郸学步 不小心踩了坑 想搞明白为啥摔了 真正工程中可不敢这么写
    aijam
        9
    aijam  
       16 小时 7 分钟前
    a=b=c 在 python 里是 leftmost 所以相当于
    aijam
        10
    aijam  
       16 小时 6 分钟前   ❤️ 1
    a=b=c 在 python 里是 leftmost 所以相当于
    a=c
    b=c

    但在其他 c 系语言里相当于
    b=c
    a=b
    aijam
        11
    aijam  
       15 小时 56 分钟前
    @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 本身,是一个循环链表
    cmdOptionKana
        12
    cmdOptionKana  
       15 小时 53 分钟前
    @superhot 懂了就可能忍不住用,还不如不懂,更容易杜绝使用。而且这个也没啥技术因素,纯粹人为规定,属于“偏好”,偏好是不值得学的。
    rrfeng
        13
    rrfeng  
       15 小时 35 分钟前
    触发个报错更明显了:

    ```
    >>> 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
    ```
    NoOneNoBody
        14
    NoOneNoBody  
       14 小时 46 分钟前
    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 属性,所以没有报错而已
    franklinyu
        15
    franklinyu  
       9 小时 52 分钟前 via iPhone
    正因為這些奇奇怪怪的歧義,一些新語言直接不支持鏈式賦值,我覺得挺好
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   955 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 21ms · UTC 21:40 · PVG 05:40 · LAX 13:40 · JFK 16:40
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.