我希望创建自己的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)
答案 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__
就足以防止对命名属性的双重访问。