仅当我的模块需要某个对象时,如何触发另一个模块的延迟导入?
How to trigger a late import of another module only when a certain object is needed from my module?
情况
我想要一个大致如下工作的模块:
# my_module.py
my_number = 17
from other_module import foo
my_object = foo(23)
但是,存在一个问题:安装 other_module
会给某些用户带来问题,并且只有那些想要使用 my_object
的用户才需要 - 而这又只是一小部分用户。我想让那些不需要 my_object
的用户免于安装 other_module
.
因此,我希望仅当从 my_module
导入 my_object
时才会导入 other_module
。换句话说,用户应该能够 运行 以下内容而无需安装 other_module
:
from my_module import my_number
目前我的最佳解决方案
我可以通过包含导入的函数提供 my_object
:
# in my_module.py
def get_my_object():
from other_module import foo
my_object = foo(23)
return my_object
然后用户必须执行如下操作:
from my_module import get_my_object
my_object = get_my_object()
问题
是否有更好的方法来有条件地触发 other_module
的导入?我最感兴趣的是让用户尽可能简单。
方法 A – 清洁解决方案
创建一个新的独立模块并让用户从另一个模块导入对象。例如
from my_module import my_number # regular use
from my_module.extras import my_object # for a small part of the user base
这意味着在您的代码中,您创建了一个模块文件夹 my_module
,其中包含一个 __init__.py
,您可以在其中导入常用内容而不导入 extras
子模块。
如果您不想将 extras
放入 my_module
(更简单),只需在单独的 extras.py
模块中创建 my_object
。
方法 B – 99% 的时间都表明架构不好
您可以使用 importlib.import_module
在 get_my_object
中动态导入模块而不会污染全局 space 并且 比在创建函数的函数中导入更干净副作用,例如使用该导入名称覆盖您的全局变量(参见),但是这通常其他部分的错误编码模式的标志代码。
方法 C – 简单有效
当有些用户可能没有库时,我通常倾向于使用这种简单的模式,因为 Python 3 不鼓励不在顶级的导入:
try:
import other_module
_HAS_OTHER_MODULE_ = True
except:
_HAS_OTHER_MODULE_ = False
def get_my_object():
assert _HAS_OTHER_MODULE_, "Please install other module"
return other_module.foo(23)
我更喜欢 get_my_object()
方法,但是从 Python 3.7 开始,您可以通过定义 module-level __getattr__
function:
# my_module.py
my_number = 17
def __getattr__(name):
if name != 'my_object':
raise AttributeError
global my_object
from other_module import foo
my_object = foo(23)
return my_object
这将尝试导入 other_module
并仅在访问 my_object
时调用 foo
。一些注意事项:
- 它将不会触发尝试通过
my_module
内的全局变量查找访问my_object
。它只会在 my_module.my_object
属性访问或 from my_module import my_object
导入(在后台执行属性访问)时触发。
- 如果您忘记在
__getattr__
中分配全局 my_object
名称,则每次访问时都会重新计算 my_object
。
Module-level __getattr__
在 Python 3.7 之前什么都不做,因此您可能想要执行版本检查并为 Python 3.6 做一些其他事情及以下:
import sys
if sys.version_info >= (3, 7):
def __getattr__(name):
...
else:
# Do something appropriate. Maybe raise an error. Maybe unconditionally
# import other_module and compute my_object up front.
这是一个常见的技巧:
import sys
class MockModule:
def __init__(self, module):
self.module = module
def __getattr__(self, attr):
if attr == 'dependency_required_var':
try:
import foo
return self.module.dependency_required_var
except ImportError:
raise Exception('foo library is required to use dependency_required_var')
else:
return getattr(self.module, attr)
MockModule.__name__ = __name__
sys.modules[__name__] = MockModule(sys.modules[__name__])
dependency_required_var = 0
使用 this PEP,我们可以在 Python 3.7 及更高版本中简单地执行(我们应该能够但我无法让它工作)以下操作:
def __getattr__(attr):
if attr == 'dependency_required_var':
try:
import foo
return dependency_required_var
except ImportError:
raise Exception('foo library is required to use dependency_required_var')
else:
return globals()[attr]
PEP貌似被采纳了,但是PEP的相关pull request貌似关闭了,具体有没有实现我也不确定。
情况
我想要一个大致如下工作的模块:
# my_module.py
my_number = 17
from other_module import foo
my_object = foo(23)
但是,存在一个问题:安装 other_module
会给某些用户带来问题,并且只有那些想要使用 my_object
的用户才需要 - 而这又只是一小部分用户。我想让那些不需要 my_object
的用户免于安装 other_module
.
因此,我希望仅当从 my_module
导入 my_object
时才会导入 other_module
。换句话说,用户应该能够 运行 以下内容而无需安装 other_module
:
from my_module import my_number
目前我的最佳解决方案
我可以通过包含导入的函数提供 my_object
:
# in my_module.py
def get_my_object():
from other_module import foo
my_object = foo(23)
return my_object
然后用户必须执行如下操作:
from my_module import get_my_object
my_object = get_my_object()
问题
是否有更好的方法来有条件地触发 other_module
的导入?我最感兴趣的是让用户尽可能简单。
方法 A – 清洁解决方案
创建一个新的独立模块并让用户从另一个模块导入对象。例如
from my_module import my_number # regular use
from my_module.extras import my_object # for a small part of the user base
这意味着在您的代码中,您创建了一个模块文件夹 my_module
,其中包含一个 __init__.py
,您可以在其中导入常用内容而不导入 extras
子模块。
如果您不想将 extras
放入 my_module
(更简单),只需在单独的 extras.py
模块中创建 my_object
。
方法 B – 99% 的时间都表明架构不好
您可以使用 importlib.import_module
在 get_my_object
中动态导入模块而不会污染全局 space 并且 比在创建函数的函数中导入更干净副作用,例如使用该导入名称覆盖您的全局变量(参见
方法 C – 简单有效
当有些用户可能没有库时,我通常倾向于使用这种简单的模式,因为 Python 3 不鼓励不在顶级的导入:
try:
import other_module
_HAS_OTHER_MODULE_ = True
except:
_HAS_OTHER_MODULE_ = False
def get_my_object():
assert _HAS_OTHER_MODULE_, "Please install other module"
return other_module.foo(23)
我更喜欢 get_my_object()
方法,但是从 Python 3.7 开始,您可以通过定义 module-level __getattr__
function:
# my_module.py
my_number = 17
def __getattr__(name):
if name != 'my_object':
raise AttributeError
global my_object
from other_module import foo
my_object = foo(23)
return my_object
这将尝试导入 other_module
并仅在访问 my_object
时调用 foo
。一些注意事项:
- 它将不会触发尝试通过
my_module
内的全局变量查找访问my_object
。它只会在my_module.my_object
属性访问或from my_module import my_object
导入(在后台执行属性访问)时触发。 - 如果您忘记在
__getattr__
中分配全局my_object
名称,则每次访问时都会重新计算my_object
。 Module-level
__getattr__
在 Python 3.7 之前什么都不做,因此您可能想要执行版本检查并为 Python 3.6 做一些其他事情及以下:import sys if sys.version_info >= (3, 7): def __getattr__(name): ... else: # Do something appropriate. Maybe raise an error. Maybe unconditionally # import other_module and compute my_object up front.
这是一个常见的技巧:
import sys
class MockModule:
def __init__(self, module):
self.module = module
def __getattr__(self, attr):
if attr == 'dependency_required_var':
try:
import foo
return self.module.dependency_required_var
except ImportError:
raise Exception('foo library is required to use dependency_required_var')
else:
return getattr(self.module, attr)
MockModule.__name__ = __name__
sys.modules[__name__] = MockModule(sys.modules[__name__])
dependency_required_var = 0
使用 this PEP,我们可以在 Python 3.7 及更高版本中简单地执行(我们应该能够但我无法让它工作)以下操作:
def __getattr__(attr):
if attr == 'dependency_required_var':
try:
import foo
return dependency_required_var
except ImportError:
raise Exception('foo library is required to use dependency_required_var')
else:
return globals()[attr]
PEP貌似被采纳了,但是PEP的相关pull request貌似关闭了,具体有没有实现我也不确定。