如何导入标准库模块而不是本地目录?

11

我有一个本地目录名为“calendar”,其中包含一个“__init__.py”文件。

我想要“import calendar”导入标准库模块calendar,而不是由本地目录定义的模块。

我已经尝试过使用“from __future__ import absolute_import”和更改PYTHONPATH。

我不能只是重命名该目录,这真的有很好的理由。


你可以 import sys 并自己重新排列 sys.path。但是,如果你说你实际上在 sys.path 上有一个目录,其中有一个与内置模块/包同名的包/模块(即,你的 calendar 在路径的顶层),你真的需要将其重命名(实际上还有更好的理由)。没有一般方法来解决顶级名称冲突。 - BrenBarn
1
可能重复:https://dev59.com/bW025IYBdhLWcg3wZlHd - user2443147
3个回答

17
问题在于启动Python时,当前工作目录(根据版本/平台为'''.')始终位于sys.path的顶部。
使用绝对导入没有任何区别——这只是指先查找sys.path,而不是在回退到sys.path之前查找相对导入。
正确的解决方案显然是要么(a)重命名calendar,要么(b)将其移动到其他包的子包中,而不是将其置于顶层。无论您有多好的理由,做正确的事情的理由可能更好。
但如果你必须绕过这个问题,有几件事情可以做。最简单的方法是临时篡改sys.path
syspath = sys.path
sys.path = [path for path in sys.path if path.strip('.')]
import calendar
sys.path = syspath

无论你做什么,这都会引起巨大的问题。当你尝试导入本地包calendar时——即使你是从完全不同的源文件导入——什么也不会发生,因为在sys.modules中已经有一个名为calendar的东西,所以那个源文件将只获得stdlib calendar模块而不是你的包。

因此,在运行时你还需要将它们中的一个重命名并从sys.modules中删除。或许可以这样做:

syspath = sys.path
sys.path = [path for path in sys.path if path.strip('.')]
calmod = sys.modules.get('calendar')
del sys.modules['calendar']
calendar = __import__('calendar')
sys.modules['calendar'] = calmod
sys.path = syspath

而且,根据你的模块运行顺序不同(可能很难预测,甚至不确定),在其他位置也有很大机会需要类似的hackery。

(如果您实际上从未需要导入本地包calendar,那么您就不会遇到这个问题...但是我无法想象您可能有什么好理由...)


9
很失望没有明确的方式可以说“从标准库中导入”,甚至只是修改PYTHONPATH来说“从我安装Python的地方导入”。谢谢您提供的详细信息! - Jonathan Paulson
1
@JonathanPaulson: 但是你不能有两个具有相同限定名称的模块——而且这在整个程序范围内都是全局的—所以这样的语言特性只会让意外触发这种歧义更加容易。如果从我的回答的后半部分不能清楚地理解这一点,那么我的解释可能很糟糕,请告诉我哪里不理解,我会再试一次。 - abarnert
现在我感到失望的是,模块的内部完全限定名称没有以它来自的sys.path元素为前缀。 - Jonathan Paulson
1
@JonathanPaulson:那将是一场巨大的混乱。sys.path通常包含系统范围内的站点包、发行版的分发包、每个用户的站点包、每个pip安装包的目录、每个easy_install安装包的eggfile等等。我看不出这会有什么帮助。如果这些名称仅供内部使用,则无法指定您想要的两者中的哪一个,那么拥有两者有什么用呢?如果它们是可访问的,那么您最终将不得不编写不同的代码,具体取决于bs4是否安装为分发、sys-site、user-site等包... - abarnert
2
@JonathanPaulson:曾经有一个提议,即在3.0版本中摆脱所有内置的顶级包,并将所有内容移动到一个巨大的“std”包中(就像C++98将整个stdlib移动到“std ::”命名空间中一样),这将解决这个问题...但如果你无法想象人们不想要它的所有原因,你可以搜索长时间的争论线程... - abarnert
Python如何确保标准库模块导入其他标准库模块时,真正使用的是标准库模块,而不是本地模块? 假设我们导入了pathlib(来自标准库),它又会执行import os(即非相对导入)。如果本地存在一个os.py文件,那么就会使用该文件。如果你说不要使用这些名称,那没问题,但如果以后标准库中添加了新的模块呢? - calestyo

3
你可以修改 sys.path,导入包后再将 sys.path 恢复为原始值。
import sys
original_path = sys.path
sys.path = original_path[1:]
import calendar
sys.path = original_path

2

正如其他人所说,最好的选择是将日历模块重命名为一个标准库未使用的新模块。

但如果无法实现这一点,则可以创建另一个模块(在syspath中可见),放在本地calendar.py文件所在目录之外。

因此,如果您的层次结构如下所示:

project/
   __init__.py
   app1/
      __init__.py
      calendar.py
      module_in_which_i_want_to_use_python_std_calendar.py

你可以在apps之外创建一个名为std_calendar.py的新模块(放置在calendar.py之外)。在这个文件中,你可以导入日历(这将是标准日历模块)。
from calendar import *

层级结构应该是这样的:

project/
   __init__.py
   std_calendar.py
   app1/
      __init__.py
      calendar.py
      module_in_which_i_want_to_use_python_std_calendar.py

module_in_which_i_want_to_use_python_std_calendar.py 中,您可以使用标准日历:
from project import std_calendar as calendar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接