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 · 4297 次点击
    这是一个创建于 1457 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    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 还有随机性。它生成的时候肯定是一个一个读的吧?为什么要加这个随机性?
    yanhh
        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 enumerate(set('我爱你中国')) }
    {'爱': 0, '中': 1, '我': 2, '国': 3, '你': 4}
    >>>
    geebos
        10
    geebos  
       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   ·   实用小工具   ·   5622 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 01:34 · PVG 09:34 · LAX 17:34 · JFK 20:34
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.