如何理解Python的模块查找

时间:2019-08-26 03:37:10

标签: python python-3.x module

我在目录中创建了两个新文件random.py和main.py。代码如下:

# random.py

if __name__ == "__main__":
    print("random")
# main.py

import random

if __name__ == "__main__":
    print(random.choice([1, 2, 3]))

当我运行main.py文件时,程序报告错误。

Traceback (most recent call last):
  File "main.py", line 8, in <module>
    print(random.choice([1, 2, 3]))
AttributeError: module 'random' has no attribute 'choice'

Main.py导入我自己定义的随机模块。

但是,如果我在同一目录中创建一个新的sys.py文件和一个main.py文件,则代码如下:

# sys.py

if __name__ == "__main__":
    print("sys")
# main.py

import sys

if __name__ == "__main__":
    print(sys.path)

当我运行main.py文件时,成功。

main.py导入内置模块sys。

为什么会有如此明显的区别?

脚本文件的目录关系如下:

C:.
    main.py
    random.py
    sys.py

非常感谢您的回答。 原谅我可怜的英语。

2 个答案:

答案 0 :(得分:4)

sys是一个内置模块,这意味着它直接编译成Python可执行文件本身。当Python寻找模块时,内置模块优先于外部文件。标准random模块不是内置的,因此无法得到这种处理。

引用docs

  

sys.modules中找不到命名模块时,Python接下来将搜索sys.meta_path,其中包含元路径查找器对象列表。 按顺序查询这些查找器,以了解他们是否知道如何处理命名模块...

     

Python的默认sys.meta_path具有三个元路径查找器,一个知道如何导入内置模块,一个知道如何导入冻结的模块,另一个知道如何从导入路径导入模块(即基于路径的查找器)。

由于内置模块的查找器位于搜索导入路径的查找器之前,因此将在导入路径上的任何内容之前找到内置模块。

您可以在sys.builtin_module_names中看到Python内置的所有模块的名称的元组。


也就是说,尽管任何内置模块都将优先于从文件加载的模块,但是sys有其自己的特殊处理方式。 sys是Python的基础构建块之一,sys模块的许多设置需要在导入系统运行正常之前足以完成。 sys是在解释器设置过程中通过绕过常规导入系统的方式显式创建的,然后以后sys的导入在sys.modules中找到它而不会遇到任何元路径查找器。

创建sys的方式和位置是一个实现细节,该实现细节因Python版本而异(并且在不同的Python实现中差异很大),但是在CPython 3.7.4代码中,您可以看到它的开始在Python/pylifecycle.c中的755行上。

答案 1 :(得分:3)

tl;博士缓存

sys在其他python模块中有些特殊情况,因为它是在程序启动时无条件加载的(大概是因为其中的许多常量,函数和数据-例如流{{1} }和stdout-由python解释器使用)。正如@ user2357112在另一个答案中指出的,这部分是因为它内置在python可执行文件中,而且还因为运行大量python的核心功能是必要的(请参阅下面的如何加载才能使导入工作) 。 stderr是标准库的一部分,但执行时不会自动加载,这是我们与random之间的主要相关差异

查看python's documentation on the subject可以阐明python如何解决导入问题:

  

在导入搜索期间检查的第一位是sys。此映射充当所有先前导入的模块(包括中间路径)的缓存。
  ...
  导入期间,将在sys.modules中查找模块名称,如果存在,则关联的值是满足导入要求的模块,然后过程完成。但是,如果值为sys.modules,则会引发None。如果缺少模块名称,Python将继续搜索模块。

关于它在哪里寻找模块,您可以从观察到的行为中看到它首先在本地目录中寻找。也就是说,它首先搜索本地目录,然后搜索“通常的位置”。

如何处理ModuleNotFoundError与如何处理sys之间存在差异的原因是缓存-random被缓存了(因此python甚至不检查导入路径),而sys未缓存(因此python会检查导入路径,并在本地导入)。


有几种方法可以更改此行为。

首先,如果必须有一个名为random的本地模块,则可以use importlib to import it in relative or absolute terms,而不会与已经缓存的sys产生歧义。我不知道这会对独立尝试导入sys的其他模块产生怎样的影响,而且您实际上不应该将文件命名为与标准库模块相同。

或者,如果您希望代码在检查本地目录之前检查python的内置模块,那么您应该能够通过修改sys来做到这一点,该命令显示了搜索路径以查找输入的顺序(与sys.path环境变量相同,或任何其他类似特定于语言的环境变量)。 $PATH的第一个元素通常是一个空字符串sys.path,这将导致搜索当前工作目录。因此,您只需将其移动到''的后面,即可最后搜索而不是首先搜索:

sys.path