Python一分钟:装饰器

一、装饰器基础

函数即对象

在python中函数可以作为参数传递,和任何其它对象一样如:str、int、float、list等等

python 复制代码
def say_hello(name):
    return f"Hello {name}"

def be_awesome(name):
    return f"Yo {name}, together we're the awesomest!"

def greet_bob(greeter_func):
    return greeter_func("Bob")

内部函数

在函数内部可以创建函数,这样的函数被称为内部函数:

python 复制代码
def parent():
    print("Printing from parent()")

    def first_child():
        print("Printing from first_child()")

    def second_child():
        print("Printing from second_child()")

    second_child()
    first_child()

在python在交互式shell中运行:

shell 复制代码
python -i test.py

在使用 Python 时,尤其是在交互式 shell 中,其强大的自省(introspection)能力提供了极大的便利。自省是指对象在运行时能够了解自身属性的能力。

shell 复制代码
>>> parent()

内部函数只有在父函数被调用时才会被定义,它的作用域仅限于父函数parent()内:

>>> first_child()
Traceback (most recent call last):
  ...
NameError: name 'first_child' is not defined

函数作为返回值

函数可以作为返回值返回:

python 复制代码
def parent(num):
    def first_child():
        return "Hi, I'm Elias"

    def second_child():
        return "Call me Ester"

    if num == 1:
        return first_child
    else:
        return second_child

通过变量保存返回的函数引用,可以像普通函数那样正常调用:

>>> first = parent(1)
>>> second = parent(2)

>>> first
<function parent.<locals>.first_child at 0x7f599f1e2e18>

>>> second
<function parent.<locals>.second_child at 0x7f599dad5268>

简单的装饰器

python 复制代码
def decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

def say_whee():
    print("Whee!")

say_whee = decorator(say_whee)

在shell中运行:

python 复制代码
>>> from hello_decorator import say_whee

>>> say_whee()
# <function decorator.<locals>.wrapper at 0x7f3c5dfd42f0>

通过@符号来使用装饰器:

python 复制代码
def decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

@decorator
def say_whee():
    print("Whee!")

传递参数:

python 复制代码
def do_twice(func):
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        func(*args, **kwargs)
    return wrapper_do_twice

在shell中运行:

python 复制代码
>>> from decorators import do_twice

>>> @do_twice
... def return_greeting(name):
...     print("Creating greeting")
...     return f"Hi {name}"
...

>>> return_greeting("Adam")
# Creating greeting
# Creating greeting
# 'Hi Adam'

保留原始信息

当函数被我们之前创建的装饰器装饰时,它的内部信息就会变得混乱,使用@functools.wraps装饰器可以解决这个问题:

python 复制代码
import functools

def do_twice(func):
    @functools.wraps(func)
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        return func(*args, **kwargs)
    return wrapper_do_twice

在shell中运行:

python 复制代码
>>> from decorators import do_twice

>>> @do_twice
... def say_whee():
...     print("Whee!")
...

>>> say_whee
<function say_whee at 0x7ff79a60f2f0>

>>> say_whee.__name__
'say_whee'

>>> help(say_whee)
Help on function say_whee in module whee:

say_whee()

可以看到经过@functools.wraps装饰器后,say_whee函数的元数据被保留下来,包括函数名、文档字符串等。

二、更复杂的装饰器

嵌套装饰器

装饰器可以堆叠用于一个函数:

python 复制代码
>>> from decorators import debug, do_twice

>>> @debug
... @do_twice
... def greet(name):
...     print(f"Hello {name}")

带参数的装饰器

我们可以定义能够接收参数的装饰器,如下:

python 复制代码
from decorators import repeat

@repeat(num_times=4)
def greet(name):
    print(f"Hello {name}")


greet("World")

实现方法如下:

python 复制代码
import functools

# ...

def repeat(num_times):
    def decorator_repeat(func):
        @functools.wraps(func)
        def wrapper_repeat(*args, **kwargs):
            for _ in range(num_times):
                value = func(*args, **kwargs)
            return value
        return wrapper_repeat
    return decorator_repeat

拆分讲解上面的代码:

首先decorator_repeat部分的行为,就和我们之前定义的普通装饰器一样:

python 复制代码
...
    def decorator_repeat(func):
        @functools.wraps(func)
        def wrapper_repeat(*args, **kwargs):
            for _ in range(num_times):
                value = func(*args, **kwargs)
            return value
        return wrapper_repeat
...

外层由repeat()函数接收参数,并返回wrapper_repeat装饰器的引用。

在装饰器中跟踪状态

可以实现装饰器对象中添加属性来追踪函数调用次数:

python 复制代码
import functools

def count(func):

    @functools.wraps(func)
    def warp(*args, **kwargs):
        warp.num_calls += 1
        print(f"Call {warp.num_calls} of {func.__name__}()")
        return func(*args, **kwargs)
    
    warp.num_calls = 0
    return warp

@count
def hello():
    print("Hello")

hello()
hello()
hello()

类装饰器

有两种不同的方式可以在类中使用装饰器,第一种方法和我们上面学到的在函数中使用装饰器的方法非常类似。

Python有很多内置的装饰器很常用:

  • @classmethod
  • @staticmethod
  • @property

在python中维护状态的典型方法就是使用类。之前实现的count函数可以使用类装饰器重写。为了使实例可调用,需要实现.__call__()方法:

python 复制代码
>>> class Counter:
...     def __init__(self, start=0):
...         self.count = start
...     def __call__(self):
...         self.count += 1
...         print(f"Current count is {self.count}")

每次调用实例都会执行.__call__()方法:

python 复制代码
>>> counter = Counter()
>>> counter()
Current count is 1

>>> counter()
Current count is 2

>>> counter.count

实现装饰器类的典型方法是应该实现.__init__().__call__()

python 复制代码
import functools

# ...

class CountCalls:
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func
        self.num_calls = 0

    def __call__(self, *args, **kwargs):
        self.num_calls += 1
        print(f"Call {self.num_calls} of {self.func.__name__}()")
        return self.func(*args, **kwargs)

使用方法:

python 复制代码
>>> from decorators import CountCalls

>>> @CountCalls
... def say_whee():
...     print("Whee!")
...

>>> say_whee()
# Call 1 of say_whee()
# Whee!

>>> say_whee()
# Call 2 of say_whee()
# Whee!

>>> say_whee.num_calls
# 2

原文链接

相关推荐
陌小呆^O^7 分钟前
Cmakelist.txt之win-c-udp-server
c语言·开发语言·udp
Gu Gu Study14 分钟前
枚举与lambda表达式,枚举实现单例模式为什么是安全的,lambda表达式与函数式接口的小九九~
java·开发语言
时光の尘28 分钟前
C语言菜鸟入门·关键字·float以及double的用法
运维·服务器·c语言·开发语言·stm32·单片机·c
以后不吃煲仔饭42 分钟前
Java基础夯实——2.7 线程上下文切换
java·开发语言
进阶的架构师43 分钟前
2024年Java面试题及答案整理(1000+面试题附答案解析)
java·开发语言
前端拾光者1 小时前
利用D3.js实现数据可视化的简单示例
开发语言·javascript·信息可视化
程序猿阿伟1 小时前
《C++ 实现区块链:区块时间戳的存储与验证机制解析》
开发语言·c++·区块链
傻啦嘿哟1 小时前
如何使用 Python 开发一个简单的文本数据转换为 Excel 工具
开发语言·python·excel
大数据编程之光1 小时前
Flink Standalone集群模式安装部署全攻略
java·大数据·开发语言·面试·flink
初九之潜龙勿用1 小时前
C#校验画布签名图片是否为空白
开发语言·ui·c#·.net