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

原文链接

相关推荐
林小果118 分钟前
适配器模式
java·开发语言·设计模式·适配器模式
嗡嗡嗡qwq1 小时前
python调用c++动态链接库,环境是VS2022和vscode2023
开发语言·c++·python
luthane2 小时前
python 实现area under curve曲线下面积算法
开发语言·python·算法
ZHOU西口2 小时前
ChromaDB教程_2024最新版(下)
python·langchain·大模型·embedding·transformers·chroma·all-minilm-l6
你可以自己看2 小时前
python中数据科学与机器学习框架
开发语言·python·机器学习
循环渐进Forward2 小时前
【C++笔试强训】如何成为算法糕手Day2
开发语言·数据结构·c++·算法·哈希算法·笔试·牛客
小鸿的摸鱼日常2 小时前
Kivy,一个上天入地的 Python 库
python·kivy·python 库之旅
Banana08403 小时前
seq2seq翻译实战-Pytorch复现
人工智能·pytorch·python
计算机编程-吉哥3 小时前
计算机毕业设计 基于Python的汽车销售管理系统 Python+Django+Vue 前后端分离 附源码 讲解 文档
python·django·计算机毕业设计·计算机毕业论文·计算机毕业设计选题·软件工程毕业设计论文·汽车销售管理系统
向上爬的卓卓卓4 小时前
C++【类和对象】(构造函数与析构函数)
java·开发语言·c++·visual studio