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


121

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

我进行了几次搜索,但似乎找不到解决我问题的方法。

有任何想法而不必重命名我的模块吗?


24
最佳做法是不要命名模块以隐藏内置模块。
the_drow 2011年

3
解决方案是“选择其他名称”。您不重命名的方法是个坏主意。为什么不能重命名模块?重命名有什么问题?
S.Lott,

确实。因为没有很好的答案,强烈建议不要使用stdlib模块。
ncoghlan 2011年

我避免使用相同的模块名称,因为解决方案似乎比其价值更大。谢谢!
树枝

9
@the_drow此建议无法扩展,纯粹和简单。PEP328欣然承认这一点。
康拉德·鲁道夫2012年

Answers:


4

公认的解决方案包含一种现已弃用的方法。

这里的importlib文档为直接从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


像魅力一样工作...在开发库时使用它进行测试,因此我的测试始终使用开发版本,而不是已发布(和已安装)的版本。在windows 10,我不得不路径写信给我的模块,像这样:file_path=r"C:\Users\My User\My Path\Module File.py"。然后我module_name像已发布的模块一样进行调用,以使我拥有完整的工作脚本,将其摘录下来,可以在其他个人电脑上使用
Luke Savefrogs

141

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

from __future__ import absolute_import
import socket

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


4
这似乎是我的正确答案。有关更多信息,请参见2.5 changelogPEP328
彼得·恩内斯

5
这是正确的解决方案。不幸的是,当启动包中的代码时,它不起作用,因为那样一来就无法识别该包,并且本地路径以开头PYTHONPATH另一个问题显示了如何解决该问题。
康拉德·鲁道夫2012年

5
这是解决方案。我检查了Python 2.7.6,这是必需的,但它仍然不是默认值。
Havok


1
然后,不要将您的模块命名为与内置模块冲突的模块。
Antti Haapala

38

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

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


这将如何与sys.modules加载本地模块的交互以及随后的尝试?
2011年

@Omnifarious:它将使用其名称将模块添加到sys.modules中,这将防止加载本地模块。您始终可以使用自定义名称来避免这种情况。
波亚兹·亚尼夫

@Boaz Yaniv:您应该使用本地日历的自定义名称,而不是标准名称。其他Python模块可能会尝试导入标准模块。而且,如果这样做,则可以实现的是基本上重命名本地模块,而不必重命名文件。
2011年

@Omnifarious:您可以选择任何一种方式。其他一些代码可能会尝试加载本地模块,并得到相同的错误。您将不得不做出让步,由您决定要支持哪个模块。
波亚兹·亚尼夫

2
谢谢你的波阿斯!尽管您的代码段较短(和文档很短),但我认为重命名该模块要比拥有一些可能会在将来使人(或我本人)感到困惑的hacky代码更容易。
树枝

15

解决此问题的唯一方法是自己劫持内部进口设备。这并不容易,而且充满了危险。您应不惜一切代价避免使用圣杯形的信标,因为其危险性很高。

重命名您的模块。

如果您想学习如何劫持内部导入机制,可以在这里找到如何执行此操作的方法:

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

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

这意味着,如果您 import calendar在某个地方,那么导入的任何模块都将被视为具有官方名称的模块,calendar并且所有其他尝试到import calendar任何其他地方的模块(包括在主Python库的其他代码中)都将获得该日历。

可能可以使用 Python 2.x中 imputil模块模块导致从某些路径加载的模块以不同于sys.modulesfirst或类似的方式查找要导入的模块。但这是一件非常繁琐的事情,而且无论如何在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__])

imputil被视为已弃用。您应该使用imp模块。
波亚兹·亚尼夫

顺便说一下,它与Python 3完全兼容。而且根本没有那么多毛。但您应该始终意识到,依赖python以一种方式处理路径或以这种顺序查找模块的代码可能迟早会损坏。
波亚兹·亚尼夫

1
是的,但是在这种孤立的情况下(模块名称冲突),钩住导入机制实在是太过分了。而且由于它有毛并且不兼容,所以最好不要管它。
波阿斯·亚尼夫

1
@jspacek到目前为止,还不错,但是只有在使用PyDev的调试器时才会发生冲突,而不是在常规使用中发生。并确保您检查最新的代码(github中的URL),因为它与上面的答案
有所不同

1
@jspacek:这是一个游戏,而不是一个库,所以就我而言,向后兼容性根本不是问题。而且,仅当使用通过PyDev IDE(使用Python的codestd模块)运行时,名称空间冲突才会发生,这意味着只有一小部分开发人员会遇到这种“合并黑客”的问题。用户完全不会受到影响。
MestreLion

1

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

  • 支持“点”表示法,例如。包模块
  • 是系统模块上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)

-2

更改导入路径:

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

这将不起作用,因为执行此操作后,如果不亲自修改导入机制,将无法导入本地模块。
2011年

@Omnifarious:这是一个不同的问题,您可以使用第三个执行日历导入*的模块来解决该问题。
linuts 2011年

不,这可能不起作用,因为python在中缓存模块名称sys.modules,并且不会再次导入具有相同名称的模块。
波亚兹·亚尼夫
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.