Как правильно использовать декоратор? (TypeError: wrapper () принимает 0 позиционных аргументов, но 1 был задан) - PullRequest
0 голосов
/ 30 апреля 2020

Я пытаюсь написать декоратор, который проверяет наличие определенных пакетов c перед использованием функции.

В приведенном ниже примере numpy не должно выдавать ошибку, но non_existent_test_package должно информировать пользователя о том, что ему необходимо установить пакеты для использования этой функции. Дело в том, чтобы уменьшить зависимости.

ОБНОВЛЕНИЕ, основанное на предложении @ henry-harutyunyan


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'

Теперь кажется, что декоратор проверяет, существуют ли пакеты во время выполнения, а не когда фактически вызывается функция. Как я могу настроить этот код?

Ответы [ 2 ]

1 голос
/ 01 мая 2020

Если вы хотите, чтобы проверка происходила при вызове базовой функции, вам необходим дополнительный уровень обтекания:

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, не имеет смысла требовать его для проверки.

1 голос
/ 30 апреля 2020

Это будет работать

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 также не будут жаловаться на неиспользованную переменную.

Чтобы выполнить декоратор только при вызове функции, вам нужно использовать фабрику декоратора, как указано выше. См. эту ссылку для получения более подробной информации.

...