Python装饰器之函数参数检查

2015-12-18  本文已影响1809人  YWMZ

关于python decorator, 这里就不累述了,推荐左耳朵耗子的博文Python修饰器的函数式编程。下面主要分享自己在工作中遇到的一个案例。

文章目录

问题背景

最近一段时间在做图像类应用的测试,其中涉及到图像算法采用opencv python bindings来实现,比如下面这几个函数接口。

def get_perceptual_hash(source):
    """计算图像的Perceptual Hash(http://en.wikipedia.org/wiki/Perceptual_hashing)
    """
    # 省略若干代码

def diff_image(base_image, compare_image, diff_image_prefix):
    """生成base_image, compare_image的差异图片
    """
    # 省略若干代码

团队内部的测试代码默认采用utf8编码的字符串,或者是unicode。在locale为中文情况下,opencv python bindings的API只接受gbk格式的文件路径字符串。那么问题就来了,我需要在读取图像文件做算法之前,对source/base_image/compare_image这些参数一个个做类型转换转成gbk编码?

# 非装饰器版本
def get_perceptual_hash(srouce):  
    if isinstance(source, unicode):
        source = source.encode('gbk')
    elif isinstance(srouce, str):
        source = source.decode('utf8').encode('gbk')
    else:
        raise Exception('')

    # 省略若干代码

def diff_image(base_image, compare_image, diff_image_prefix):
    for image in [base_image, compare_image]:
        if isinstance(image, unicode):
            image = image.encode('gbk')
        elif isinstance(image, str):
            image = image.decode('utf8').encode('gbk')
        else:
            raise Exception('')
    # 省略若干代码

这样的代码显得繁琐,也不容易维护。可以写个【装饰器函数】,接受【被装饰函数】的参数名作为【装饰器函数】的参数,在真正的算法逻辑执行前完成参数的类型转换。嗯,用代码描述是这样的。

@convert_arguments_to_unicode(['param_2'])
def func(param_1, param_2):
  pass

上面的例子中,函数func的逻辑真正执行前,param_2已经完成了类型转换。

解决方案

问题的难点,怎么从被装饰函数的参数列表(*args, **kwargs)中,找到指定名字的参数并完成修改。在python语言中,函数也是对象,那么这个对象有哪些属性可以来利用呢?经过一番搜索,在官网上找到了答案。

Data model

>>> def func(param_1, param_2):
...     print "param_1 is ", param_1
...     print "param_2 is ", param_2
...
>>> func.func_code.co_argcount
2
>>> func.func_code.co_varnames
('param_1', 'param_2')

理解了上面这段示例,就可以动手写装饰器函数了,暂时只处理函数的匿名参数*args

def convert_arguments_to_unicode(arguments):
    def make_wrapper(func):
        def wrapper(*args, **kwargs):
            # 拿到被装饰函数的参数名列表
            code = func.func_code
            names = list(code.co_varnames[:code.co_argcount])
            # args类型是tuple, tuple是不可变对象
            args_in_list = list(args)
            # 装饰arguments
            for argument in arguments:
                num = names.index(argument)
                value = args[num]
                if isinstance(value, str):
                    value = value.decode('utf8').encode('gbk')
                elif isinstance(value, unicode):
                    value = value.encode('gbk')
                args_in_list[num] = value
            new_args = tuple(args_in_list)
            return func(*new_args, **kwargs)
        return wrapper
    return make_wrapper

再来看看装饰器版本的代码吧, 是不是简洁明了。

# 装饰器版本
@convert_arguments_to_unicode(['source'])
def get_perceptual_hash(srouce):  
    # 省略若干代码

@convert_arguments_to_unicode(['base_image', 'compare_image'])
def diff_image(base_image, compare_image, diff_image_prefix):
    # 省略若干代码
上一篇 下一篇

猜你喜欢

热点阅读