如何在项目中有同名模块时从标准库导入?(如何控制Python查找模块的位置?)

150 投票
6 回答
80439 浏览
提问于 2025-04-16 17:48

在我的项目文件夹里,有一个叫做 calendar 的模块。在代码的其他地方,我想使用标准库里的 Calendar 类。但是当我尝试用 from calendar import Calendar 来导入这个类时,它却从我自己的模块导入了,导致后面出现错误。

我该怎么避免这个问题呢?我需要重命名这个模块吗?

6 个回答

39

其实,解决这个问题挺简单的,但实现起来总会有点脆弱,因为它依赖于Python的导入机制,而这个机制在未来的版本中可能会改变。

(下面的代码展示了如何加载本地模块和非本地模块,以及它们是如何共存的)

def import_non_local(name, custom_name=None):
    import imp, sys

    custom_name = custom_name or name

    f, pathname, desc = imp.find_module(name, sys.path[1:])
    module = imp.load_module(custom_name, f, pathname, desc)
    f.close()

    return module

# Import non-local module, use a custom name to differentiate it from local
# This name is only used internally for identifying the module. We decide
# the name in the local scope by assigning it to the variable calendar.
calendar = import_non_local('calendar','std_calendar')

# import local module normally, as calendar_local
import calendar as calendar_local

print calendar.Calendar
print calendar_local

如果可能的话,最好的解决办法是避免把你的模块命名为标准库或内置模块的名字。

146

其实不需要重命名这个模块。从Python 2.5版本开始,你可以使用 absolute_import 来改变导入模块的方式。

比如说,如果你想导入标准库里的 socket 模块,即使你的项目里有一个叫 socket.py 的文件,也可以这样做:

from __future__ import absolute_import
import socket

在Python 3.x中,这种行为是默认的。虽然Pylint会对这段代码提出警告,但其实这段代码是完全有效的。

8

在Python 3.5及以上版本中,可以使用标准库中的importlib模块,直接从指定的路径导入文件,这样就不需要通过import的查找机制了:

import importlib.util
import sys

# For illustrative purposes.
import tokenize
file_path = tokenize.__file__  # returns "/path/to/tokenize.py"
module_name = tokenize.__name__  # returns "tokenize"

spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)

在实际代码中,file_path可以设置为任何一个.py文件的路径,以便导入;module_name应该是要导入的模块的名称(这是导入系统在后续import语句中查找模块时使用的名称)。后面的代码会用module作为模块的名称;如果想用不同的名称,可以把变量名module改成其他名字。

如果想加载一个包而不是单个文件,file_path应该指向包的根目录下的__init__.py文件。

撰写回答