page contents

用 Python 代码实现 Python 内置函数 max!

这种报错我一般不急着改业务逻辑,先看调用姿势。因为 max 这个函数看着简单,真要自己写一遍,就会发现它里面藏了不少小边界。

attachments-2026-06-VtE2zBp46a28c083bddd8.png脚本跑到一半炸了,报错很干净:

ValueError: max() arg is an empty sequence

这种报错我一般不急着改业务逻辑,先看调用姿势。因为 max 这个函数看着简单,真要自己写一遍,就会发现它里面藏了不少小边界。

不是找最大值那么一句话完事。

比如这几种写法都得考虑:

max([3, 8, 2])

max(3, 8, 2)

max([], default=0)

max(users, key=lambda x: x["score"])

如果自己实现一个 max,第一版很多人会这么写:

def my_max(nums):
    biggest = nums[0]
    for num in nums:
        if num > biggest:
            biggest = num
    return biggest

这代码不能说错,只能说只够应付最顺手的情况。

传进来一个空列表,直接 IndexError。传进来一个生成器,nums[0] 又挂了。想按字段比较,也不支持。更别说 max(1, 2, 3) 这种多参数写法。

我自己写这种工具函数,第一眼会先把入口分清楚:到底是一个可迭代对象,还是多个独立参数。

_MISSING = object()

def dong_max(*values, key=None, default=_MISSING):
    if not values:
        raise TypeError("dong_max expected at least 1 argument")

    if len(values) == 1:
        iterator = iter(values[0])

        try:
            winner = next(iterator)
        except StopIteration:
            if default is _MISSING:
                raise ValueError("dong_max() arg is an empty sequence")
            return default
    else:
        if default is not _MISSING:
            raise TypeError("default only works with a single iterable argument")

        iterator = iter(values[1:])
        winner = values[0]

    winner_score = winner if key is None else key(winner)

    for item in iterator:
        item_score = item if key is None else key(item)

        if item_score > winner_score:
            winner = item
            winner_score = item_score

    return winner

这版代码不长,但几个坑基本都堵住了。

先看普通列表:

print(dong_max([7, 2, 9, 4]))
# 9

再看多参数:

print(dong_max(7, 2, 9, 4))
# 9

这里有个细节,多个参数时,不能再允许 default。

dong_max(1, 2, default=0)

这个写法本身就不该成立。default 是给空迭代对象兜底的,不是给多参数模式用的。

空列表这里也要单独处理:

print(dong_max([], default=0))
# 0

没有 default 就直接抛异常:

print(dong_max([]))

这个地方我不建议偷偷返回 None。因为返回 None 会把问题往后拖,后面再出现 TypeError,你还得回头查是谁塞了个空数据。

还有 key。

业务里真正用 max,很多时候不是比较对象本身,而是比较对象里的某个字段。

orders = [
    {"order_id": "A1001", "amount": 96},
    {"order_id": "A1002", "amount": 128},
    {"order_id": "A1003", "amount": 128},
]

print(dong_max(orders, key=lambda row: row["amount"]))
# {'order_id': 'A1002', 'amount': 128}

注意,返回的是原始对象,不是 key 算出来的值。

这个点很容易写错。有些人会直接返回 item_score,最后拿到一个 128,业务上就丢了订单信息。

还有一个不太显眼的点:如果两个值一样大,max 返回先遇到的那个。

上面 A1002 和 A1003 的 amount 都是 128,结果返回 A1002。这是因为代码里只用了 >,没有用 >=。

if item_score > winner_score:
    winner = item
    winner_score = item_score

别小看这个判断。

有些导入脚本会按时间顺序读文件,同分时保留第一条记录。如果你手一抖写成 >=,结果就变成后面的覆盖前面的,数据看着没错,细节已经变了。

再拿生成器试一下:

def read_latency():
    yield 32
    yield 18
    yield 41
    yield 27

print(dong_max(read_latency()))
# 41

这也是前面不用下标取第一个元素的原因。

生成器没有 nums[0],只能用 next(iterator) 先拿第一条,再继续往后扫。这个写法才接近真实可用的版本。

如果要给日志处理脚本用,大概会这么写:

logs = [
    "order=1001 cost=23",
    "order=1002 cost=87",
    "order=1003 cost=45",
]

def pick_cost(line):
    return int(line.rsplit("=", 1)[1])

slowest = dong_max(logs, key=pick_cost)

print(slowest)
# order=1002 cost=87

这类场景里,max 不是为了炫技,就是少写一堆临时变量。

自己实现一遍之后,再回头看内置 max,它其实做了几件很朴素的事:

接收数据。

拿第一条当临时赢家。

后面一条条比较。

遇到更大的就替换。

空数据该报错报错,该兜底兜底。

看起来普通,但边界处理很干净。

我平时看工具函数,就看这种地方。顺手场景能跑,不算本事。空数据、生成器、key 函数、同值保序、多参数混用,这些都不炸,才算能放进项目里用。

当然,真写生产代码,不建议自己造 max。内置函数已经足够稳。

但自己写一遍,挺值。因为你会发现很多所谓“简单函数”,简单的不是实现,是你平时只用了它最舒服的那一小块。

更多相关技术内容咨询欢迎前往并持续关注好学星城论坛了解详情。

想高效系统的学习Python编程语言,推荐大家关注一个微信公众号:Python编程学习圈。每天分享行业资讯、技术干货供大家阅读,关注即可免费领取整套Python入门到进阶的学习资料以及教程,感兴趣的小伙伴赶紧行动起来吧。

attachments-2022-05-rLS4AIF8628ee5f3b7e12.jpg

 

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
Pack
Pack

2115 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 2228 文章
  3. Pack 2115 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章