Python 's builtin min/max is evil

2017-11-14 19:53:37 +08:00
 workwonder

最近频繁被 Python 内建的 min 函数坑害,该函数接受两种调用方式: min(a, b, ...)min([a, b, ...]),然后加上其内部容错一点都没有,一不小心就跑挂了,比如参数中有 None,参数为空等 “意外” 情形。鉴于最近多次受灾,我实现了其安全替代: https://gist.github.com/wonderbeyond/fdd874f37d86534f23109f153cb671a2 (自带 doctest )

5719 次点击
所在节点    Python
65 条回复
xujinkai
2017-11-14 21:08:02 +08:00
早报错,早发现,早治疗
workwonder
2017-11-14 21:14:02 +08:00
> 不符合 min/max 逻辑的使用方式就应该报错而不应该过度包装

@n2ex2 我觉得是否符合逻辑得看场景,比如当我们说 世界首富 的时候考虑到了财富未被统计(p.wealth=None)的人了吗?
我的某些 task.start=None 在系统里面是正常的;我取 `min(t.start for t in tasks)` 的结果作为 project.start 也是既定规则; tasks 是空列表也是正常的,表示没有任务,那么我让 `min([])` 返回 None 表示没有结果也是符合预期的。
所以我觉得我没有过度扭曲 min/max 的语义。
workwonder
2017-11-14 21:16:38 +08:00
> 我觉得我会这样写:min(t.start for t in tasks if t.started)

@dawncold 这样并没有覆盖所有 corner case 呀!你应该没注意看问题。你试试:

>>> min(x for x in [None] if x)
ValueError: min() arg is an empty sequence
n2ex2
2017-11-14 21:19:48 +08:00
@workwonder 既然是看场景的问题,那就不应该写进基础的函数,你有需求自己改。
workwonder
2017-11-14 21:28:05 +08:00
@n2ex2 确实,我实现的 min/max 替代跟我的场景比较契合。
但我觉得更大范围用于一般场景也没啥问题(这里的语义是取最大最小值,至于数据校验并不是该函数的语义,忽略掉 None 也没关系),你可以看作是 API 风格问题,换个语言的标准库,表现风格都差别很大吧。
dawncold
2017-11-14 21:29:16 +08:00
@workwonder 我觉得在执行 min(t.start for t in tasks if t.started)之前你应该判断 tasks 是否为空
workwonder
2017-11-14 21:32:34 +08:00
@dawncold 完全可以,只是在我的场景里面 min 用的比较多,需要判断的也比较多,最近多次意外挂掉。才搞了个包办的 min/max 实现。
dawncold
2017-11-14 21:36:55 +08:00
@workwonder 哦,可能模型有些问题? bad smell
workwonder
2017-11-14 21:41:55 +08:00
对了,我想额外补充一句,我的场景里面要跟其它子系统对接的,我完全没有对其**数据校验**的需要,也完全没有**尽早报错**的考虑,我能提取到需要的属性则提取之,提出不到就设置为 None,然后我这边继续运行我的。
SuperMild
2017-11-14 23:06:34 +08:00
@workwonder 标准库肯定要及早报错的,如果标准库按你那个思路来,才是坑。报错了怎么处理都有自由,不报错让不同需求的人咋办啊。
xrlin
2017-11-14 23:23:18 +08:00
这只是你的需求,None 本就不应该和数值类型比较,抛出异常才是最常见最保险的做法。
e9e499d78f
2017-11-14 23:25:12 +08:00
提前 filter 一下就可以了
lrxiao
2017-11-15 01:45:33 +08:00
ignore None 还叫 安 全 替 代 ...
xiaket
2017-11-15 06:15:59 +08:00
min 只负责判断大小,类型处理还是在接收数据的时候搞清楚吧
billgreen1
2017-11-15 07:43:44 +08:00
@workwonder None 是空值,不是未知值。缺失值我刚才说了是 float("nan"), 是数值类型。而且我觉得不要相信来自别人的输入是第一要义。
workwonder
2017-11-15 08:24:26 +08:00
@billgreen1 你不觉得 float('inf/nan') 是种很晦暗东西吗,我就当不认识它们,也几乎没见人用过,过于奇怪,比如为啥没有 int 类型的等价物。
用 None 表示未知(没有设置)我觉得没毛病,而且类型无关。
billgreen1
2017-11-15 09:11:37 +08:00
@workwonder 能解决问题就好,不用在这上面纠结。多接触自己不了解的东西,觉得 xx 是很灰暗的东西,就当不认识它们,个人感觉这样不好,有点鸵鸟把头埋进沙子里,就当危险不存在一样。

不过我发现了一个问题是自带的 min 真不好用。numpy 里面的 min:
np.min([1,2,np.nan]) ---> nan
np.min([np.nan, 1,2])---> nan

但是自带的 min

min([np.nan, 1, 2]) ---> nan

min([1,2,np.nan]) ---> 1

这里面出现了不一致
workwonder
2017-11-15 09:12:23 +08:00
@xiaket 赞同,我就是这个意思。而且我确实不关心里面是否有 None,或者去掉 None 之后是否只剩空列表。
lrxiao
2017-11-15 09:22:32 +08:00
@billgreen1 因为 IEEE754 说了因为 NaN 本身没有序 除非-NaN +NaN 比较 不知道 numpy 什么意思
workwonder
2017-11-15 09:23:25 +08:00
@lrxiao 我不仅 ignore none,还忽略了空参数。
我觉得挺安全。min 能接受任意长度的序列,空序列除外。我在序列为空时返回 None 是自我保护。此时请不要再说数据校验的事儿(很多人在纠结),这里只比大小,逻辑既然走到这,说明这些情况是合理的。

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

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

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

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

© 2021 V2EX