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

时间:2011-05-17 13:34:32

标签: python import

情况: - 我的project_folder中有一个名为calendar的模块 - 我想使用Python库中的内置Calendar类 - 当我使用日历导入日历时,它会抱怨,因为它正在尝试从我的模块加载。

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

任何想法,而无需重命名我的模块?

6 个答案:

答案 0 :(得分:129)

无需更改模块的名称。相反,您可以使用absolute_import更改导入行为。例如,使用stem/socket.py我按如下方式导入套接字模块:

from __future__ import absolute_import
import socket

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

答案 1 :(得分:34)

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

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

答案 2 :(得分:13)

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

改为重命名模块。

如果你想学习如何劫持内部进口机械,你可以在这里找到如何做到这一点:

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

如果你采取危险的路径,你将遇到的一个问题是,当你加载一个模块时,它最终会有一个“正式名称”,这样Python就可以避免再次解析该模块的内容。可以在sys.modules中找到模块的“正式名称”到模块对象本身的映射。

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

有可能使用Python 2.x中的imputil module设计客户导入程序,导致从某些路径加载的模块首先查找他们导入的模块,而不是sys.modules。类似的东西。但这是一个非常毛茸茸的事情,无论如何它都无法在Python 3.x中运行。

你可以做一件非常丑陋和可怕的事情,不涉及钩住进口机制。这是你可能不应该做的事情,但它可能会奏效。它将您的calendar模块转换为系统日历模块和日历模块的混合体。感谢Boaz Yanivskeleton of the function I use。把它放在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__])

答案 3 :(得分:4)

可接受的解决方案包含一种现已弃用的方法。

importlib文档here给出了一个很好的例子,说明了从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的路径

答案 4 :(得分:1)

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

  • 支持'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.connection,但我有一个名为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)

答案 5 :(得分:-1)

更改导入路径:

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