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 )

5746 次点击
所在节点    Python
65 条回复
billgreen1
2017-11-15 09:26:51 +08:00
@lrxiao numpy 是 Python 的第三方包,可以说是 python 里数值计算类的标准包了。
zhicheng
2017-11-15 09:38:16 +08:00
是谁说 None 不能和 int 比较的啊。

Python 2.7.10 (default, Jul 15 2017, 17:16:57)
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.31)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> None > 0
False
>>> None < 0
True
lrxiao
2017-11-15 09:47:28 +08:00
@billgreen1 我知道。。我在想为什么 numpy 这么干 大概是早发现 早报错的思路。。
lrxiao
2017-11-15 09:48:29 +08:00
@workwonder 那也是你自己的情况。。然后就 evil。。
workwonder
2017-11-15 10:02:30 +08:00
@lrxiao 我承认我的标题有点雷人。发帖前在处理这个问题,把一些 min 的使用场景替换了下,心情比较激动😂

我的诉求能否普遍适用,大家自己斟酌。

我觉得把它看做是 API 风格问题也能说通,大家可以看看数据库怎么处理 NULL 的,有自己的一套主见。

然后,留一个问题大家看怎么解决,就是我为了判断序列是否为空,要提前把迭代器消化成 list,会浪费内存,对于比较长的生成器是不靠谱的。
workwonder
2017-11-15 10:05:25 +08:00
@zhicheng 哥们,我刚确认了下 python2 和 python3 不一样

>>> import sys
>>> sys.version
'3.6.2 (default, Jul 17 2017, 23:14:31) \n[GCC 5.4.0 20160609]'
>>> min([None, 1])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'int' and 'NoneType'
>>> None > 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: '>' not supported between instances of 'NoneType' and 'int'
Chingim
2017-11-15 10:09:50 +08:00
我觉得 max min 居然是全局函数,这点才 evil 吧。[1,2,3].max() 才符合直觉吧
xiaket
2017-11-15 10:47:29 +08:00
@Chingim 发现一只异端,按住啦!我去点火! lol

事实上 sum/all/min/max 这几个全局函数配合 list comprehension 来用效果很好
ruoyu0088
2017-11-15 10:52:45 +08:00
不仔细看文档吗。min, max 都有 default 参数专门对应参数为空的情况。如果希望空列表返回 None:
a = [1, 2, None, 4]
min((v for v in a if v is not None), default=None)
workwonder
2017-11-15 11:10:35 +08:00
@ruoyu0088

心好累!你一定没注意看我的诉求,我完全可以加各种判断,但是场景太多,容易出错。
你写的表达式并没有覆盖 min_ignore_none 所处理的各种情况,但是已经很长了。

你试试:

>>> min(*[], default=1)
>>> min(*[1], default=None)
>>> min(1, default=None)

当然你不会手写这种表达式,但他们在我的场景里面是会出现的
ruoyu0088
2017-11-15 11:29:49 +08:00
@workwonder 我当然知道你的需求不只是 default,不过我看你的那个安全替代的方案,提前把迭代器消化成 list,就能看出你不知道 default 参数。
VYSE
2017-11-15 11:33:20 +08:00
可以重构 None 嘛,PY3 可以直接继承 None,PY2 实现等同 None 的 class,然后加__lt__,__gt__,__cmp__呗
cyrbuzz
2017-11-15 11:38:27 +08:00
可以用 key 和 default 参数实现的简单些。
```
from collections import Iterable

def _min(*args, **kwargs):
__min = lambda y: min(y, default=None, key=lambda x: x if x is not None else float('inf'))

if not args:
return None

if not isinstance(args[0], Iterable):
if len(args) == 1:
return args[0]
return __min(args)

return __min(*args)
```
cyrbuzz
2017-11-15 11:40:33 +08:00
eloah
2017-11-15 13:28:33 +08:00
不爽不要用,只是你自己的需求而已
起个标题搞个大新闻, naive
自己写个__cmp__很难吗
bonfy
2017-11-15 14:35:14 +08:00
不要将滥用说作 evil 啊
自己的情况自己分析解决就行了,evil 啥了啊...
repus911
2017-11-15 18:24:21 +08:00
Python 's builtin min/max is not evil
Daetalus
2017-11-15 19:07:35 +08:00
你不应该把数据清理(清洗)步骤和数据处理步骤混在一起。

对于处理数据的人员来说,进行防御性编程,比如添加“数据校验”并“尽早报错”是常识。你自己武断的去除这两个步骤是不负责任的。追求目前能用就行了,这怎么看都是新手的作风。如果数据传到你这依然有 None,证明前面出了问题,你要么反馈给前面步骤的负责人员。嫌麻烦的话就自己再添加一个数据清理的步骤。

返回 None 是大忌,最好的做法是报错并给出可靠的错误消息。
workwonder
2017-11-15 21:05:33 +08:00
@Daetalus 我不是说了,我的数据逻辑里面有 None 是正常逻辑,表示未知。
你可以认为我的做法没有普适性,但你的言论更加武断。
cy18
2017-11-15 22:01:11 +08:00
同意 @n2ex2 #11 的观点,不符合 min/max 逻辑的使用方式就应该报错而不应该过度包装。
如果自带的 min,max 默认处理 None,这才是 evil 的。可以参考 JS 各种神奇的==结果。

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

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

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

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

© 2021 V2EX