导入尚不存在的模块

时间:2019-06-27 08:05:26

标签: python python-3.x

我希望创建自己的amoffat's sh模块的变体,在其中它可以从用户的UNIX路径中导入几乎任何命令,例如:

from sh import hg

但是,我很难找到一种方法来拦截/覆盖python自己的import [...]from [...] import [...]。在这一点上,我只需要一种方法至少可以获取from导入对象的[名称],到那时,我可以简单地setattr()partial()从那里开始,我希望。但是,目前我完全不知道如何执行此操作,因此没有任何代码可显示。

我要做什么的要点:

from test import t # Even though "t" doesn't exist in the module (yet)

希望对完整代码有任何帮助!


最终答案,合并后:

def __getattr__(name):
    if name == '__path__': raise AttributeError
    print(name)

2 个答案:

答案 0 :(得分:4)

如果您使用的是Python 3.7 + ,PEP-562实际上是一种简单的方法,可以在模块级别定义__getattr__

def __getattr__(name):
    if name == "t":
        return "magic"
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

还有一个函数__dir__,您可以定义该函数来声明内置dir()对模块名称的说明。


sh所做的工作更为复杂,因为他们希望支持3.7以下的版本:修改sys.modules并用一个伪装成模块的特殊对象替换该模块。

答案 1 :(得分:1)

正如@ L3viathan指出的那样,这很容易starting with Python 3.7:只需在您的特殊模块中定义一个__getattr__函数即可。因此,例如,您可以创建一个“ echo”模块(仅返回您请求的对象的名称),如下所示:

echo.py(Python> = 3.7)

def __getattr__(name):
    return name

然后您可以像这样使用它:

from echo import x
print(repr(x))
# 'x'

在Python的早期版本中,必须对模块进行子类化,如PEP-562中所暗示。这在Python 3.7中也适用。

echo.py(Python> = 2)

import sys, types

class EchoModule(types.ModuleType):
    def __getattr__(self, name):
        return name

sys.modules[__name__] = EchoModule(__name__)

您将使用与3.7版本相同的方式:from echo import something

更新

由于某种原因,Python尝试为每个from echo import <x>调用两次检索属性。加载模块时,它还会调用__getattr__('__path__')。在以下情况下,可以通过以下代码避免副作用:

echo.py(仅定义一次属性)

import sys, types

class EchoModule(types.ModuleType):
    def __getattr__(self, name):
        # don't define __path__ attribute
        if name == '__path__':
            raise AttributeError
        print("importing {}".format(name))
        # create the attribute in case it's required again
        setattr(self, name, name)
        # return the new attribute
        return getattr(self, name)

sys.modules[__name__] = EchoModule(__name__)

每次导入先前未使用的属性(类似于collections.defaultdict时),此代码都会在echo模块中创建一个属性。然后,如果Python稍后尝试再次导入相同的属性,它将直接从模块中拉出它,而不是调用__getattr__(对象属性为normal behavior)。

这里还有一些代码可以避免设置虚假的__path__属性;这也避免了在请求__path__时运行您的代码。注意,这实际上可能是最重要的部分。在我进行测试时,只需将AttributeError的值提高到__path__就足以防止对命名属性的双重访问。