前言「内建函数」是 built-in function 的另一种常见译法和「内置函数」指的是同一个东西不是第三类神秘存在。网上有些文章把「内置」「内建」「标准库」「系统函数」并列成四个层级那是不准确的说法——Python 里只有三层内置命名空间里的名字、标准库模块里的函数、以及你自己写的代码。很多初学者对内置函数有误解觉得它们带某种编译期魔法。真相要朴素得多内置函数就是解释器启动时预置在builtins模块里的一批普通名字任何作用域都能不带import直接引用它们仅此而已。理解这一点之后一堆现象就顺了为什么能用len当变量名然后把自己的len用崩、为什么dir(builtins)能看到完整清单、为什么把builtins打补丁能全局生效。本文从名字从哪来讲到它们怎么调用你的代码配可运行示例和一批踩过才知道的坑。示例基于 Python 3.8。一、名字从哪里来builtins 模块与 LEGBPython 找名字时按 LEGB 顺序Local局部→ Enclosing闭包→ Global模块全局→ Builtins内置。内置那一层就是builtins模块提供的。# 适用于 Python 3.8import builtinsprint(builtins.len([1, 2, 3])) # 3通过模块也能拿到print(len is builtins.len) # True是同一个对象names [n for n in dir(builtins) if not n.startswith(_)]print(len(names))print(sorted(names)[:12])想验证一个名字是不是内置的可以检查它是否在builtins.__dict__里# 适用于 Python 3.8import builtinsdef describe(name):obj getattr(builtins, name, None)return f{name}: {obj!r}print(describe(sum))print(sum in builtins.__dict__) # True关于__builtins__注意是两条下划线包着的复数形式它是每个模块全局命名空间里自动出现的一个名字内容通常是builtins模块或其字典。具体是模块还是字典属于 CPython 的实现细节不要依赖。要访问内置命名空间稳妥做法永远是显式import builtins而不是去读__builtins__。二、它们长什么样C 实现与类型对象内置函数大多是 C 实现的所以它们在 Python 层面没有可读的源码。看一个名字指向什么最直接的办法是用type()# 适用于 Python 3.8print(type(len)) # class builtin_function_or_methodprint(type(print)) # class builtin_function_or_methodprint(type(42)) # class int有意思的是map、filter、zip、enumerate、reversed这几个名字在 CPython 里指向的是类型对象而不是builtin_function_or_method# 适用于 Python 3.8print(type(map)) # 在 CPython 中输出 class mapprint(type(zip)) # class zip这是 CPython 的实现方式官方文档统一把它们称作内置函数built-in function所以写代码时不必纠结这个分类知道type()能查就行。有些内置函数还有属性可读比如len.__name__、len.__doc__。想批量了解用help()# 适用于 Python 3.8help(len)help()会打印出签名和文档字符串。注意签名里如果出现/表示它前面的参数是仅位置参数positional-only不能按关键字传如果出现*表示它后面的参数是仅关键字参数。三、内置函数如何调用你的代码数据模型协议这是弄懂的关键内置函数本身不做所有事它们把工作转交给对象的协议方法dunder method。你写的代码实际被调用的方法len(obj)obj.__len__()obj in containercontainer.__contains__(obj)iter(obj)obj.__iter__()str(obj)obj.__str__()缺省回退到__repr__bool(obj)obj.__bool__()缺省回退到__len__obj[key]obj.__getitem__(key)hash(obj)obj.__hash__()abs(obj)obj.__abs__()round(obj, n)obj.__round__(n)# 适用于 Python 3.8class Bag:def __init__(self, items):self.items list(items)def __len__(self):return len(self.items)def __contains__(self, x):return x in self.itemsb Bag([apple, pear])print(len(b)) # 2走 __len__print(pear in b) # True走 __contains__print(bool(Bag([]))) # False没有 __bool__ 时回退到 __len__所以自定义类能不能被len()、in、for支持取决于你有没有实现对应协议内置函数只是在合适的时机去调用它们。四、惰性还是立即返回类型决定用法内置函数里有相当一批是惰性的——它们返回迭代器而不是容器。这是最容易出 bug 的地方因为迭代器只能消费一次。内置函数返回惰性能否重复遍历len、sorted、sum、min、max、list、tuple具体结果否可以map、filter、zip、enumerate、reversed迭代器是不可以rangerange对象惰性求值可以本身是序列# 适用于 Python 3.8nums [1, 2, 3, 4]evens filter(lambda x: x % 2 0, nums)print(list(evens)) # [2, 4]print(list(evens)) # []迭代器已被消费完# range 是特例它是惰性求值的序列不是一次性迭代器r range(3)print(list(r), list(r)) # [0, 1, 2] [0, 1, 2]range是序列支持len()、下标、in而map/filter/zip/enumerate/reversed是一次性迭代器。这个区别决定了你要不要先list(...)物化。五、覆盖与打补丁builtins 的两种用法既然内置函数只是名字就有两件事可以做在局部作用域遮蔽它或者全局替换它。# 适用于 Python 3.8# 1) 遮蔽作用域内看不到内置的那个了sum 0# print(sum([1, 2])) # TypeErrorsum 现在是 0不是函数# 2) 全局打补丁谨慎使用会影响整个进程import builtinsoriginal_open builtins.opendef loud_open(*args, **kwargs):print(正在打开:, args[0] if args else )return original_open(*args, **kwargs)builtins.open loud_openwith open(__file__, encodingutf-8) as f:f.readline()builtins.open original_open # 用完立刻还原全局打补丁适合调试、埋点、教学演示不要在生产库的导入期做这件事它会让别人的代码行为变得难以预期。常见坑点坑 1用内置函数名当变量名。❌list [1, 2, 3]、len 5、id abc—— 之后整个作用域里list()、len()、id()全废。✅ 换名items、length、ident。用编辑器高亮能很快发现这类遮蔽。坑 2把惰性函数当容器用。❌if map(f, xs):—— 迭代器对象本身恒为真条件永远成立。✅if any(map(f, xs)):或先result list(map(f, xs))再判断。坑 3对着已消费的迭代器再遍历。❌z zip([1, 2], [3, 4])print(list(z)) # [(1, 3), (2, 4)]print(list(z)) # []✅ 需要多次用就先pairs list(z)。坑 4把__builtins__当稳定接口。❌ 直接读__builtins__并假设它是字典或假设它是模块在不同执行环境下它可能是另一种。✅ 显式import builtins通过builtins.__dict__或getattr(builtins, name)访问。坑 5忽略签名里的/和*。❌ 把sorted的key按位置传sorted(data, lambda x: x[1])会抛TypeError因为key与reverse都是仅关键字参数。✅ 写成sorted(data, keylambda x: x[1])不确定时先help(函数名)看清签名里/其前是仅位置参数与*其后是仅关键字参数的位置。坑 6以为sorted和sort一样能原地改。❌data data.sort()——sort原地排序并返回Nonedata变None。✅data sorted(data)或data.sort()不接返回值。坑 7全局打补丁后不还原。❌ 在库的__init__.py里替换builtins.open却不恢复污染整个进程。✅ 只在调试时临时替换并在finally里还原原对象。坑 8以为内置函数比手写循环总是更快。❌ 不加判别地把所有for都换成maplambda还期望变快——额外的函数调用开销有时反而更慢。✅ 优先看可读性要比较性能就自己用timeit实测不要采信任何没测过的倍数说法。总结要点结论名字来源builtins模块解释器启动时注入查找顺序LEGB局部 → 闭包 → 全局 → 内置本质普通名字大多 C 实现type()可查与你的代码的关系通过__len__、__iter__等协议方法回调返回类型一部分立即返回结果一部分返回一次性迭代器可覆盖作用域内遮蔽、全局builtins打补丁慎用把内置函数理解成预置在builtins里、按 LEGB 查得到的普通名字是打通所有相关疑问的钥匙。剩下的两件事就是记住哪些函数返回的是只能用一次的迭代器以及记不清签名时用help()去查而不是凭印象写参数。Python 2 与 Python 3 的内置函数有一处断崖式差异Python 2 的print是语句、raw_input是读取输入的函数、xrange存在、unicode是内置类型这些在 Python 3 中分别是函数print()、input()、range、str。Python 2.7 已于 2020 年 1 月 1 日停止维护本文一律使用 Python 3 的写法。