关于python:当存在同名模块时从内置库导入

Importing from builtin library when module with same name exists

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

我做了一些搜索,似乎找不到解决问题的方法。

  • 当存在同名的本地模块时,如何访问Python中的标准库模块?
  • http://docs.python.org/whatsnew/2.5.html
  • 在python中导入模块时,如何避免一直写模块名?

有没有不必重新命名模块的想法?


不需要更改模块的名称。相反,您可以使用绝对导入来更改导入行为。例如,对于stem/socket.py i,导入socket模块如下:

1
2
from __future__ import absolute_import
import socket

这只适用于Python2.5及更高版本;它是启用行为,这是Python3.0及更高版本中的默认行为。皮林特会抱怨代码,但它是完全有效的。


实际上,解决这个问题相当容易,但是实现总是有点脆弱,因为它依赖于Python导入机制的内部,并且在未来的版本中它们可能会发生变化。

(以下代码显示了如何加载本地和非本地模块以及它们如何共存)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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

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


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

改为重命名模块。

如果你想学习如何劫持内部进口机器,下面是你将要了解如何做到这一点的地方:

  • python 2.7文档的导入模块部分
  • python 3.2文档的导入模块部分
  • PEP 302-新进口挂钩

有时有充分的理由去冒这个险。你给出的理由不在其中。重命名模块。

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

这意味着,如果您在一个地方使用import calendar,则导入的任何模块都将被视为正式名称为calendar的模块,以及在其他任何地方使用import calendar的所有其他尝试(包括在属于主python库的其他代码中),都将获得该日历。

可以使用python 2.x中的imputil模块来设计一个客户导入器,该模块导致从某些路径加载的模块首先查找它们导入的模块,而不是sys.modules或类似的模块。但这是一个非常棘手的事情,而且它在Python3.x中也不会工作。

你可以做一件极其丑陋和可怕的事情,但这并不涉及钩住进口机制。这可能是你不应该做的,但它可能会起作用。它将您的calendar模块转换为系统日历模块和日历模块的混合体。感谢BoazYaniv为我使用的功能提供了框架。把这个放在你的calendar.py文件的开头:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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__])


我想提供我的版本,它结合了博阿兹·亚尼夫和无所不能的解决方案。它将导入模块的系统版本,与以前的答案有两个主要区别:

  • 支持"dot"符号,例如package.module
  • 是对系统模块的import语句的替换,这意味着您只需替换这一行,如果已经对模块进行了调用,则它们将按原样工作。

把这个放在一个可以访问的地方,这样你就可以调用它(我的在我的文件中有我的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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.connection,但我有一个本地包,已经叫做mysql(官方mysql实用程序)。为了从System MySQL包中获取连接器,我替换了这个:

1
import mysql.connector

有了这个:

1
2
3
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__])

。结果

1
2
# 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)

更改导入路径:

1
2
3
4
5
import sys
save_path = sys.path[:]
sys.path.remove('')
import calendar
sys.path = save_path