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
WordTian
V2EX  ›  Python

万万没想到, Python 的字符串裁切函数居然还能有 bug

  •  
  •   WordTian · 2022-12-21 19:54:53 +08:00 · 8026 次点击
    这是一个创建于 463 天前的主题,其中的信息可能已经有所发展或是发生改变。

    吐个槽,大家猜猜 python3 执行这 2 句话都会出现什么?

    print('test.py'.rstrip(".py"))
    print('testy.py'.rstrip(".py"))
    

    这 2 句呢

    print('test6y'.rstrip("6y"))
    print('testy6y'.rstrip("6y"))
    
    51 条回复    2023-01-11 09:29:17 +08:00
    wxf666
        1
    wxf666  
       2022-12-21 20:00:22 +08:00   ❤️ 6
    你可能想用的是 `removesuffix`

    上一个犯过类似错误的帖子:/t/880251
    likelylee
        2
    likelylee  
       2022-12-21 20:02:41 +08:00
    The chars argument is a string specifying the set of characters to be removed. If omitted or None, the chars argument defaults to removing whitespace. The chars argument is not a suffix; rather, all combinations of its values are stripped.

    https://stackoverflow.com/questions/6479676/bug-in-pythons-str-rstrip-function-or-my-own-stupidity
    drizztdu
        3
    drizztdu  
       2022-12-21 20:03:41 +08:00
    zooo
        4
    zooo  
       2022-12-21 20:03:46 +08:00
    都是 test
    WordTian
        5
    WordTian  
    OP
       2022-12-21 20:32:26 +08:00 via Android
    看了楼上的信息,确实是我用错了,我的锅!

    白白为 UC 震惊部打了一回工!
    ClericPy
        6
    ClericPy  
       2022-12-21 21:08:00 +08:00
    好容易有个新帖子 ... https://docs.python.org/zh-cn/3/library/stdtypes.html#str.rstrip

    入参是 chars 是字符集不是字符串... 这跟 strip 入参不是一致的么, 没有区别. 遇事不决官方文档
    lambdaq
        7
    lambdaq  
       2022-12-21 21:42:44 +08:00   ❤️ 1
    这个的确是个很经典的坑。我觉得的确算 py 的设计缺陷!
    weyou
        8
    weyou  
       2022-12-21 21:48:49 +08:00 via Android   ❤️ 4
    @lambdaq 文档写的明明白白的,怎么能算设计缺陷?自己学艺不精罢了
    maggch97
        9
    maggch97  
       2022-12-21 22:41:43 +08:00   ❤️ 1
    简单想一下也知道这个方法一定要设计成这样, 这个函数一般是用来去首尾空的, 空格字符有 n 种. 显然需要传入的是 set 而不是一个固定的 string

    不要动不动就觉得别人的设计有问题. 虽然 python 很屎, 但是还不至于屎在这么简单的地方
    lambdaq
        10
    lambdaq  
       2022-12-21 22:46:38 +08:00   ❤️ 1
    @weyou 反过来,那么只要不是 undefined behavoir ,都怪用户自己笨 or 懒? 语言设计一点责任都没有嘛。

    有没有可能,比如这个方法被叫做 rstripchar() 就不会有这么多误会而且意义更明确?
    lambdaq
        11
    lambdaq  
       2022-12-21 22:51:28 +08:00
    @maggch97 这个设计既然是有问题的。首先入参并不是一个 set ,而就是一个 str 。不信你自己去试 TypeError: rstrip arg must be None or str 。其次谁说的空格都必须是「一个」 char ? 比如我只想去掉末尾的 \r\n 但是保留 \n\r 呢?
    geeglo
        12
    geeglo  
       2022-12-21 22:53:12 +08:00
    别争啦。PHP 也这样。
    Jirajine
        13
    Jirajine  
       2022-12-21 22:53:56 +08:00
    @weyou 设计缺陷就是设计缺陷。character set 和 string 就不该共用一个类型。把这个函数接受的类型改成 char set ,使用的时候 ratrip(set("abcda"))就不会被误解了。
    Trim21
        14
    Trim21  
       2022-12-21 23:04:15 +08:00
    @lambdaq #11 你描述的这种行为已经不再是 python 的 rstrip 或者 js 的 trimEnd 这一类函数的行为了,而是前面提到的新 api removesuffix 了


    @Jirajine #13 字符串的 strip 函数添加的时候 python 还没有内置的 set 类型。
    maggch97
        15
    maggch97  
       2022-12-21 23:08:57 +08:00   ❤️ 7
    @lambdaq char[] as char set 这种用法不常见吗?
    一个 array 就能表达的结构必须要一个 hash 表或者树?
    https://learn.microsoft.com/en-us/dotnet/api/system.string.trim?view=net-7.0#system-string-trim(system-char()) 要不要看看其他语言是怎么设计的?
    全天下的语言都有设计缺陷?
    xuyang2
        16
    xuyang2  
       2022-12-22 08:24:42 +08:00
    @maggch97
    这就是编程语言的"后发优势"啊

    https://docs.python.org/2.7/library/stdtypes.html#str.rstrip
    Changed in version 2.2.2: Support for the chars argument.

    https://www.python.org/downloads/release/python-222/
    Python 2.2.2
    Release Date: Oct. 14, 2002
    krixaar
        17
    krixaar  
       2022-12-22 09:08:10 +08:00   ❤️ 1
    @lambdaq #10 你可以给 str 类加个 remove_all_these_characters_and_their_combinations_from_the_right_side_of_the_string()方法然后提个 PEP 啊,Python 开源的你可以直接去提意见修改对不对,在这里跟我们对线干啥呢?
    snw
        18
    snw  
       2022-12-22 09:26:23 +08:00 via Android   ❤️ 1
    在我看来这函数取名和文档示例都不好,你看人家 PHP 就很好(虽然我更喜欢巨硬家的文档):
    https://www.php.net/manual/en/function.rtrim.php

    这个函数就该叫 rtrim(),主要使用场景是移除字符串末尾多余的非显示字符。PHP 的文档看一眼语法例子就能明白,Python 的文档读一遍一头雾水:我为什么要把 mississippi 变成 mississ ?为什么是从倒数第 2 个 i 开始截而不是从倒数第 1/4 个 i 开始截?
    pepesii
        19
    pepesii  
       2022-12-22 09:36:42 +08:00
    哈哈哈,曾经我也在这儿掉过坑,学艺不精啊
    NoAnyLove
        20
    NoAnyLove  
       2022-12-22 09:38:59 +08:00
    @snw 这文档哪儿有问题了,文字和例子都给得清清楚楚啊。都说了是任意 combination 了,所以右边的`ippi`被移除了,因为用任意数量的 i 、p 、z 可以组成`ippz`,再之后的`s`就不行了,因为`s`不在`ipz`里面,所以就停止了。
    snw
        21
    snw  
       2022-12-22 09:43:19 +08:00 via Android
    @snw
    另外,Python 的文档里只提了省略参数则默认移除 whitespace 。但凡用过其他语言 trim 函数(哪怕是 Excel )的人都知道非显示字符有很多,常见的除了 ascii 第 32 个字符还有 、tab 等,不讲明根本就不知道移除的是哪个(些)。

    连 Excel 的 TRIM 函数文档都会特别提示这一点:
    https://support.microsoft.com/en-us/office/trim-function-410388fa-c5df-49c6-b16c-9e5630b479f9
    snw
        22
    snw  
       2022-12-22 09:50:18 +08:00 via Android
    @NoAnyLove
    因为你知道这函数用途了,再看这个文档就会觉得自然,反过来就很困难。总之对比一下其他语言的文档。
    NoAnyLove
        23
    NoAnyLove  
       2022-12-22 10:24:22 +08:00
    @snw #21 为啥会觉得 whitespace 不清楚,看看 wikipedia 就知道,

    > In computer programming, whitespace is any character or series of characters that represent horizontal or vertical space in typography.

    不清楚 whitespace 具体有哪些的话,查一下文档或者 Google 一下就行了。举个例子,如果有一个创建线程的 API 文档,旁边还专门给出线程的定义,这固然是好的,但是没有也没啥奇怪的。不能因为自己对某个概念不熟悉就觉得是文档做得不好啊。

    #22 我第一次看这个文档的时候也没觉得有啥问题啊。如果要对比 php 的文档的话,感觉你困惑只是不清楚 whitespace 有哪些,而非 rstrip 是怎么工作的。
    JeffGe
        24
    JeffGe  
       2022-12-22 10:31:28 +08:00 via Android
    看标题就猜到是这个问题了,我觉得不管文档写得好不好,至少得先去看一下嘛
    xuminzhong
        25
    xuminzhong  
       2022-12-22 10:35:55 +08:00
    哎,我也学艺不精,平时一直只是用无参来去空格和\n ,没试过多个字符组合,差点也掉坑里了。
    liprais
        26
    liprais  
       2022-12-22 10:36:29 +08:00
    人菜瘾大
    wheeler
        27
    wheeler  
       2022-12-22 10:40:41 +08:00 via iPhone
    golang 是 trimleft 和 trimprefix ,直观吗?
    xuyang2
        28
    xuyang2  
       2022-12-22 11:03:39 +08:00   ❤️ 1
    @wheeler 我觉得还行

    当然如果调用 API 之前,连两句话的 godoc 都懒得看,(其中一句还是介绍别的方法的)
    我也不知道该说啥了

    https://pkg.go.dev/strings#TrimLeft
    func TrimLeft(s, cutset string) string

    TrimLeft returns a slice of the string s with all leading Unicode code points contained in cutset removed.

    To remove a prefix, use TrimPrefix instead.
    lovelylain
        29
    lovelylain  
       2022-12-22 11:12:12 +08:00 via Android
    @Jirajine 如果你用 c++
    julyclyde
        30
    julyclyde  
       2022-12-22 11:17:00 +08:00
    @lambdaq 即使语言设计者有责任
    用语言写软件的那个程序员依然是软件质量的第一责任人
    lovelylain
        31
    lovelylain  
       2022-12-22 11:17:44 +08:00 via Android
    @Jirajine 如果你用 c++,想必也会用错 std::string::find_first_of/find_last_of 并坚持认为是 api 设计缺陷,而非自己不看文档瞎 jb 用的问题咯。
    binxin
        32
    binxin  
       2022-12-22 11:20:32 +08:00   ❤️ 1
    看完讨论,觉得 py 就这么定义的爱用不用。
    看完文档中下面这个 case ,突然有点想说脏话。

    'mississippi'.rstrip('ipz')
    'mississ'
    lambdaq
        33
    lambdaq  
       2022-12-22 12:10:57 +08:00
    @krixaar 你好厉害哦。到 17 楼才参与讨论,大家差不多分队了就开始加入人多的地方并开始指责另外一方对线是吧?

    好好讨论问题不行吗?非得搞饭圈哪一套?要不再来打打拳?

    我觉得一个设计好的语言大部分的时候都是从内置方法名字就能看出其作用而不会被坑。我不觉得 rstrip 符合这个标准。你们觉得 rstrip 没问题,那何苦加入 removesuffix 这个新方法呢。

    你们这么喜欢开地图炮,那么我也开个地图炮,为 rstrip 辩护的就是受虐狂和典型的东亚反思怪。做题家那种以能猜中出题人难题怪题为傲的沾沾自喜心思。https://peps.python.org/pep-0616/ 去看这 Rationale 明明这个东西坑了一大堆人。This repeated issue is evidence that these methods are useful. The new methods allow a cleaner redirection of users to the desired behavior

    语言是拿来用的,不是拿来讲茴香豆几种写法区别的。rstrip 有他的使用场景、历史上有其合理性,不代表它占据这个容易混淆的名字 20 年被很多人误会就是服众的。

    退万步说,不在 rstrip 文档里用 72px 红色加粗字体指出可能的坑就是对不起观众。
    lambdaq
        34
    lambdaq  
       2022-12-22 12:35:02 +08:00
    地图炮开完了,最后还是反思一下。我自己怎么踩坑的呢,当不知道有 strip rstrip lstrip 存在的时候,啥字符串方法都靠自己数下标去 slice 操作,后来看文档发现新世界,居然 py 字符串自带了这么多方法。于是就果断在能用的地方全部用内置方法。写起来真爽。。。rstrip 这玩意迷惑的一点还在于 80% 的字符串,其实能返回和 removesuffix 一样的结果。。。。你还以为你的姿势是正确的。。。直到你去跑野数据的字符串发现一个大循环内部报了个奇怪的错,费劲心思调试出来搞得怀疑人生。。。。

    好了。你们可以喷我了。
    krixaar
        35
    krixaar  
       2022-12-22 15:46:49 +08:00
    @lambdaq #33 因为如果 rstrip 这个方法确实做到了在文档里说明的作用,而使用者单纯根据这个方法的“名称”理解导致和文档不一致,这怎么能是“设计者的责任”?
    换个例子,substr/substring/string.sub 这类看着就是截取字符串的函数,参数 start ,JS 等众多语言里从 0 开始,SQL 里从 1 开始,Oracle SQL 里 0 和 1 都算 1 ,这是 SQL“设计者的责任”?用错了难道不是该 RTFM ?

    “他是错了,难道对方就没有责任吗”这种话整天看太多了,如果你不是这类话术表达的意思,我表示抱歉。如果你就是这个意思,那就我错了,你说的都对,GvR 罪大恶极,Python 语言好死不死用什么英文缩写做方法名,气抖冷!
    lambdaq
        36
    lambdaq  
       2022-12-22 16:44:40 +08:00
    @krixaar

    对线的话就要对准;大概分三条线:

    1. rstrip 单就它本身的引入目的和实现方式有没有设计缺陷?大家也说了,很多语言都这样,所以不算有缺陷
    2. rstrip 作为 py 众多方便的 str 方法之一有没有迷惑性和误导性?我个人觉得有,而且算设计缺陷
    3. 字符串操作方法相关的文档和说明有没有缺陷?我也觉得有问题。

    你要对哪一条?还是要比划打拳?
    krixaar
        37
    krixaar  
       2022-12-22 17:06:24 +08:00
    @lambdaq #36 正确的说“缺陷”方式直到你的回复还都没提到,这里我直接说了吧,rstrip 的问题在于这个 r:
    1. rstrip 的“Right”并没有指出是字符串的“Right”还是方向的“Right”,即,是从字符串的“右侧”开始 strip ,还是从字符串的开头“向右”strip ,因为,从字符串“右侧”往回删除是“向左”删除。( StackOverflow 关于用反了的问题不止一个,这里举个例子: https://stackoverflow.com/questions/71667211/python-string-rstrip-doesnt-strip-specified-characters
    2. 对于 RTL ( Right-to-Left ,从右向左的那些语言环境),此时的 R 和 L 对于这部分可能是土豪国家的程序员来说是不是具有迷惑性。
    所以任何语言中对于 trim/strip 提到 L 和 R 的都有迷惑性,应该改为 Start/End ,例如 JS 还有 dotNet 中 trimStart 和 TrimEnd 这类,或者,就如同 Python 之后出现的 removesuffix 。

    而不是讨论怎么“strip”和跟着的“参数”到底该是什么,讨论这个 L 和 R 啊亲们。
    参数这就单纯是语言设计,Python 如果有设计者的责任,那也是强制缩进而不是一个破内置方法。

    至于说这个方法有没有误导性,你只学 Python 这一门语言是没有误导性的,误导性和迷惑性的引入不还是因为有“很多其它语言”不是这样吗?
    类比我之前的例子,这次扯远点,Lua/Matlab 的数组索引从 1 开始,设计缺陷还是语言 feature ?这比一个 rstrip 迷惑多了,整个和 offset/length 相关的地方都得考虑到底要不要+1 或者-1 了。
    zedx7
        38
    zedx7  
       2022-12-22 17:33:36 +08:00
    @lambdaq 我觉得你可以提个 pr
    lambdaq
        39
    lambdaq  
       2022-12-22 17:36:53 +08:00   ❤️ 1
    @krixaar 所以你压根就不是来讨论问题的。。。。就是来搅着玩的。你说的这个问题,rstrip 是问题最严重的一个嘛? .zfill() 呢? .center() 这些对于非半角文字岂不是问题更多?你把注意力转移开,只能说你一开始想跑题。
    krixaar
        40
    krixaar  
       2022-12-22 17:54:13 +08:00   ❤️ 1
    @lambdaq #39 从一开始讨论这个方法是不是“设计缺陷”就已经跑题了啊,楼主的问题已经解决,文档表述没有错误,不是 bug ,是 feature ,就可以结了啊,这不是有人给上升到“设计缺陷”了吗。
    单一个 rstrip 这个方法的参数和行为和别的语言不同,只能说明它们是不同的语言,而不是这个语言的“设计缺陷”,不然所有的语言类似功能互相之间都是“设计缺陷”,如同 substring 的不同语言表现。
    真正 rstrip 这个“方法名”的“设计缺陷”,就如同我上个回复,指的是 rstrip 这个名字本身,而不是使用者会“猜错它的参数”,本来就不该猜,也不能类比,按照每个语言的文档来。
    我还没提重载这种同方法不同参数都能有不同功能的设计模式,这不更“设计缺陷”嘛。
    还是我第一个回复,你让官方把 rstrip 的方法名给改得详细一些就完事了,“缺陷”就补上了,不用继续讨论了。
    下班了下班了不回了,楼主看到一堆回复估计都懵了🤣。已 block ,请互 block 。
    junkun
        41
    junkun  
       2022-12-22 18:37:17 +08:00
    @snw import string;print(string.whitespace)
    snw
        42
    snw  
       2022-12-22 19:06:55 +08:00 via Android   ❤️ 2
    @junkun
    恭喜,你也被坑了🐶

    string.whitespace 只包含 ascii 范围内的 whitespace ,但 strip()参数留空时默认的 whitespace 是 unicode 范围的。

    比如说以下两个 print 结果是不同的:
    import string
    s="abc\xa0"
    print(s.strip())
    print(s.strip(string.whitespace))
    Jirajine
        43
    Jirajine  
       2022-12-22 22:53:07 +08:00
    @jobmailcn 楼上已经说了,当时 py 还没有 set 类型,所以这就是设计缺陷,缺陷来源于历史包袱。
    cpp 更是一个历史包袱一大堆的缝合怪,没什么好说的。
    WordTian
        44
    WordTian  
    OP
       2022-12-23 00:45:43 +08:00 via Android
    没想到大家的反响这么大,估计也是有不少人被类似问题坑过的。。。
    我发这贴的原因也是排查 bug 排查了半天,发现了问题之后,对这个函数的误用很是愤愤,发个贴吐槽吐槽。
    最后,提示下 removesuffix 是 python 3.9 引入的,有些生产环境的版本可能还不支持。老老实实用数组下标进行裁剪吧
    iseki
        45
    iseki  
       2022-12-23 09:44:08 +08:00 via Android
    不不不,看到这种没明确写出 suffix 的函数,就应该提醒自己,它可能不是 suffix ,去看文档。话说看下文档也不麻烦,ctrl q 一下而已。
    junkun
        46
    junkun  
       2022-12-23 20:03:23 +08:00   ❤️ 1
    @snw 确实,我弄错了,strip 用的是 str.isspace ,而不是 string.whitespace 。
    snw
        47
    snw  
       2022-12-24 01:01:10 +08:00
    ldyisbest
        48
    ldyisbest  
       2022-12-24 12:08:23 +08:00
    hhhh
    llsquaer
        49
    llsquaer  
       2022-12-24 13:31:47 +08:00
    这个很坑..可能是 strip 最初设计问题导致的意外情况..原意应该是去除两端字符,结果搞成了去除两端字符的任意组合.

    strip 是删除左右两边字符串的组合...只要是任意组合匹配上就都删除..

    3.9 版才真正的加入了 removeprefix 删除前缀 , removesuffix 删除后缀 , 这个才是完全匹配上才删除
    snw
        50
    snw  
       2022-12-24 15:41:25 +08:00
    @llsquaer
    str.strip()一开始没有参数,用途只是去除两端的若干空白字符,实现方法是由外到内逐个判断是否为空白字符,这个设计很自然没问题。去除“任意组合”只是描述实际表现出的行为。

    Python 2.3 添加了参数用于自定义字符:
    https://docs.python.org/3/whatsnew/2.3.html
    The strip(), lstrip(), and rstrip() string methods now have an optional argument for specifying the characters to strip.
    acerphoenix
        51
    acerphoenix  
       2023-01-11 09:29:17 +08:00
    有一说一,只看主题,直观的意思跟 op 理解的一样。虽然不 bug,但坑是一定的。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5945 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 43ms · UTC 02:35 · PVG 10:35 · LAX 19:35 · JFK 22:35
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.