推荐学习书目
› 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
lanlanye
V2EX  ›  Python

你们说 Python 这个是 Bug 还是 Feature?

  •  1
     
  •   lanlanye ·
    laipz8200 · Aug 30, 2022 · 8892 views
    This topic created in 1531 days ago, the information mentioned may be changed or developed.
    class Foo:
        def __init__(self, s: set = set()):
            self.s = s
    
    
    if __name__ == "__main__":
        f1 = Foo()
        f2 = Foo()
        f1.s.add(1)
        f1.s.add(2)
        f1.s.add(3)
        print(f2.s)
    
    

    版本 3.10.4

    结果会是什么呢?

    71 replies  •  2023-01-08 12:50:47 +08:00
    wxf666
    wxf666
    Aug 30, 2022
    1
    不是 {1, 2, 3} 么?
    MoYi123
    MoYi123
    Aug 30, 2022
    2
    老问题了. {1,2,3}啊.
    Jooooooooo
    Jooooooooo
    Aug 30, 2022
    3
    bug 是说不符合设计预期的行为. 这显然不可能是 bug. (虽然我不懂 python
    lanlanye
    lanlanye
    OP
    Aug 30, 2022❤️1
    4
    我猜这种写法大概相当于

    ```
    default = set([1])


    class Foo:
    def __init__(self, s=default):
    self.s = s
    ```
    lusi1990
    lusi1990
    Aug 30, 2022 via Android
    5
    这样用会引发内存泄露啊
    lanlanye
    lanlanye
    OP
    Aug 30, 2022
    6
    @Jooooooooo 应该算符合预期吧,不过感觉有点反直觉,可能我下意识地以为这样写相当于每次创建一个新的 set
    lanlanye
    lanlanye
    OP
    Aug 30, 2022
    7
    @lusi1990 会吗?不是每次实例化时 copy 一个指向 set 的引用吗?
    wxf666
    wxf666
    Aug 30, 2022❤️2
    8
    @lanlanye 人家[官方文档]( https://docs.python.org/zh-cn/3/reference/compound_stmts.html#function)写得很清楚了:

    > **默认形参值会在执行函数定义时按从左至右的顺序被求值。** 这意味着当函数被定义时将对表达式求值一次,相同的“预计算”值将在每次调用时被使用。 这一点在默认形参为可变对象,例如列表或字典的时候尤其需要重点理解:如果函数修改了该对象(例如向列表添加了一项),则实际上默认值也会被修改。 这通常不是人们所想要的。 绕过此问题的一个方法是使用 None 作为默认值,并在函数体中显式地对其进测试,例如:

    ```python
    def whats_on_the_telly(penguin=None):
    __if penguin is None:
    ____penguin = []
    __penguin.append("property of the zoo")
    __return penguin
    ```
    est
    est
    Aug 30, 2022❤️4
    9
    当年 1.6 亿美金估值的公司—— Digg 是如何被一句 Python 函数可变默参 毁掉的

    /t/467817
    lanlanye
    lanlanye
    OP
    Aug 30, 2022
    10
    @est 惊了居然还有这种事情……这个设计确实挺反直觉的,而且很难注意到,刚才发现 PyCharm 对这种情况有提示,Pyright 没有
    Drahcir
    Drahcir
    Aug 30, 2022
    11
    开发了这么多年 Python ,还真没在意这个问题,学习了。
    zone10
    zone10
    Aug 30, 2022❤️1
    12
    算是设计失误, 就跟 Null 的设计失误一样, 反正我用过 Rust 受不了 Go 作为一门比较新的语言居然不保证空类型安全, 特别是结构体默认为 nil 太 tm 容易出错了
    mxT52CRuqR6o5
    mxT52CRuqR6o5
    Aug 30, 2022
    13
    这个我知道,参数默认值在函数声明时就初始化了,像 js 的话参数默认值是在函数执行时初始化的
    fds
    fds
    Aug 30, 2022❤️1
    14
    现在流行用 https://docs.python.org/3/library/dataclasses.html#dataclasses.field
    ```py
    @dataclass
    class C:
    mylist: list[int] = field(default_factory=list)

    c = C()
    c.mylist += [1, 2, 3]
    ```
    20015jjw
    20015jjw
    Aug 30, 2022 via iPhone
    15
    正常 lint 都会提示
    老问题了
    Aloento
    Aloento
    Aug 30, 2022 via iPhone
    16
    @est 所以我说谁用 python 谁倒霉
    kkhaike
    kkhaike
    Aug 30, 2022
    17
    我刚学 python 也踩过这个坑,默认值要用 None
    liuran
    liuran
    Aug 30, 2022 via Android
    18
    不进行 deep copy 算是 python 的 feature 吧,之前自己刚开始用的时候就遇到了这个坑,然后认真学习了一下 shallow copy 和 deep copy
    abersheeran
    abersheeran
    Aug 30, 2022
    19
    新手常犯的错误了……
    lyang
    lyang
    Aug 30, 2022
    20
    这个例子不知道,但是类似的碰到过,可以理解
    Symo
    Symo
    Aug 30, 2022
    21
    应该说是一个反直觉的设计缺陷.
    rev1si0n
    rev1si0n
    Aug 30, 2022
    22
    不是 Bug 也不是 Feature
    littlewing
    littlewing
    Aug 30, 2022
    23
    所以说 py 垃圾语言啊
    yazinnnn
    yazinnnn
    Aug 30, 2022
    24
    不会 python, 试了试可以这样写

    class Foo:
    def __init__(self, createSet = lambda : set()):
    self.s = createSet()

    有点反直觉, 但是想了想又算合理
    Leviathann
    Leviathann
    Aug 30, 2022
    25
    彻底的设计缺陷
    和 js 的 var 和 this 有得一比 甚至更恶劣
    wenhaoy
    wenhaoy
    Aug 30, 2022
    26
    为什么操作 f1 ,会改变 f2 的值?
    hhhhhh123
    hhhhhh123
    Aug 30, 2022
    27
    print(id(f1), id(f2))
    print(id(f1.s), id(f2.s))

    140294972551120 140294972551024
    140295241401280 140295241401280

    破案了。
    ipwx
    ipwx
    Aug 30, 2022
    28
    不是 Bug 也不是 Feature 。我觉得有过 C++ 经验的程序员更容易接受这个设定。
    sillydaddy
    sillydaddy
    Aug 30, 2022❤️1
    29
    @ipwx
    为啥 C++要躺枪。这明显是一个坑,C++有这么坑吗?
    hhhhhh123
    hhhhhh123
    Aug 30, 2022
    30
    我想起来了, 这不是你们说的 设计问题, 这特么是写法问题:
    如 def aaa(list =[] ) 多个使用的时候 地址同一个 所以一般参数不写空 数组 集合等。

    好吧也可以说是设计问题, 但是 注意规范就行。 这特么是我老旧以前的一个面试题。。呜呜呜
    ipwx
    ipwx
    Aug 30, 2022❤️1
    31
    @sillydaddy ummm 本来觉得 C++ 也应该是类似 Python 的行为,没想到不是。

    https://ideone.com/iulg3B

    这还挺让我意外的。这么说来 C++ 的默认值是求值表达式,在调用时会展开。
    ipwx
    ipwx
    Aug 30, 2022
    32
    @sillydaddy 嘛不管怎么说我从内心最基本的原则里面其实早就接受了 Python 的这个行为。如果不是今天看到这个帖子你的回复我估计从来都不会发现这件事。

    不过哪怕避开这种写法,C++ 写起来也应该很方便。。。至少我上面这个例子里面的用法肯定不是正常的 C++ 用法就对了。
    SmiteChow
    SmiteChow
    Aug 30, 2022
    33
    算 bug ,因为人们已经用工具来避免这种情况发生了。
    ipwx
    ipwx
    Aug 30, 2022
    34
    yysy 我觉得接受这种行为去写程序也没啥不好,写起来基本不会有啥不方便的。把对象构造从默认值上面移开其实是一种很好的习惯。
    Eureka0
    Eureka0
    Aug 30, 2022
    35
    确实有点反直觉,但也不能说是 bug 了,别拿可变对象作为函数默认值就是了
    h404bi
    h404bi
    Aug 30, 2022
    36
    习惯 JavaScript 类似默参写法再去写 Python 特别容易中招。只能说此梨非彼梨。
    ipwx
    ipwx
    Aug 30, 2022
    37
    @h404bi 怎么说呢,其实我内心觉得任何语言在默认值里面构造堆对象都不是好的行为。

    @sillydaddy 我第一反应 C++ 程序员应该不会意外这种设计,主要是 C++ 有栈对象。如果是栈对象作为默认值,那么我理所当然地认为这里栈对象本身是默认值的一个拷贝。反而如果默认值是 new XXX(),我理所当然地觉得,那这种东西作为默认值,本来就不行,涉嫌内存泄露。

    简而言之就是我潜意识觉得符合 RAII 原则的程序就不会在默认值 new XXX()。毕竟连 std::shared_ptr<XXX>(new XXX) 都是错误用法,应该使用 std::make_shared<XXX>() 不是吗(笑
    ipwx
    ipwx
    Aug 30, 2022
    38
    接上一条。总之 C++ 里面我第一反应就是写个重载,然后重载函数构造一个局部栈对象或者智能指针传给参数多的那一个,根本不会轮到 new XXX 这种默认值出场。
    keenkiller
    keenkiller
    Aug 30, 2022
    39
    默认形参不能用集合,我记得刚学 Python 的时候就知道了……
    HashV2
    HashV2
    Aug 30, 2022
    40
    把默认值赋值逻辑写倒方法里 不要写在参数上
    est
    est
    Aug 30, 2022
    41
    @Aloento 怎么说呢,面对技术选型,比如用 java 的倒霉的多了去了。但是人数多,你不怎么看得出来,倒霉了只能算你自己蠢。。。。。
    gengchun
    gengchun
    Aug 30, 2022
    43
    这个算是 101 的东西吧。这个是 mutable defaults 另一个是 closures late binding 。

    这两个有没有放在最前面介绍应该做为判断教材是否合格的主要指标。

    另外评价别人的代码,这也是很重要的参考,一旦出现,就要认真想一想了。
    x7DnVcd9bA706oWp
    x7DnVcd9bA706oWp
    Aug 30, 2022
    44
    @kkhaike 是的,一般这么写
    class Foo:
    def __init__(self, s=None):
    if s is None:
    s = set()
    self.s = s
    AV1
    AV1
    Aug 30, 2022❤️2
    45
    老实承认,就是设计失误。

    其他语言的默认参数的表达式在调用的时候才执行的,但 python 是在定义的时候执行的。前者更符合多数人的直觉。
    x7DnVcd9bA706oWp
    x7DnVcd9bA706oWp
    Aug 30, 2022
    46
    @Aloento 菜还怪人家语言不行
    Vegetable
    Vegetable
    Aug 30, 2022
    47
    @duange7X24 一般是 self.s = s or set()
    lucays
    lucays
    Aug 30, 2022
    48
    目前应该所有 py 教学都会说避免这个,要说他是 feature 肯定不是,你根本不可能在任何场景用这个
    说是 bug 嘛,这个好像也不太要修复,硬要 2 选 1 那就是 bug

    还有一个就是,python 的 for 循环完了可以直接接 else 没有 if ,是 feature ,显然不是 bug ,但是我觉得,也应该删除这个 feature ,纯纯的屁用没有,如果真有人用了,还得反应一会才知道这段代码在干嘛,也很反直觉
    acmore
    acmore
    Aug 30, 2022
    49
    是 Feature ,但它会导致一大堆 Bug
    所以是个坏 Feature
    Aloento
    Aloento
    Aug 30, 2022
    50
    @est 中肯的,客观的


    @duange7X24 确实,我推荐您使用 Jvav
    westoy
    westoy
    Aug 30, 2022
    51
    python 默认的 args 在__defaults__下面, 不可能让__defaults__的结果随缘吧?

    本质还是可变和不可变对象引用的问题啊,js 虽然能规避掉 (args=[])这种问题, 但是要是下意识写出 const func = (args=window.xxxx)=>{}不一样么
    jatsz
    jatsz
    Aug 30, 2022
    52
    测试的阶段算 Bug ,这种 Public API 都 release 的就算 Feature 了,只能将错就错,外加补一下文档了。
    dushixiang
    dushixiang
    Aug 31, 2022 via iPhone
    53
    学习了,以前好像写过这样的代码…
    dayeye2006199
    dayeye2006199
    Aug 31, 2022
    54
    老 feature 了
    mikewang
    mikewang
    Aug 31, 2022 via iPhone❤️1
    55
    @Vegetable Python 之父中国分父是吧
    js8510
    js8510
    Aug 31, 2022
    56
    lanlanye
    lanlanye
    OP
    Aug 31, 2022 via iPhone
    57
    @westoy 确实,其实发现这个现象后很快就反应过来原因了,但直觉上还是觉得参数列表里的东西应该是表达式而不是直接求好的值……
    lanlanye
    lanlanye
    OP
    Aug 31, 2022 via iPhone
    58
    @Vegetable 老实说我 16 年入门时看过,当时好像还没有这段……不过我也记不太清了
    fbichijing
    fbichijing
    Aug 31, 2022
    59
    很多 Python 书函数部分都会提到,**参数尽量不要用可变对象**。函数参数部分的可变对象在内存中使用了相同的地址,导致实例化后的操作会产生意料之外的行为。

    我觉得说是 bug 有点过,至少是可以理解的行为。只是在一开始不知道的时候容易被坑。

    ```python
    class Foo:
    def __init__(self, s=None):
    self.s = s if s else set()
    ```
    r4aAi04Uk2gYWU89
    r4aAi04Uk2gYWU89
    Aug 31, 2022
    60
    @zone10 结构体默认可不是 nil
    janxin
    janxin
    Aug 31, 2022
    61
    这是 Feature
    lolizeppelin
    lolizeppelin
    Aug 31, 2022
    62
    python 其实挺 c 的...
    对象全是引用传递
    DonkeyBenjamin
    DonkeyBenjamin
    Aug 31, 2022
    63
    这个不是 python 新手常见问题,python💩山里面成堆出现的吗?
    一般人都知道可以这么写

    def foo(l=None):
    l = l or []
    Namek77
    Namek77
    Aug 31, 2022
    64
    我记得 Pyright 是可以检测出来的
    x7DnVcd9bA706oWp
    x7DnVcd9bA706oWp
    Aug 31, 2022
    65
    @Aloento 都是坑而已,何必纠结坑的哪个更坑;我现在到 go 坑了
    rebeccaMyKid
    rebeccaMyKid
    Aug 31, 2022
    66
    正确的:
    ```
    class Foo:
    def __init__(self, s: set[int] | None = None):
    self.s = s or set()


    if __name__ == "__main__":
    f1 = Foo(set())
    f2 = Foo()
    f1.s.add(1)
    f1.s.add(2)
    f1.s.add(3)
    print(f2.s)
    print(f1.s)

    ```
    apake
    apake
    Aug 31, 2022
    67
    并不是 bug, 也不是 feature. 是一个编程要注意的事项. 默认参数不要使用可变类型.

    @lolizeppelin 并不是引用传递, 而是按值传递引用
    chrawsl
    chrawsl
    Sep 1, 2022
    68
    编辑器会提示不建议这么写,这么写只会实例化一个 set 对象
    llsquaer
    llsquaer
    Sep 1, 2022
    69
    又学到一个不被裁的方法了..哈哈
    noahliaszn
    noahliaszn
    Sep 2, 2022
    70
    It's a bug,but also is a feature. It depends on how to use it.
    缓存数据的时候可以用这个特性 参考 https://foofish.net/python-tricks.html
    ```python

    def factorial(num, cache={}):
    if num == 0:
    return 1
    if num not in cache:
    print('xxx')
    cache[num] = factorial(num - 1) * num
    return cache[num]


    print(factorial(4))
    print("-------")
    print(factorial(4))
    ```
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   1453 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 136ms · UTC 15:13 · PVG 23:13 · LAX 07:13 · JFK 10:13
    ♥ Do have faith in what you're doing.