Python 装饰器

栏目: Python · 发布时间: 5年前

内容简介:在函数是一个对象,可以被赋值给变量。函数可以作为参数传递给另一个函数。

Python 中,函数是一级对象,它可以赋值给变量,也可以作为参数传递给另外一个函数。

函数赋值给变量

函数是一个对象,可以被赋值给变量。

def hi():
    print("Hi, James.")

print(hi)    # <function hi at 0x7fe4b9001510>
hi()         # Hi, James.

hello = hi

print(hello) # <function hi at 0x7fe4b9001510>
hello()      # Hi, James.
复制代码

函数作为参数

函数可以作为参数传递给另一个函数。

def hi():
    print('Hi, James.')
    
def before_hi(func):
    print('Before excuting hi()')
    func()

before_hi(hi)
# Before excuting hi()
# Hi, James.
复制代码

函数作为返回值

函数不仅可以作为参数,也可以作为另一个函数的返回值。

def hi():
    return 'Hi'

def hello():
    return hi

h = hello()
h()  # 'Hi'
复制代码

函数中定义函数

我们可以将函数定在另外一个函数中,也就是嵌套函数。

def hi():
    print('Hi')
    
    def hello():
        print('hello')
    
    hello()
    
hi()
# Hi
# hello
复制代码

装饰器

理解了函数是一级对象后,装饰器也就呼之欲出了。

装饰器其实就是个可调用对象(函数或带有 __call__ 的类等),它接收一个函数,完成一些操作后,返回另外一个函数。

使用装饰器,我们可以在不修改原来代码的基础上,更直观的为代码添加额外的功能,如权限验证、日志记录等。

语法

我们编写一个简单的装饰器:

def decorated_by(func):
    return func

def add(x, y):
    return x + y

add = decorated_by(add)

add(2, 3)  # 5
复制代码

Python 2.5 开始,在函数声明前使用 @ 应用装饰器:

def decorated_by(func):
    print('I am a decorator')
    return func

@decorated_by      # I am a decorator
def add(x, y):
    return x + y
    
add(2, 3)  # 5
复制代码

@decorated_by 实际上干这么一件事: add = decorated_by(add) ,也就是被装饰的函数依然存在,只是变量 add 指向了新的函数。

更重要的是可以看出: 在应用装饰器到函数时,执行装饰器代码

多个装饰器

使用多个装饰器时,按照 从下到上 的顺序应用它们

def decorated_a(func):
    print('I am decorator a')
    return func

def decorated_b(func):
    print('I am decorator b')
    return func

def decorated_c(func):
    print('I am decorator c')
    return func

@decorated_a
@decorated_b
@decorated_c
def add(x, y):
    return x + y

# I am decorator c
# I am decorator b
# I am decorator a
复制代码

编写装饰器

我们来编写一个装饰器,确保函数接收到的参数都是整型。

def requires_ints(func):

    def wrapper(*args, **kwargs):
        values = [i for i in kwargs.values()]
        for arg in list(args) + values:
            if not isinstance(arg, int):
                raise TypeError('Only accept integers.')
        return func(*args, **kwargs)
    
    return wrapper


@requires_ints
def add(x, y):
    return x + y

print(add.__name__)  # wrapper
print(add.__doc__)   # None
复制代码

可以发现 wrapper 替换原函数的 namedocstring ,这并不是我们想要的结果。使用 functools.wraps 来解决这个问题:

from functools import wraps

def requires_ints(func):
    
    @wraps(func)
    def wrapper(*args, **kwargs):
        values = [i for i in kwargs.values()]
        for arg in list(args) + values:
            if not isinstance(arg, int):
                raise TypeError('Only accept integers.')
        return func(*args, **kwargs)
    
    return wrapper

@requires_ints
def add(x, y):
    '''Return the sum of x and y'''
    return x + y

print(add.__name__)  # add
print(add.__doc__)   # Return the sum of x and y
复制代码

Python 2 中使用 help(add) 查看时,显示的参数信息仍为 *args**kwargs

带参数的装饰器

上面写的装饰器看上去并没有任何参数(使用 @ 时被装饰的方法作为隐式参数传递给装饰器)。但是,有时候让装饰器自带一些参数,可以提供更灵活的应用。

我们改写 requires_ints ,使其还可以限制被装饰函数的参数的数量:

from functools import wraps

def requires_ints(count=0):
    
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            values = [i for i in kwargs.values()]
            args_list = list(args) + [i for i in kwargs.values()]
            
            if count == 0:
                # Not limit
                pass
            elif len(args_list) > count:
                raise Exception(f'The number of arguments cannot more than {count}')
            
            for arg in args_list:
                if not isinstance(arg, int):
                    raise TypeError('Only accept integers.')
            return func(*args, **kwargs)

        return wrapper
    return decorator


@requires_ints(5)
def add(*args):
    '''Return the sum of all arguments'''
    return sum(args)
复制代码

分解下上述装饰器的运行步骤:

  1. @requires_ints() 返回函数 decorator
  2. add(*args) 作为参数传递进 decorator
  3. 之后就和前面的所写的装饰器一样了,带参数的装饰器只是多了一层嵌套。

我们之前应用装饰器时使用的是 @requires_ints 形式,而在这里需使用 @requires_ints() 返回真正的装饰器,然后才能发挥效果,这显然是不太友好的。

下面我们来改进一下,使 @requires_ints@requires_ints() 都可以。

from functools import wraps

def requires_ints(_decorated=None, count=0):
    
    if _decorated and count:
        raise RuntimeError('Unexpected  arguments.')
    
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            values = [i for i in kwargs.values()]
            args_list = list(args) + [i for i in kwargs.values()]
            if count == 0:
                # Not limit
                pass
            elif len(args_list) > count:
                raise Exception(f'The number of arguments cannot more than {count}')
            
            for arg in args_list:
                if not isinstance(arg, int):
                    raise TypeError('Only accept integers.')
            return func(*args, **kwargs)

        return wrapper
    
    if _decorated:
        # 使用 @requires_ints 形式, 被装饰的方法赋值给 _decorated
        # 执行 decorator(_decorated) 返回 wrapper, 即和不带参数的装饰器一样
        return decorator(_decorated)
    else:
        # 使用 @requires_ints() 形式, _decorated 确保为 None, 不能由用户手动传入
        # 前面做了检测,如果用户手动传入了 _decorated 和 count, 则报错
        return decorator


@requires_ints
def add1(*args):
    '''Return the sum of all arguments'''
    return sum(args)

@requires_ints()
def add2(*args):
    '''Return the sum of all arguments'''
    return sum(args)

@requires_ints(count=5)
def add3(*args):
    '''Return the sum of all arguments'''
    return sum(args)
复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

链接

链接

[美]艾伯特-拉斯洛•巴拉巴西 (Albert-László Barabási) / 沈华伟 / 浙江人民出版社 / 2013-8-1 / 59.90元

[内容简介] ★《链接》是《爆发》的作者,艾伯特-拉斯洛•巴拉巴西的成名之作,同时也是复杂网络的奠基之作,社交网络的入门之作。巴拉巴西之前,随机网络理论一直主导者我们的网络思维,是巴拉巴西第一个证明了,我们不是生活在随机世界里,真实网络是无尺度的。 ★巴拉巴西在书中追溯了网络的数学起源,分析了社会学家在此基础上得出的研究成果,最后提出自己的观点:我们周围的复杂网络,从鸡尾酒会、恐怖组织......一起来看看 《链接》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具