如何将装饰器用于具有各种参数的函数?

时间:2017-02-26 03:08:58

标签: python python-3.x decorator

仅查看这两行中的前两行。

if not check_abs(_abs_dir):
    return False

这些是函数(只需查看参数和前两行,函数的内容与此问题无关)。

def check_is_file(_abs_dir:str):
    if not check_abs(_abs_dir):
        return False

    return os.path.isfile(
        norm_case_norm_path(_abs_dir)
    )

def create_file_or_folder(_name:str, _abs_dir:str, _is_file:bool):
    if not check_abs(_abs_dir):
        return False

    abs_dir = join(_abs_dir, _name)
    create = False

    if check_existence(abs_dir):
        if _is_file and not check_is_file(_abs_dir):
            create = True
        if not _is_file and not check_is_folder(_abs_dir):
            create = True
    else:
        create = True

    if create:
        if _is_file:
            open(abs_dir, "a").close()
        elif not _is_file:
            os.makedirs(abs_dir)
        return create

    return create

如何制作这些代码

if not check_abs(_abs_dir):
    return False

进入check_is_file()create_file_or_folder()的装饰器?两种功能都有不同的参数和位置。

1 个答案:

答案 0 :(得分:0)

问题是你不知道要检查哪个参数,因为它在你要装饰的每个函数中的不同位置。但是,您可以使用inspect模块检查每个函数的签名,检查以确保它具有指定名称的参数,如果是,则将形式参数名称映射到实际参数值每次调用它时都会传递给函数。

拥有该信息可以确定与目标参数关联的参数的值并根据需要对其进行处理 - 在您的代码和下面的示例中,将调用check_abs()函数的值为传递的_abs_dir参数并检查函数的返回值。

(注意:已修​​改为支持多个命名参数。)

from functools import wraps
import inspect

def check_abs(_abs_dir):
    print('check_abs({!r}) called'.format(_abs_dir))
    return True

def check_abs_param(*params):
    def decorator(function):
        sig = inspect.signature(function)
        if any(map(lambda param: param not in sig.parameters, params)):
            raise NameError('One or more expected parameter names missing from '
                            'declaration "{}{}:".'.format(function.__name__, sig))
        @wraps(function)
        def wrapped(*args, **kwargs):
            bound = sig.bind(*args, **kwargs)  # Map parameter names to argument values.
            for param in params:
                arg_value = bound.arguments[param]  # Get argument value.
                if not check_abs(arg_value):
                    return False if sig.return_annotation == bool else None

            return function(*args, **kwargs)
        return wrapped
    return decorator

# tests
try:
    @check_abs_param('_abs_dir')
    def check_is_bogus(somearg):  # param name does not match decorator
        print('check_is_bogus() called\n')
except NameError as exc:
    print(exc)
    print('NameError exception raised from "check_is_bogus(somearg):" declaration, as '
          'expected.\n')
else:
    print('Error: Expected exception NOT raised from "check_is_bogus(somearg):" '
          'declaration.\n')

@check_abs_param('_abs_dir')
def check_is_file(_abs_dir:str):
    print('check_is_file() called\n')

@check_abs_param('_abs_dir')
def create_file_or_folder(_name:str, _abs_dir:str, _is_file:bool):
    print('create_file_or_folder() called\n')

@check_abs_param('source_dir_abs', 'dest_dir_abs')
def copy_file_or_folder(source_dir_abs:str, dest_dir_abs:str) -> bool:
    print('copy_file_or_folder() called\n')

#check_is_bogus('first_dir')  # can't call, definition failed
check_is_file('second_dir')
create_file_or_folder('name', 'third_dir', 'False')
copy_file_or_folder('source_dir', 'dest_dir')

输出:

One or more expected parameter names missing from declaration "check_is_bogus(somearg):".
NameError exception raised from "check_is_bogus(somearg):" declaration, as expected.

check_abs('second_dir') called
check_is_file() called

check_abs('third_dir') called
create_file_or_folder() called

check_abs('source_dir') called
check_abs('dest_dir') called
copy_file_or_folder() called