Python 3.12 Special Attribute -__builtins__
__builtins__是 Python 中一个特殊的内置属性,它存在于每个模块的全局命名空间中(通常作为模块的属性)。它提供了对 Python内置命名空间(builtins)的访问,包含了所有内置函数(如print、len)、内置异常(如Exception)、内置常量(如True、False)等。理解__builtins__有助于深入掌握 Python 的作用域机制、动态修改内置行为(尽管不推荐),以及实现自定义执行环境。
1.__builtins__的基本概念
- 定义:
__builtins__是一个模块(通常)或字典,它引用 Python 的内置命名空间。通过它可以访问所有内置名称。 - 类型:在大多数情况下(例如在主模块中),
__builtins__是一个模块对象(builtins模块);在某些上下文(如exec或eval的受限环境)中,它可能是一个字典。 - 位置:每个模块(包括
__main__)的全局命名空间中都有__builtins__属性。它通常由解释器在模块初始化时自动设置。 - 可写性:
__builtins__是可写的,但直接修改它可能导致不可预知的行为(例如破坏内置函数的可用性)。通常不应修改。
示例:
importbuiltinsprint(__builtins__isbuiltins)# True(在大多数情况下)print(__builtins__.printisprint)# True2.__builtins__与builtins模块的关系
Python 标准库中有一个名为builtins的模块,它直接暴露所有内置名称。__builtins__通常就是这个模块的引用。但需要注意,在某些执行环境(如exec或eval中指定了自定义的全局命名空间)中,__builtins__可能被设置为一个字典(即{'print': print, ...}),而不是完整的builtins模块。
验证:
importbuiltinsprint(__builtins__isbuiltins)# 输出 True(在普通 Python 脚本中)3. 不同模块中的__builtins__行为
- 主模块(
__main__):__builtins__指向builtins模块。 - 被导入的模块:解释器在导入模块时,会自动为该模块的全局命名空间设置
__builtins__属性,同样指向builtins模块(除非被覆盖)。 exec或eval的受限环境:如果调用exec(code, globals)时没有提供内置字典,Python 会自动将__builtins__设置为当前内置模块的字典(或模块)。但如果提供了自定义globals且未包含__builtins__,Python 会自动注入一个字典形式的__builtins__。
4. 用途与典型场景
- 动态访问内置函数:通过
__builtins__可以在运行时获取内置函数的引用,例如getattr(__builtins__, 'len')。 - 限制或替换内置函数:在沙盒环境中,可以通过替换
__builtins__中的条目来禁用危险函数(如open、eval),但这种方法不是绝对安全的(因为存在多种绕过方式)。 - 自省:列出所有内置名称(例如
dir(__builtins__))。 - 实现自定义执行环境:在
exec或eval中,通过提供自定义的__builtins__字典来控制可用函数。
5. 示例与逐行解析
示例 1:查看__builtins__的内容
# 打印所有内置名称print(dir(__builtins__))# 获取特定内置函数print(__builtins__.len([1,2,3]))# 3逐行解析:
| 行 | 代码 | 解释 |
|---|---|---|
| 2 | dir(__builtins__) | 列出__builtins__中所有名称,包括len、print、Exception等。 |
| 5 | __builtins__.len([1,2,3]) | 通过__builtins__调用内置函数len。 |
为什么这样写?
- 演示了
__builtins__提供了访问所有内置名称的途径。
示例 2:检查__builtins__的类型
print(type(__builtins__))# <class 'module'> (在普通脚本中)print(__builtins__is__builtins__.__dict__)# False(因为 __builtins__ 是模块,不是字典)注意:在exec环境中,__builtins__可能是一个字典。可以通过isinstance(__builtins__, dict)判断。
示例 3:动态调用内置函数(通过名称字符串)
func_name='max'ifhasattr(__builtins__,func_name):func=getattr(__builtins__,func_name)print(func([1,5,3]))# 5逐行解析:
| 行 | 代码 | 解释 |
|---|---|---|
| 1 | func_name = 'max' | 内置函数名的字符串。 |
| 2 | hasattr(__builtins__, func_name) | 检查__builtins__是否有该名称。 |
| 3 | func = getattr(__builtins__, func_name) | 获取函数对象。 |
| 4 | func([1,5,3]) | 调用内置函数max。 |
为什么这样写?
- 这种方式允许在运行时根据字符串动态调用内置函数,适用于配置驱动或元编程。
示例 4:临时替换内置函数(危险操作,仅作演示)
original_print=__builtins__.printdefmy_print(*args,**kwargs):original_print("Custom print:",*args)# ✅ 使用保存的原始函数__builtins__.print=my_printprint("Hello")# 输出: Custom print: Hello# 恢复__builtins__.print=original_printprint("Hello")# 输出: Hello逐行解析:
| 行 | 代码 | 解释 |
|---|---|---|
| 1 | 保存原始print函数 | 以便后续恢复。 |
| 3-5 | 定义自定义print | 添加前缀。 |
| 7 | 替换__builtins__.print | 所有后续print调用都会使用新函数。 |
| 9 | 调用print | 输出自定义内容。 |
| 11-12 | 恢复原始print | 还原内置行为。 |
为什么这样写?
- 演示了如何修改内置行为,但强烈不建议在生产环境中使用,因为这会影响整个进程,且可能导致难以调试的 bug。通常只在测试或特殊框架中临时使用。
示例 5:在exec中使用自定义__builtins__
custom_builtins={'print':lambdax:print(f"Custom:{x}"),'len':len,}code=""" print("Hello") print(len([1,2,3])) """exec(code,{'__builtins__':custom_builtins})输出:
Custom: Hello 3逐行解析:
| 行 | 代码 | 解释 |
|---|---|---|
| 1-4 | 定义自定义内置字典 | 只提供print和len,其中print被修改。 |
| 6-9 | 待执行的代码 | 使用了print和len。 |
| 11 | exec执行 | 将自定义的__builtins__作为全局字典的一部分传入。 |
为什么这样写?
- 创建受限的执行环境,只允许使用特定的内置函数。常用于代码沙盒或模板引擎中。
6. 底层实现机制(CPython)
在 CPython 中,__builtins__的设置发生在模块初始化和执行代码块时。
- 主模块(
__main__):当 Python 启动时,会创建一个主模块对象,并将其全局字典中的__builtins__设置为builtins模块(即PyEval_GetBuiltins()返回的模块)。 - 导入的模块:在
import语句加载模块时,importlib会创建新的模块对象,并自动将其__builtins__属性设置为builtins模块。这发生在_bootstrap._load_unlocked中。 exec和eval:当执行动态代码时,Python 会根据提供的全局字典决定__builtins__的值:- 如果全局字典中已有
__builtins__键,则使用其值。 - 否则,如果全局字典中包含了
'__builtins__'且值为None,则内置名称将不可用。 - 否则,Python 会自动注入一个
__builtins__条目:如果全局字典的__name__为'__main__',则设置为builtins模块;否则设置为builtins.__dict__(字典形式)。
- 如果全局字典中已有
内置模块 vs 字典:
- 在模块顶层,
__builtins__通常是一个模块对象,因为直接使用模块可以保持完整的命名空间,且效率更高。 - 在动态代码(
exec)中,使用字典更轻量,且方便进行受限控制。
安全性:尽管可以通过替换__builtins__来限制内置函数,但仍存在绕过方式(例如通过().__class__.__bases__[0].__subclasses__()等技巧访问危险函数)。因此,依赖__builtins__的沙盒并不安全。
7. 注意事项与陷阱
- 不要随意修改
__builtins__:修改全局内置函数会影响整个 Python 进程,可能导致其他模块产生意外行为。 __builtins__可能不是模块:在exec或某些特定上下文中,它可能是一个字典。因此,不要假设它有__dict__属性或可以直接导入。builtins模块是更好的选择:如果需要显式访问内置名称,推荐直接导入builtins模块,因为它更明确、更可靠。importbuiltinsprint(builtins.printisprint)# True- 与
__builtin__(Python 2)的区别:在 Python 3 中,__builtins__代替了 Python 2 的__builtin__模块。 - 避免在库代码中依赖
__builtins__:因为库可能被导入到自定义环境中,其中__builtins__可能被修改。
8. 与其他特殊属性的关系
| 属性 | 关系 |
|---|---|
__name__ | 模块名。__builtins__是模块的属性,独立于__name__。 |
__dict__ | 模块的命名空间字典。__builtins__是该字典中的一个键。 |
__file__ | 模块的文件路径,与__builtins__无关。 |
builtins | 标准库模块,通常与__builtins__指向同一个对象。 |
9. 总结
| 特性 | 说明 |
|---|---|
| 角色 | 提供对 Python 内置命名空间的访问 |
| 类型 | 通常是builtins模块(对象),也可能是字典 |
| 位置 | 每个模块的全局命名空间 |
| 访问方式 | __builtins__.function_name或直接导入builtins |
| 可写性 | 可写,但强烈不建议修改 |
| 底层 | 由解释器在模块初始化时自动设置 |
| 典型用途 | 动态访问内置函数、受限执行环境、自省 |
| 最佳实践 | 直接使用import builtins;不要依赖__builtins__进行安全控制 |
掌握__builtins__有助于深入理解 Python 的作用域和内置命名空间的管理。虽然日常开发中很少直接操作它,但在编写框架、调试或实现动态执行环境时,它是一个有用的工具。希望本文能帮助你全面掌握这一特殊属性。
如果在学习过程中遇到问题,欢迎在评论区留言讨论!