记一次令人沮丧的调试 - 又:为什么 enumerate(set(...))的顺序有时是随机的?

2020-12-29 21:44:15 +08:00
 yanhh

大大前天写了一个神经网络,跑了三天,到大前天重启电脑的时候才发现状态保存没写好,当时以为是有参数变量忘记存了,或者是存储的时候操作错误,查错查了三个小时都没查出问题(还发了个帖子: https://www.v2ex.com/t/739076 ),心情很差,不想调了,就把代码放在一边,跑去做别的事了,每天时不时看看代码,但是怎么看都感觉没问题。

刚刚终于开始单步调试,一调就发现问题了。

问题相当隐蔽:

我的网络输入的是文本,预处理的时候先做了一个字符表:

chars = set(open('...txt').read())
self.char_to_ix = { ch:i for i,ch in enumerate(chars) }

这个字符表看起来每次运行都是一样的,但是实际上不是。

不知道为什么,enumerate(set(...))这个操作每次得到的内容顺序是随机的。大家可以去自己的 Python 里黏贴这一句试试:

{ ch:i for i,ch in enumerate(set('我爱你中国')) }

比比如我第一次运行,结果是:

{'国': 0, '爱': 1, '中': 2, '你': 3, '我': 4}

重启 Python(必须重启),再运行,结果变为:

{'爱': 0, '中': 1, '你': 2, '我': 3, '国': 4}

就是因为这个简单的错误,一旦重启程序,虽然网络参数都保存得很好,但是因为数据源变了,所以训练得从头开始。

最令人沮丧的是,我本来以为这是一个很难调试的错误,所以一直没有去动它;但是真正一开始调试,又发现这个问题如此简单————随后发现,我只是懒而已(= =)

不管怎么样,enumerate(set(...))的这个行为都很奇怪。我简单翻看了一下文档,但是没见到有关解释。打算去 Stackoverflow 问问。

4267 次点击
所在节点    Python
40 条回复
just1
2020-12-29 21:48:59 +08:00
set 本来就没顺序啊...
yanhh
2020-12-29 21:51:19 +08:00
对啊,就是想不懂为什么 Python 的 set 还有随机性。它生成的时候肯定是一个一个读的吧?为什么要加这个随机性?
yanhh
2020-12-29 21:52:00 +08:00
………………一想就懂了,因为内部实现有哈希
superrichman
2020-12-29 22:06:23 +08:00
集合没有顺序的
顺便说一句 dict 的 key 也没顺序
chchwy
2020-12-29 22:08:43 +08:00
看 set 底下的實現,如果是 tree 那就是有序,如果是 hash 就是無序
ManjusakaL
2020-12-29 22:13:52 +08:00
set 中的元素顺序不是随机的,而是有序的,其顺序依赖其 hash 值。大家通常意义上的无序指不是按照插入序而已
至于每次启动 hash 值都不一样,是因为 Python 3 之后 hash seed 会是一个随机数,具体参见 https://docs.python.org/3/using/cmdline.html#cmdoption-r
guog
2020-12-29 22:17:49 +08:00
这是 dict 无序啊…
ErwinCheung
2020-12-29 22:31:09 +08:00
dict 无序现在版本上来不是说 ok 了的吗
ErwinCheung
2020-12-29 22:32:07 +08:00
Python 3.9.0 (default, Nov 21 2020, 14:55:42)
[Clang 12.0.0 (clang-1200.0.32.27)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> { ch:i for i,ch in enumerate(set('我爱你中国')) }
{'爱': 0, '中': 1, '我': 2, '国': 3, '你': 4}
>>> { ch:i for i,ch in enumerate(set('我爱你中国')) }
{'爱': 0, '中': 1, '我': 2, '国': 3, '你': 4}
>>> { ch:i for i,ch in enumerate(set('我爱你中国')) }
{'爱': 0, '中': 1, '我': 2, '国': 3, '你': 4}
>>> { ch:i for i,ch in enumerate(set('我爱你中国')) }
{'爱': 0, '中': 1, '我': 2, '国': 3, '你': 4}
>>> { ch:i for i,ch in enumerate(set('我爱你中国')) }
{'爱': 0, '中': 1, '我': 2, '国': 3, '你': 4}
>>>
geebos
2020-12-29 23:33:51 +08:00
@superrichman 3.8 之后 dict 默认是有序字典了
yanhh
2020-12-29 23:47:47 +08:00
@ErwinCheung

> 比如我第一次运行,结果是:
> {'国': 0, '爱': 1, '中': 2, '你': 3, '我': 4}
> 重启 Python (必须重启),再运行,结果变为:
> {'爱': 0, '中': 1, '你': 2, '我': 3, '国': 4}
yanhh
2020-12-29 23:51:10 +08:00
重新打开一遍 Python 之后运行结果会变,所以重启脚本再次运行的时候会出问题,结果不一样
Arthur2e5
2020-12-30 00:09:45 +08:00
数学学过吗?一般的集合是有序吗?对顺序有需求请用 OrderedSet,有序集名字放在那。至于是不是每次都给你同一个顺序,那是实现怎么省事怎么来。

@guog @ErwinCheung Python set() 实现和 dict() 是分开的。dict 的顺序是 py 3.7 开始定义的,是 CPython 3.6 的新行为的固化。

* * *

另外,举需要重启例子的请用 python -c 。`python -c "print({ ch:i for i,ch in enumerate(set('我爱你中国')) })"`,cmd 和 sh 都能跑。
yanhh
2020-12-30 00:13:02 +08:00
@Arthur2e5 haha
swulling
2020-12-30 02:40:02 +08:00
set 不保证顺序,一句话就完了,写代码不要依赖任何不保证的东西。

作为兴趣去探索这些行为是可以的,但是代码千万不能依赖。

回来你这个问题,正确的代码应该是 set 后再 sort 为列表
ErwinCheung
2020-12-30 06:51:10 +08:00
莽撞了哈 感谢大佬们 哈哈哈
20015jjw
2020-12-30 07:12:16 +08:00
一次沮丧的看帖
ETiV
2020-12-30 08:29:00 +08:00
看到 hash,想到了两年前参与过的一个项目

线上出了 bug,研发调试了一个通宵都没找到问题,来问我

只能一句句帮着分析、定位问题
结论就是,这哥们把 hash 当 md5 用

这哥们儿广东人,一口广普,
“打日志”说成“打艺妓”,记忆犹新
hello2060
2020-12-30 08:52:24 +08:00
一次沮丧的看帖。。
knightdf
2020-12-30 09:19:42 +08:00
先弄清楚各种数据结构是有序和无序,底层怎么实现的再来说行为奇不奇怪。。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/740130

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX