掌握 Python 函数装饰器:提高功能和代码效率

装饰器是 Python 语言中一个非常有用的功能,它允许程序员在不更改函数的情况下更改函数的行为。听起来很奇怪?好吧,别担心,因为

在这篇文章中,我们将:

  1. 了解什么是装饰器以及它们是如何工作的。
  2. 如何创建自己的装饰器
  3. 请参阅装饰器的实际用例。

所以,让我们开始吧。

了解 Python 中的函数!

我们都使用过 Python 中的函数。最基本的例子 print() 是一个让我们在屏幕上看到输出的函数。函数基本上是一个可重用的代码块,用于执行特定任务或一组任务。函数是编程中的基本概念,用于组织和模块化代码,使其更易于管理、可读和高效。

Python 函数的基本结构如下所示。

所有 Python 函数都以关键字“def”开头,后跟函数名称和括号。

例如

def hello(name):
    print(f"Hello, {name}")

hello("Shekhar")

输出

Hello, Shekhar

此函数仅打印带有给定参数的 hello 消息。

但是,如果我们简单地像这样调用函数,现在会发生什么呢?

print(hello)

我们将得到一个输出,如下所示

<function hello at 0x000001EC6A3F8CC0>

与 Python 中的大多数内容一样,函数也是一个对象,函数名称只是对该对象的引用。上面的输出告诉我们,在内存位置“0x000001EC6A3F8CC0”有一个函数对象。

这意味着您可以将函数用作其他函数的参数,将函数存储为字典值,或从另一个函数返回一个函数。这导致了许多使用函数的强大方法。😲

不要混淆;让我们一个接一个地开始。

函数名称只是对函数的引用

让我们看一下这个婴儿示例,以了解函数名称只是引用的概念。

print("Shekhar")

myfunction = print

myfunction("Hello")

print(print)
print(myfunction)

输出

Shekhar
Hello
<built-in function print>
<built-in function print>

当我们编写 myfunction = print 时,我们通过 myfunction 引用 print 对象,因此 myfunction 充当打印函数。

现在,让我们看另一个例子。我们可以做这样的事情

def add_one(number):
    return number + 1

def multiply_10(number):
    return number * 10

function_list = [add_one, multiply_10]

print(add_one(10))
print(multiply_10(10))

print(function_list[0](10))
print(function_list[1](10))

我们得到这个输出

11
100
11
100

嵌套函数

现在我们已经了解了 Python 中函数的基础知识,让我们更深入地了解一下。

以这个函数为例。

def prefix(prefix):
    def hello(name):
        print(f"{prefix} Hello, {name}!")
    return hello

print_debug = prefix("DEBUG :")
print_debug("shekhar")

print_warning = prefix("Warning :")
print_warning("Opps!")

输出

DEBUG : Hello, shekhar
Warning :Hello, Opps!

在这里,prefix(“DEBUG:”) 返回对函数 hello 的引用,并将前缀参数替换为 “DEBUG:”

创建函数装饰器

让我们通过一个例子来了解装饰器。请考虑下面的代码。

def reverse(func):
    def reverse_caller(text):
        func(text[::-1])

    return reverse_caller

rev_print = reverse(print)
rev_print("Hello Shekhar!")

rev_print = reverse(print_warning)
rev_print("Shekhar!")
输出
!rahkehS olleH
Warning : Hello, !rahkehS!

现在,这个函数 reverse() 将函数引用作为参数并返回一个函数 😯

这就是我们所说的装饰器。它是一个函数,将函数作为参数,对其进行修改并返回函数。

另一种方法如下。

@reverse
def greet(name):
    print(f"Hello {name}")

greet("Shekhar")
Hello rahkehS

这与 greet=reverse(greet) 完全相同。

@reverse只是一种语法,使内容整洁且更易于阅读

制作装饰器的一般模板如下

def decorator_name(func):
    def wrapper(func):
        #DO SOMETHING
    return wrapper

练习

现在我们已经了解了基础知识,让我们编写一个装饰器,它将 Print “BEFORE” 运行函数并打印 “AFTER”。

我强烈建议您在开始之前自行尝试。

按照我们的装饰器模板,我们有以下内容

def before_after(func):
    def wrapper(name):
        print("Before")
        func(name)
        print("After")
    return wrapper

@before_after
def greet(name):
    print(f"Hello {name}")


greet("shekhar")

输出

它按预期😃工作

Before
Hello shekhar
After

在装饰器中处理参数

现在,以我们之前的例子 greet 为例,并尝试向其添加默认参数,看看会发生什么。

@before_after
def greet(name="Shekhar"):
    print(f"Hello {name}")

你觉得怎么样?它会起作用还是不起作用?

让我们看看输出。

TypeError: before_after.<locals>.wrapper() missing 1 required positional argument: 'name'

它失败了,因为当我们装饰一个函数并使用它时,我们实际上并没有使用我们原来的函数。

我们正在使用嵌套包装函数。如果我们传递不带参数的 greet(),那么我们的包装函数也不会接收任何参数,但它需要一个“name”参数才能正常运行。

这就是为什么错误说“wapper() 缺少 1 个必需的位置参数:name”

让我们再举一个例子来进一步理解这个概念。

def greet(name):
    print(f"Hello {name}")

greet("Shekhar")

print(greet.__name__)

@before_after
def greet(name):
    print(f"Hello {name}")

greet("Shekhar")

print(greet.__name__)

输出

Hello Shekhar
greet
Hello rahkehS
wrapper

name 是 Python 中的一个内置属性,它返回对象的名称,正如我之前提到的,函数名称是对函数对象的引用,我们可以获取它们的名称(或者你可以说函数对象的名称)。

这里需要注意的一件有趣的事情是,当我们将装饰器添加到函数时,函数的名称会更改为包装器(因为这是装饰器中包装器函数的名称)。这表明,当我们使用装饰函数时,我们实际上是在使用装饰器中的包装器函数。

如何改进我们的装饰器?

正如我们所看到的,让我们的装饰器能够处理这些情况非常重要。如果我们尝试将多个参数传递给我们的修饰函数,我们将面临类似的错误。

为了解决这类问题,我们使用 *args 和 **kwargs,它们分别转换为 argument 和 keyword arguments。我将在即将发表的博客中更详细地解释这些内容,在博客中,我将分享 Python 的实际运行方式以及函数在 Python 中的执行方式和实际意义;这些操作符称为解包运算符。

  • *将任何变量展开为列表
  • **将任何变量扩展为 Dictionary

例如

a = [1,2,3]
b = [3,4,5]
c1 = [*a,*b]
c2 = [a,b]

dict1 = {'a':1,'b':2}
dict2 = {'b':2,'c':3}
dict3 = {**dict1,**dict2}

print(f"c1:{c1}\n c2:{c2}")
print(dict3)
c1:[1, 2, 3, 3, 4, 5]
c2:[[1, 2, 3], [3, 4, 5]]
{'a': 1, 'b': 2, 'c': 3}

*args 使我们能够向函数发送任意数量的参数,而 **kwargs 使我们能够在调用函数时传入任意数量的关键字参数。

例如

def testing(*args,**kwargs):
    print(f"Args = {args}")
    print(f"KWargs = {kwargs}")

testing("hello",1,name="shekhar")
Args = ('hello', 1)
KWargs = {'name': 'shekhar'}

“args”和“kwargs”是通用的命名约定。您可以使用遵循相同 * 和 ** 模式的任何其他名称,其工作方式相同。

在装饰器中使用 Args 和 Kwargs

def before_after(func):
    def wrapper(*args, **kwargs):
        print("Before")
        func(*args, **kwargs)
        print("After")

    return wrapper


@before_after
def greet(name="Shekhar"):
    print(f"Hello {name}")


greet()
greet("joey")
Before
Hello Shekhar
After
Before
Hello joey
After

另一个练习

让我们编写一个装饰器来运行修饰的函数三次,并返回一个包含结果的 3 值元组。

import random

def do_thrice(func):
    print(f"Adding decorator to {func.__name__}")

    def wrapper():
        return (func(), func(), func())

    return wrapper


@do_thrice
def roll_dice():
    return random.randint(1, 6)

print(roll_dice())

输出

Adding decorator to roll_dice
(2, 2, 1)

真实用例

现在我们已经看到了足够多的创建装饰器的例子,让我们看看装饰器非常有用的一些真实用例。

测量其他功能的性能

假设您的代码中有许多函数,并且您想要优化您的代码。现在,我们无需编写代码来获取每个函数的运行时和内存使用情况,而是可以简单地编写一个装饰器并将其应用于我们想要测试的所有函数。

让我们以这个例子为例,我们使用 perf_counter(就像秒表一样)计算函数运行时,并使用 tracemalloc 函数计算内存使用量。(它是标准库的一部分,我将就此主题制作一个专门的博客)。

from functools import wraps
import tracemalloc
from time import perf_counter 

def measure_performance(func):
    @wraps(func)  # It is a inbuilt decorator in python
    # When used, the decorated function name remains the same

    def wrapper(*args, **kwargs):
        tracemalloc.start()
        start_time = perf_counter()
        func(*args, **kwargs)
        current, peak = tracemalloc.get_traced_memory()
        finish_time = perf_counter()
        print(f"Function: {func.__name__}")
        print(
            f"Memory usage:\t\t {current / 10**6:.6f} MB \n"
            f"Peak memory usage:\t {peak / 10**6:.6f} MB "
        )
        print(f"Time elapsed is seconds: {finish_time - start_time:.6f}")
        print(f'{"-"*40}')
        tracemalloc.stop()

    return wrapper


@measure_performance
def function1():
    lis = []
    for a in range(1000000):
        if a % 2 == 0:
            lis.append(1)
        else:
            lis.append(0)


@measure_performance
def function2():
    lis = [1 if a % 2 == 0 else 0 for a in range(1000000)]


function1()
function2()

输出

Function: function1
Memory usage:            0.000000 MB
Peak memory usage:       8.448768 MB
Time elapsed is seconds: 0.329556
----------------------------------------
Function: function2
Memory usage:            0.000000 MB
Peak memory usage:       8.448920 MB
Time elapsed is seconds: 0.294334
----------------------------------------

我们只是在函数中添加了两个装饰器,而不是将整个代码添加到每个函数中。这使我们的代码更加简洁和可读。

动态添加功能

许多库(如 Flask 或 FastAPI)广泛使用装饰器来创建 API 路由。例如,在 FastAPI 中,我们使用如下内容:

@app.get("/helloWorld", tags=["Testing"])
def read_root():
    return {"Hello": "World"}

这将创建一个 get 端点 /helloworld,每当调用时,该端点都会触发 read_root() 函数。

发表评论

邮箱地址不会被公开。 必填项已用*标注