列表复制导致的BUG,以及random.choices()的问题

近日查看程序生成的日志,发现有两个程序均出现一些重复的操作,尽管我程序中有判断,重复操作会被忽略,不会造成什么不良影响,但是有BUG总归是不好的,于是今天早晨抽出时间探究一下,看看到底是哪里出了问题。

列表复制导致的BUG

程序A很简单,把核心逻辑抽取出来大致是这样:

users = ['a', 'b', 'c', 'd']
for i in range(0, 10):
    users_copy = users
    users_copy.insert(0, 'z')
    for user in users_copy:
        print(user, " do something!")

很简单的几行代码,书写规范逻辑清晰(要脸要脸),为啥会出错呢?

Do something 啥的应该没啥错,继续简化一下代码:

users = ['a', 'b', 'c', 'd']
for i in range(0, 10):
    users_copy = users
    users_copy.insert(0, 'z')
    print(users_copy)

运行一下瞧瞧:

OMG,这是什么鬼,为何插了好多'z',之所以用user_copy,我就是想每次只更改copy的值啊。既然发现了结果的错误,就很轻易可以知道问题在哪里了。据说在 Python 语言中,一个变量保存的值除了基本类型保存的是值外,其它都是引用

所以我上述代码中:
users_copy = users
除了帮我给users起了个外号以外,并没有帮我生成一份全新的copy,亏我那么信任它,给它起了个高大山的名字。

知道了这个问题后,再改起来就简单了。

users = ['a', 'b', 'c', 'd']
for i in range(0, 10):
    users_copy = users[:]
    users_copy.insert(0, 'z')
    print(users_copy)

将代码改成上述样子,再执行一下:

这才是我想要的结果。

除了切片操作users_copy = users[:]还可以用list()函数users_copy = list(users),看起来更优雅一些?

上述修改只适合简单列表,嵌套列表等请使用copy.deepcopy(),反正对我的程序而言,这样的修改就足够了,就不再赘述了。

random.choices()的问题

搞定了程序A,我以为程序B肯定也是相同问题喽,毕竟二者的症状是一样一样的。

然而,我找了半天,我并没有进行插入之类的改变列表的操作,程序中打印了一下过程中的列表,一直没有变化,那重复是怎么产生的呢?

哎,还是老办法,简化一下逻辑,测试一下吧。

import random
users = ['a', 'b', 'c', 'd']
for i in range(0, 10):
    users_selected = random.choices(users, k=2)
    for user in users_selected:
        print(user, " do something!")

就是从一组用户中随即选择两个让他们干活!这么简单的逻辑,怎么看都没啥问题啊?

好吧,继续简化:

import random
users = ['a', 'b', 'c', 'd']
for i in range(0, 10):
    users_selected = random.choices(users, k=2)
    print(users_selected)

运行一下:

大哥,你不是在玩我吧,我让你找俩人干活,不是让你找一个人干两份活! 我吐了一口老血。

该不会是 random.choices的BUG吧,毕竟这货是Python 3.6当中新引进进来的。之前我还特意发一篇文章比较几种方式呢:
《Python 随机选取元素的一些方法以及概率问题》

想到自己发现了一个了不得的BUG,为全人类做出了贡献,那是相当兴奋啊。不过提BUG之前,先好好看看文档吧。

random.choices(population, weights=None, *, cum_weights=None, k=1)
Return a k sized list of elements chosen from the population with replacement.

等等,人家文档里只说了从列表中选择k个元素,没说不能选重复元素啊?(抗议,这是陷阱!)

random.sample(population, k)
Return a k length list of unique elements chosen from the population sequence or set.

倒是人家sample函数好好的,才是我需要的东西,当初手贱把random.sample改成了random.choices

结论

尽管两个程序表现出相同的病症,但是一个是由于我想当然的认为Python中列表赋值会生成一个副本所导致,另外一个是我想当然的认为random.choices的选择结果不会重复所导致。

想当然害死人啊。

还好我只是写点滥程序自己玩,这要是写火箭发射或者卫星轨道控制啥的,画面太美我不敢想象。不过或许这也是人家不用我写火箭发射程序的原因吧😭。

H2
H3
H4
3 columns
2 columns
1 column
18 Comments