前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >让代码更具 Python 范儿的装饰器

让代码更具 Python 范儿的装饰器

原创
作者头像
mr.songw
修改2021-01-14 18:09:32
3900
修改2021-01-14 18:09:32
举报
文章被收录于专栏:Python 自习室Python 自习室

在 Python 中,装饰器的作用是在不改变函数或类的代码的前提下,改变函数或类的功能。在介绍装饰器之前,我们先来复习下 Python 中的函数。

函数
1. 函数也是对象
代码语言:txt
复制
def foo():
    print("Hello World!")

bar = foo

bar()

output:

代码语言:txt
复制
Hello World!

在上面的例子中,首先定义了函数 foo() ,然后将函数 foo 赋给变量 bar ,这样我们便可以通过 bar() 来调用函数。

2. 函数作为参数
代码语言:txt
复制
def foo():
    print("I am foo")


def bar(func):
    print("I am bar")
    func()


bar(foo)

output:

代码语言:txt
复制
I am bar
I am foo

在上面的例子中,函数 foo() 作为参数传入到 bar() 函数,然后再 bar() 函数内部通过参数来调用函数 foo()

3. 函数嵌套
代码语言:txt
复制
def parent():
    print("I am parent")

    def foo_child():
        print("I am foo")

    def bar_child():
        print("I am bar")

    foo_child()
    bar_child()


parent()

output:

代码语言:txt
复制
I am parent
I am foo
I am bar

在上面的例子中,函数 foo_child()bar_child() 嵌套在函数 parent() 的内部。嵌套在函数内部的函数只能在函数内部调用,从外部调用会报错。例如:

代码语言:txt
复制
def parent():
    print("I am parent")

    def foo_child():
        print("I am foo")

    def bar_child():
        print("I am bar")

    foo_child()
    bar_child()


foo_child()

output:

代码语言:txt
复制
Traceback (most recent call last):
  File "/Users/weisong/PycharmProjects/pythonProject/greet.py", line 14, in <module>
    foo_child()
NameError: name 'foo_child' is not defined
4. 函数作为返回值
代码语言:txt
复制
def parent(name):
    def foo_child():
        print("I am foo")

    def bar_child():
        print("I am bar")

    if name == 'foo':
        return foo_child
    else:
        return bar_child


foo = parent("foo")
bar = parent("bar")


foo()
bar()

output:

代码语言:txt
复制
I am foo
I am bar

在上面的例子中,parent() 函数根据传入的参数返回函数 foo_child 或者 bar_child,得到 parent() 函数的返回值之后,我们可以使用返回值调用相应的函数。

装饰器
1. 一个简单的装饰器

介绍完函数的各种用法后,我们来看一个简单的装饰器(decorator)。

代码语言:txt
复制
def my_decorator(func):
    def wrapper():
        print("Do something before function is called")
        func()
        print("Do something after function is called")
    return wrapper


def foo():
    print("I am foo")


foo = my_decorator(foo)
foo()

output:

代码语言:txt
复制
Do something before function is called
I am foo
Do something after function is called

在上面的例子中,语句 foo = my_decorator(foo)my_decorator 函数的返回值 wrapper 函数赋给 foo ,这样我们便可以使用 foo 来调用 wrapper 函数,在 wrapper 函数中包含着作为参数传入的函数的调用,所以会输出 I am foo

2. 语法糖

上面使用装饰器的方式有点笨重,Python 提供了一种更简单的方式来使用装饰器,这便是使用 @ 符号,我们称之为语法糖。使用 @ 符号,将上面的装饰器修改如下:

代码语言:txt
复制
def my_decorator(func):
    def wrapper():
        print("Do something before function is called")
        func()
        print("Do something after function is called")
    return wrapper


@my_decorator
def foo():
    print("I am foo")


foo()

output:

代码语言:txt
复制
Do something before function is called
I am foo
Do something after function is called

@my_decorator 相当于前面的 foo = my_decorator(foo),使用方式较前面简洁了许多。另外,如果程序中有其他的函数需要类似的装饰,只需要在它们的上方加上 @my_decorator 就可以了,大大提高了程序的可复用性和可读性。

3. 带参数的装饰器
代码语言:txt
复制
def my_decorator(func):
    def wrapper(greet):
        print("Do something before function is called")
        func(greet)
        print("Do something after function is called")
    return wrapper


@my_decorator
def foo(greet):
    print(f"{greet}, I am foo")


foo("Hello")

output:

代码语言:txt
复制
Do something before function is called
Hello, I am foo
Do something after function is called

在为函数 foo() 加了参数 greet 之后,调用函数 foo() 时便可以传入参数。当然装饰器也要做相应的修改,为函数 wrapper 也添加了参数 greet。但是上述加参数的方式有一个缺点,当使用这个装饰器来装饰一个不带参数的函数时,调用便会发生错误。例如:

代码语言:txt
复制
def my_decorator(func):
    def wrapper(greet):
        print("Do something before function is called")
        func(greet)
        print("Do something after function is called")
    return wrapper


@my_decorator
def foo():
    print("I am foo")


foo()

output:

代码语言:txt
复制
Traceback (most recent call last):
  File "/Users/weisong/PycharmProjects/pythonProject/greet.py", line 14, in <module>
    foo()
TypeError: wrapper() missing 1 required positional argument: 'greet'

为了兼容带参数的调用和不带参数的调用,可以将装饰器函数修改如下:

代码语言:txt
复制
def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Do something before function is called")
        func(*args, **kwargs)
        print("Do something after function is called")
    return wrapper


@my_decorator
def foo():
    print("I am foo")


@my_decorator
def bar(greet):
    print(f"{greet}, I am bar")


foo()
bar("Hello")

output:

代码语言:txt
复制
Do something before function is called
I am foo
Do something after function is called
Do something before function is called
Hello, I am bar
Do something after function is called
4. 带自定义参数的装饰器
代码语言:txt
复制
def repeat(num):
    def my_decorator(func):
        def wrapper(*args, **kwargs):
            print("do something before function is called")
            for i in range(num):
                func(*args, **kwargs)
            print("do something after function is called")
        return wrapper
    return my_decorator


@repeat(2)
def foo():
    print("I am foo")


@repeat(3)
def bar(greet):
    print(f"{greet}, I am bar")


foo()
bar("Hello")

output:

代码语言:txt
复制
Do something before function is called
I am foo
I am foo
Do something after function is called
Do something before function is called
Hello, I am bar
Hello, I am bar
Hello, I am bar
Do something after function is called

上面的例子中,使用装饰器时传入了参数 num ,用来表示内部函数执行的次数。

5. 被装饰的函数还是它自己吗?

还是使用前面的例子,我们打出函数 foobar() 的元信息,看看被装饰器修饰后还是不是它们自己。

代码语言:txt
复制
def repeat(num):
    def my_decorator(func):
        def wrapper(*args, **kwargs):
            print("Do something before function is called")
            for i in range(num):
                func(*args, **kwargs)
            print("Do something after function is called")
        return wrapper
    return my_decorator


@repeat(2)
def foo():
    print("I am foo")


@repeat(3)
def bar(greet):
    print(f"{greet}, I am bar")
    

print(foo.__name__)
help(foo)
print("**************************")
print(bar.__name__)
help(bar)

output:

代码语言:txt
复制
wrapper
Help on function wrapper in module __main__:

wrapper(*args, **kwargs)

**************************
wrapper
Help on function wrapper in module __main__:

wrapper(*args, **kwargs)

从输出结果可以看出,在被装饰器修饰后,被修饰函数的元信息改变了。变成了 wrapper() 函数。可以使用内置的装饰器@functools.wrap 来解决这个问题,它会保留被修饰函数的元信息。

代码语言:txt
复制
import functools


def repeat(num):
    def my_decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print("Do something before function is called")
            for i in range(num):
                func(*args, **kwargs)
            print("Do something after function is called")
        return wrapper
    return my_decorator


@repeat(2)
def foo():
    print("I am foo")


@repeat(3)
def bar(greet):
    print(f"{greet}, I am bar")


print(foo.__name__)
help(foo)
print("**************************")
print(bar.__name__)
help(bar)

output:

代码语言:txt
复制
foo
Help on function foo in module __main__:

foo()

**************************
bar
Help on function bar in module __main__:

bar(greet)

在使用@functools.wrap 之后,被修饰函数保留了原有的元信息。

装饰器的应用实例
1. 记录函数执行的时间
代码语言:txt
复制
import functools
import time


def timer(func):
    @functools.wraps(func)
    def wrapper_timer(*args, **kwargs):
        start_time = time.perf_counter()
        func(*args, **kwargs)
        end_time = time.perf_counter()
        run_time = end_time - start_time
        print(f"Finished {func.__name__!r} in {run_time:.4f} secs")
    return wrapper_timer


@timer
def waste_some_time(num_times):
    for _ in range(num_times):
        sum([i**2 for i in range(10000)])


waste_some_time(1)
waste_some_time(1000)

output:

代码语言:txt
复制
Finished 'waste_some_time' in 0.0037 secs
Finished 'waste_some_time' in 3.3343 secs
2. 控制函数相邻两次执行的时间间隔
代码语言:txt
复制
import functools
import time


def slow_down(func):
    @functools.wraps(func)
    def wrapper_slow_down(*args, **kwargs):
        time.sleep(1)
        return func(*args, **kwargs)
    return wrapper_slow_down


@slow_down
def countdown(from_number):
    if from_number < 1:
        print("Liftoff!")
    else:
        print(from_number)
        countdown(from_number - 1)


countdown(6)

output:

代码语言:txt
复制
6
5
4
3
2
1
Liftoff!
3. 代码 Debug
代码语言:txt
复制
import functools


def debug(func):
    """Print the function signature and return value"""
    @functools.wraps(func)
    def wrapper_debug(*args, **kwargs):
        args_repr = [repr(a) for a in args]                      
        kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]  
        signature = ", ".join(args_repr + kwargs_repr)           
        print(f"Calling {func.__name__}({signature})")
        value = func(*args, **kwargs)
        print(f"{func.__name__!r} returned {value!r}")           
        return value
    return wrapper_debug


@debug
def make_greeting(name, age=None):
    if age is None:
        return f"Howdy {name}!"
    else:
        return f"Whoa {name}! {age} already, you are growing up!"


make_greeting("foo")
make_greeting("foo", 18)

output:

代码语言:txt
复制
Calling make_greeting('foo')
'make_greeting' returned 'Howdy foo!'
Calling make_greeting('foo', 18)
'make_greeting' returned 'Whoa foo! 18 already, you are growing up!'
总结

本文讲述了装饰器的原理以及用法,装饰器的存在大大提高了代码的可复用性以及简洁性。

封面:该图片由jplenio在Pixabay上发布

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 函数
    • 1. 函数也是对象
      • 2. 函数作为参数
        • 3. 函数嵌套
          • 4. 函数作为返回值
          • 装饰器
            • 1. 一个简单的装饰器
              • 2. 语法糖
                • 3. 带参数的装饰器
                  • 4. 带自定义参数的装饰器
                    • 5. 被装饰的函数还是它自己吗?
                    • 装饰器的应用实例
                      • 1. 记录函数执行的时间
                        • 2. 控制函数相邻两次执行的时间间隔
                          • 3. 代码 Debug
                          • 总结
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档