我想写一个装饰器,在使用函数之前检查特定的包是否可用。
在下面的例子中,我想写一个装饰器,在使用函数之前检查特定的包是否可用。numpy
不应出现错误,但 non_existent_test_package
应该告知用户,他们需要安装包才能使用这个功能。 这样做的目的是为了减少依赖性。
import numpy as np
import importlib
def check_available_packages(packages):
if isinstance(packages,str):
packages = [packages]
packages = np.asarray(sorted(set(packages)))
def wrapper(func):
installed = list()
for package in packages:
try:
globals()[package] = importlib.import_module(package)
installed.append(True)
except ImportError:
installed.append(False)
installed = np.asarray(installed)
assert np.all(installed), "Please install the following packages to use this functionality:\n{}".format(", ".join(map(lambda x: "'{}'".format(x), packages[~installed])))
return func
return wrapper
@check_available_packages(["numpy"])
def f():
print("This worked")
@check_available_packages(["numpy", "non_existent_test_package"])
def f():
print("This shouldn't work")
# ---------------------------------------------------------------------------
# AssertionError Traceback (most recent call last)
# <ipython-input-222-5e8224fb30bd> in <module>
# 23 print("This worked")
# 24
# ---> 25 @check_available_packages(["numpy", "non_existent_test_package"])
# 26 def f():
# 27 print("This shouldn't work")
# <ipython-input-222-5e8224fb30bd> in wrapper(func)
# 15 installed.append(False)
# 16 installed = np.asarray(installed)
# ---> 17 assert np.all(installed), "Please install the following packages to use this functionality:\n{}".format(", ".join(map(lambda x: "'{}'".format(x), packages[~installed])))
# 18 return func
# 19 return wrapper
# AssertionError: Please install the following packages to use this functionality:
# 'non_existent_test_package'
现在,decorator似乎是在运行时检查包是否存在,而不是在实际调用函数时检查。 我如何调整这段代码?
这将工作
import numpy as np
import importlib
def check_available_packages(packages):
if isinstance(packages, str):
packages = [packages]
packages = np.asarray(sorted(set(packages)))
def decorator(func):
def wrapper():
installed = list()
for package in packages:
try:
globals()[package] = importlib.import_module(package)
installed.append(True)
except ImportError:
installed.append(False)
installed = np.asarray(installed)
assert np.all(installed), "Please install the following packages to use this functionality:\n{}".format(
", ".join(packages[~installed]))
func()
return wrapper
return decorator
@check_available_packages(["numpy"])
def foo():
print("This worked")
@check_available_packages(["numpy", "non_existent_test_package"])
def bar():
print("This shouldn't work")
foo()
bar()
问题是 wrapper()
你的函数正在接受一个参数,而根据定义,它不需要任何参数。所以传递 _
在此声明中 wrapper(_)
会做的工作。
_
是虚的,不能用,但它仍然是东西。IDE也不会抱怨这个未使用的变量。
要想只在函数被调用时执行装饰器,你需要像上面一样使用装饰器工厂。请看 本参考文献 了解更多细节。
如果你想让检查在底层函数被调用时发生,你需要一个额外的层次来包装它。
import functools
def check_available_packages(packages):
if isinstance(packages,str):
packages = [packages]
packages = sorted(set(packages))
def decorator(func): # we need an extra layer of wrapping
@functools.wraps(func) # the wrapper replaces func in the global namespace
def wrapper(*args, **kwargs): # so it needs to accept any arguments that func does
missing_packages = [] # no need for fancy numpy array indexing, a list will do
for package in packages:
try:
globals()[package] = importlib.import_module(package)
except ImportError:
missing_packages.append(package)
assert not missing_packages, "Please install the following packages to use this functionality:\n{}".format(", ".join(missing_packages))
return func(*args, **kwargs) # call the function after doing the library checking!
return wrapper
return decorator
我删除了对 numpy
在我看来完全没有必要,尤其是当你在测试是否有 numpy
安装了,要求它来进行检查是没有意义的。