记一次令人沮丧的调试 - 又:为什么 enumerate(set(...))的顺序有时是随机的? - V2EX
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
yanhh
V2EX    Python

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

  •  
  •   yanhh
    yanhenghuan 2020-12-29 21:44:15 +08:00 4760 次点击
    这是一个创建于 1800 天前的主题,其中的信息可能已经有所发展或是发生改变。

    大大前天写了一个神经网络,跑了三天,到大前天重启电脑的时候才发现状态保存没写好,当时以为是有参数变量忘记存了,或者是存储的时候操作错误,查错查了三个小时都没查出问题(还发了个帖子: 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 问问。

    40 条回复    2020-12-31 08:50:27 +08:00
    just1
        1
    just1  
       2020-12-29 21:48:59 +08:00   1
    set 本来就没顺序啊...
    yanhh
        2
    yanhh  
    OP
       2020-12-29 21:51:19 +08:00
    对啊,就是想不懂为什么 Python 的 set 还有随机性。它生成的时候肯定是一个一个读的吧?为什么要加这个随机性?
        3
    yanhh  
    OP
       2020-12-29 21:52:00 +08:00
    ………………一想就懂了,因为内部实现有哈希
    superrichman
        4
    superrichman  
       2020-12-29 22:06:23 +08:00 via iPhone
    集合没有顺序的
    顺便说一句 dict 的 key 也没顺序
    chchwy
        5
    chchwy  
       2020-12-29 22:08:43 +08:00   1
    看 set 底下的,如果是 tree 那就是有序,如果是 hash 就是序
    ManjusakaL
        6
    ManjusakaL  
       2020-12-29 22:13:52 +08:00 via Android   12
    set 中的元素顺序不是随机的,而是有序的,其顺序依赖其 hash 值。大家通常意义上的无序指不是按照插入序而已
    至于每次启动 hash 值都不一样,是因为 Python 3 之后 hash seed 会是一个随机数,具体参见 https://docs.python.org/3/using/cmdline.html#cmdoption-r
    guog
        7
    guog  
       2020-12-29 22:17:49 +08:00 via Android
    这是 dict 无序啊…
    ErwinCheung
        8
    ErwinCheung  
       2020-12-29 22:31:09 +08:00
    dict 无序现在版本上来不是说 ok 了的吗
    ErwinCheung
        9
    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 enumerae(set('我爱你中国')) }
    {'爱': 0, '中': 1, '我': 2, '国': 3, '你': 4}
    >>>
    geebos
        10
    geebos  
    PRO
       2020-12-29 23:33:51 +08:00
    @superrichman 3.8 之后 dict 默认是有序字典了
    yanhh
        11
    yanhh  
    OP
       2020-12-29 23:47:47 +08:00
    @ErwinCheung

    > 比如我第一次运行,结果是:
    > {'国': 0, '爱': 1, '中': 2, '你': 3, '我': 4}
    > 重启 Python (必须重启),再运行,结果变为:
    > {'爱': 0, '中': 1, '你': 2, '我': 3, '国': 4}
    yanhh
        12
    yanhh  
    OP
       2020-12-29 23:51:10 +08:00
    重新打开一遍 Python 之后运行结果会变,所以重启脚本再次运行的时候会出问题,结果不一样
    Arthur2e5
        13
    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
        14
    yanhh  
    OP
       2020-12-30 00:13:02 +08:00
    @Arthur2e5 haha
    swulling
        15
    swulling  
       2020-12-30 02:40:02 +08:00 via iPad
    set 不保证顺序,一句话就完了,写代码不要依赖任何不保证的东西。

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

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

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

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

    这哥们儿广东人,一口广普,
    “打日志”说成“打艺妓”,记忆犹新
    hello2060
        19
    hello2060  
       2020-12-30 08:52:24 +08:00
    一次沮丧的看帖。。
    knightdf
        20
    knightdf  
       2020-12-30 09:19:42 +08:00
    先弄清楚各种数据结构是有序和无序,底层怎么实现的再来说行为奇不奇怪。。
    Wincer
        21
    Wincer  
       2020-12-30 09:47:40 +08:00 via Android
    楼主这个语句,用字典推导式应该是更好的选择。
    HelloViper
        22
    HelloViper  
       2020-12-30 12:03:57 +08:00
    set 就是无序啊,需要有序用 list 啊。。。。。。。。

    dict 是 2.6 之后默认 orderlist,先 update 进去的会先遍历到
    yanhh
        23
    yanhh  
    OP
       2020-12-30 13:10:43 +08:00
    @hello2060 哈哈哈
    yanhh
        24
    yanhh  
    OP
       2020-12-30 13:14:07 +08:00
    @ETiV 巧了,我也干过这种事,哈哈哈哈,谁知道 Python 的 hash(...)函数是它返回自己的东西,不是求哈希 后来才知道 from zlib import crc32 (我就临时校验个东西,md5 比较慢,所以选了 crc32 )
    iceneet
        25
    iceneet  
       2020-12-30 13:14:27 +08:00
    set 本来就是无序的啊。。 有序的用 list
    yanhh
        26
    yanhh  
    OP
       2020-12-30 13:15:26 +08:00
    @Wincer 不懂,{ ch: i ... for ... } 这个就是字典推导式啊
    yanhh
        27
    yanhh  
    OP
       2020-12-30 13:16:49 +08:00
    @iceneet 主要是它的随机性不是每一次调用都随机,而是每一次重新开 Python 随机,在一个 Python 进程中不是随机的,所以我的程序在一个线程里怎么调用它都一样,我就以为它不是随机的,没意识到它底层是用哈希实现,还以为是 list
    yanhh
        28
    yanhh  
    OP
       2020-12-30 13:17:22 +08:00
    @knightdf 主要是它的随机性不是每一次调用都随机,而是每一次重新开 Python 随机,在一个 Python 进程中不是随机的,我的程序在一个线程里怎么调用它都一样,我就以为它不是随机的。结果就没意识到它底层是用哈希实现,还以为是 list
    Kaciras
        29
    Kaciras  
       2020-12-30 13:27:22 +08:00   1
    6 楼说的是对的,hash 随机化,启动时添加环境变量 PYTHOnHASHSEED=0 即可关闭
    hitmanx
        30
    hitmanx  
       2020-12-30 13:29:49 +08:00
    很多 C++/STL 用久了的人转写 Python 时都会想当然地以为 set 和 map/dict 是基于树结构的 /有序的
    yanhh
        31
    yanhh  
    OP
       2020-12-30 13:38:10 +08:00
    @hitmanx 自己去实现过数据结构的人容易会这样想
    lakehylia
        32
    lakehylia  
       2020-12-30 13:48:50 +08:00
    话说你要保存序列的变量,不是应该用数组 /向量 /列表吗?为什么要用集合?集合在处理序列的变量在运行效率以及空间效率上也不是最优啊。要是我,c++上肯定用 vector,Python 上用 list 。
    fuis
        33
    fuis  
       2020-12-30 13:50:32 +08:00   1
    数据结构是学得有多差。。
    lakehylia
        34
    lakehylia  
       2020-12-30 13:54:19 +08:00
    如果是需要去重,并且保证输出顺序,那肯定要对结果进行排序啊。不然你都不知道它怎么去重的,内在的去重后的顺序也是不确定的。
    zhanglintc
        35
    zhanglintc  
       2020-12-30 14:36:23 +08:00   1
    一次沮丧的看帖
    Wincer
        36
    Wincer  
       2020-12-30 15:32:34 +08:00
    @yanhh 我意思是 { ch:i for i,ch in enumerate(set('我爱你中国')) } 可以直接使用 { ch:i for i,ch in enumerate('我爱你中国') },字典推导式在生成的时候,如果存在重复的 key,后一个会自动覆盖前一个 key 的值,所以这个 set 的引入实际上对解决并无帮助,反而还引入了新的问题。
    yanhh
        37
    yanhh  
    OP
       2020-12-30 20:14:43 +08:00
    @Wincer 原来这样!懂了,这个很好
    yanhh
        38
    yanhh  
    OP
       2020-12-30 20:15:56 +08:00
    @fuis haha
    lithbitren
        39
    lithbitren  
       2020-12-30 23:41:13 +08:00
    dict 在 py3.6 以后就用链表实现有序了,dict.popitem 可以把最后添加的键值对取出来,虽然内存比纯 hash 大,但遍历速度比 set 快。
    araraloren
        40
    araraloren  
       2020-12-31 08:50:27 +08:00
    看了一遍,通通没有说到点上,顺序的 hash 自然可以保证,但是 hash 的随机性 是为了增强安全性,hash 是固定顺序的话很容易被攻击
    这有一篇很久之前的文章,https://cry.nu/perl6/secure-hashing-for-moarvm/#hashing-basics
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     932 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 20:14 PVG 04:14 LAX 12:14 JFK 15:14
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86