当存在具有相同名称的模块时,从内置库导入

情况: - 在我的 project _ 文件夹中有一个名为日历的模块 - 我想使用 Python 库中的内置 Calendar 类 - 当我使用从日历导入日历它抱怨,因为它试图从我的模块加载。

我已经做了一些搜索,我似乎无法找到一个解决我的问题。

有什么不用重命名模块的主意吗?

73747 次浏览

解决这个问题的唯一方法是自己劫持内部进口机器。这并不容易,而且充满危险。你应该不惜一切代价避开圣杯形状的灯塔,因为这太危险了。

改为重命名您的模块。

如果你想学习如何劫持内部进口机器,以下是你可以了解如何做到这一点:

有时陷入这种危险是有充分理由的。你给出的理由不在其中。重命名模块。

如果您选择了这条危险的路径,那么您将遇到的一个问题是,当您加载一个模块时,它将以一个“正式名称”结束,这样 Python 就可以避免再次解析该模块的内容。在 sys.modules中可以找到模块的“正式名称”到模块对象本身的映射。

这意味着,如果您在一个地方使用 import calendar,那么导入的任何模块都会被认为是正式名称为 calendar的模块,其他地方对 import calendar的所有尝试(包括 Python 主库中的其他代码)都将获得该日历。

可以使用 Python 2.x 中的 简易模块简易模块简易模块设计一个客户导入程序,使从特定路径加载的模块查找它们正在导入的模块,而不是先导入 sys.modules或类似的内容。但这是一件非常棘手的事情,而且无论如何在 Python 3. x 中都无法工作。

有一个非常丑陋和可怕的事情你可以做,不涉及挂钩导入机制。这是你可能不应该做的事情,但它可能会工作。它将 calendar模块转换为系统日历模块和日历模块的混合体。感谢 Boaz Yaniv我使用的函数框架。把这个放在 calendar.py文件的开头:

import sys


def copy_in_standard_module_symbols(name, local_module):
import imp


for i in range(0, 100):
random_name = 'random_name_%d' % (i,)
if random_name not in sys.modules:
break
else:
random_name = None
if random_name is None:
raise RuntimeError("Couldn't manufacture an unused module name.")
f, pathname, desc = imp.find_module(name, sys.path[1:])
module = imp.load_module(random_name, f, pathname, desc)
f.close()
del sys.modules[random_name]
for key in module.__dict__:
if not hasattr(local_module, key):
setattr(local_module, key, getattr(module, key))


copy_in_standard_module_symbols('calendar', sys.modules[copy_in_standard_module_symbols.__module__])

更改导入路径:

import sys
save_path = sys.path[:]
sys.path.remove('')
import calendar
sys.path = save_path

实际上,解决这个问题相当容易,但是实现总是有点脆弱,因为它依赖于 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

如果可能的话,最好的解决方案是避免使用与标准库或内置模块名称相同的名称来命名模块。

不需要更改模块的名称。相反,您可以使用 Absolal _ import 来更改导入行为。例如,对于 STEM/socket.py,我导入的套接字模块如下:

from __future__ import absolute_import
import socket

这只适用于 Python 2.5及以上版本; 它启用了 Python 3.0及以上版本中默认的行为。Pylint 会抱怨代码,但它是完全有效的。

我想提供我的版本,是 Boaz Yaniv 和 Omnifarian 的解决方案的结合。它将导入一个模块的系统版本,与以前的答案有两个主要区别:

  • 支持‘ dot’表示法,例如 package.module
  • 是对系统模块上的 import 语句的一个插入式替换,这意味着您只需要替换那一行,如果已经对模块进行了调用,那么它们将按原样工作

把它放在可访问的地方,这样你就可以调用它(我的在我的 _ _ init _ _. py 文件中) :

class SysModule(object):
pass


def import_non_local(name, local_module=None, path=None, full_name=None, accessor=SysModule()):
import imp, sys, os


path = path or sys.path[1:]
if isinstance(path, basestring):
path = [path]


if '.' in name:
package_name = name.split('.')[0]
f, pathname, desc = imp.find_module(package_name, path)
if pathname not in __path__:
__path__.insert(0, pathname)
imp.load_module(package_name, f, pathname, desc)
v = import_non_local('.'.join(name.split('.')[1:]), None, pathname, name, SysModule())
setattr(accessor, package_name, v)
if local_module:
for key in accessor.__dict__.keys():
setattr(local_module, key, getattr(accessor, key))
return accessor
try:
f, pathname, desc = imp.find_module(name, path)
if pathname not in __path__:
__path__.insert(0, pathname)
module = imp.load_module(name, f, pathname, desc)
setattr(accessor, name, module)
if local_module:
for key in accessor.__dict__.keys():
setattr(local_module, key, getattr(accessor, key))
return module
return accessor
finally:
try:
if f:
f.close()
except:
pass

例子

我想导入 mysql.connect,但是我有一个本地包,它已经被称为 mysql (官方的 mysql 实用程序)。因此,为了从系统 mysql 包中获取连接器,我替换了以下内容:

import mysql.connector

用这个:

import sys
from mysql.utilities import import_non_local         # where I put the above function (mysql/utilities/__init__.py)
import_non_local('mysql.connector', sys.modules[__name__])

结果

# This unmodified line further down in the file now works just fine because mysql.connector has actually become part of the namespace
self.db_conn = mysql.connector.connect(**parameters)

已被接受的解决方案包含一种现在已不再推崇的方法。

Import lib 文档 给你给出了一个很好的例子,说明了从 python > = 3.5的文件路径直接加载模块的更合适的方法:

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)

所以,你可以装任何。Py 文件,并将模块名称设置为您想要的任何值。因此,只需将 module_name调整为您希望模块在导入时具有的任何自定义名称。

要加载包而不是单个文件,file_path应该是包的根 __init__.py的路径