如何用 Python 写一个装饰器?

大家好!我是爱摸鱼的小鸿,关注我,收看每期的编程干货。

今天我们来聊聊 Python 中的一个强大而有趣的特性——装饰器。如果你是一名 Python 程序员,或者对 Python 编程感兴趣,那么一定不能错过今天这篇文章。我们将带你从零开始,深入理解并掌握装饰器的用法。准备好了吗?Let’s go!

一、什么是装饰器?

装饰器(Decorator)是 Python 中一种很有趣且非常强大的工具。简单来说,装饰器本质上就是一个函数,它能让你在不修改原函数代码的情况下,给函数增加新的功能。

这就像给手机加个壳,不改变手机本身,但可以让它看起来更炫酷,还能增加保护功能。

基础知识:函数也是对象
在 Python 中,函数可以像变量一样被传递、赋值和返回。举个例子:

def greet(name):
    return f"Hello, {name}!"

say_hello = greet
print(say_hello("World"))  # 输出:Hello, World!

这里我们把函数 greet 赋值给了变量 say_hello,然后通过 say_hello 调用了 greet 函数。这证明了函数在 Python 中是可以像对象一样操作的。

装饰器其实是一个返回函数的函数。听起来有点绕?别担心,来看一个简单的例子:

def my_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_hello():
    print("Hello!")

say_hello = my_decorator(say_hello)

say_hello()

输出结果是:

Something is happening before the function is called.
Hello!
Something is happening after the function is called.

这里我们定义了一个装饰器 my_decorator,它接收一个函数 func 作为参数,并定义了一个内部函数 wrapper 来包裹 func。在调用 func 前后,wrapper 分别打印了一些额外的信息。

然后,我们把 say_hello 函数传递给 my_decorator 并重新赋值给 say_hello,这样 say_hello 就被装饰了。

二、使用 @ 语法

Python 提供了一种更简洁的方式来使用装饰器,那就是 @ 语法。上面的例子可以改写成:

def my_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

@my_decorator
def say_hello():
    print("Hello!")

say_hello()

效果是一样的,但代码看起来更简洁、更易读。

三、装饰带参数的函数

大多数时候,我们的函数是带参数的。装饰器也需要能够处理这些参数。来看一个例子:

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Something is happening before the function is called.")
        result = func(*args, **kwargs)
        print("Something is happening after the function is called.")
        return result
    return wrapper

@my_decorator
def say_hello(name):
    print(f"Hello, {name}!")

say_hello("Alice")

输出结果是:

Something is happening before the function is called.
Hello, Alice!
Something is happening after the function is called.

这里我们使用 *args 和 **kwargs 来捕获所有的参数,无论是位置参数还是关键字参数。这样我们的装饰器就可以处理任何形式的函数了。

四、多个装饰器

函数还可以同时使用多个装饰器,每个装饰器都相当于对函数进行了一次包装。来看一个例子:

def decorator_one(func):
    def wrapper(*args, **kwargs):
        print("Decorator One")
        return func(*args, **kwargs)
    return wrapper

def decorator_two(func):
    def wrapper(*args, **kwargs):
        print("Decorator Two")
        return func(*args, **kwargs)
    return wrapper

@decorator_one
@decorator_two
def say_hello(name):
    print(f"Hello, {name}!")

say_hello("Bob")

输出结果是:

Decorator One
Decorator Two
Hello, Bob!

装饰器是从内到外应用的,这意味着 decorator_two 会先执行,然后才是 decorator_one。

五、应用案例

记录函数执行时间
让我们来看看一个更实际的例子:记录函数的执行时间。这在性能调优和分析中非常有用。

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"Function {func.__name__} took {end_time - start_time:.4f} seconds to execute.")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(2)
    print("Function finished.")

slow_function()

输出结果是:

Function finished.
Function slow_function took 2.0000 seconds to execute.

这里我们定义了一个 timer 装饰器,在调用被装饰的函数前后记录时间,并计算出函数的执行时间。


检查用户权限
另一个实际应用是检查用户权限。假设我们有一个 web 应用,需要检查用户是否有权限访问某些功能:

def check_permission(permission):
    def decorator(func):
        def wrapper(user, *args, **kwargs):
            if user.has_permission(permission):
                return func(user, *args, **kwargs)
            else:
                print(f"User {user.name} does not have {permission} permission.")
        return wrapper
    return decorator

class User:
    def __init__(self, name, permissions):
        self.name = name
        self.permissions = permissions

    def has_permission(self, permission):
        return permission in self.permissions

@check_permission('admin')
def view_admin_dashboard(user):
    print(f"Welcome to the admin dashboard, {user.name}!")

user1 = User('Alice', ['admin'])
user2 = User('Bob', [])

view_admin_dashboard(user1)  # 输出:Welcome to the admin dashboard, Alice!
view_admin_dashboard(user2)  # 输出:User Bob does not have admin permission.

这里我们定义了一个带参数的装饰器 check_permission,它接受一个权限字符串并返回一个装饰器。wrapper 函数在调用原函数前检查用户是否具有相应的权限。


缓存函数结果
缓存(memoization)是一种优化技术,用于存储函数的结果,以避免重复计算。我们可以使用装饰器来实现这一功能:

def memoize(func):
    cache = {}
    def wrapper(*args):
        if args in cache:
            return cache[args]
        result = func(*args)
        cache[args] = result
        return result
    return wrapper

@memoize
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(10))  # 输出:55

这里我们定义了一个 memoize 装饰器,用于缓存 fibonacci 函数的结果,以避免重复计算,显著提高了计算效率。

六、总结

通过这些例子,我们可以看到装饰器在 Python 编程中有着广泛的应用。从简单的函数包装,到复杂的权限检查、性能监控和结果缓存,装饰器让我们的代码更加简洁、灵活和强大。

装饰器不仅是 Python 中一个强大的工具,更是提升代码可读性和可维护性的利器。希望通过这篇文章,大家能对装饰器有一个更深入的理解,并能在实际项目中灵活应用。

在这里插入图片描述

七、作者Info

Author:小鸿的摸鱼日常

Goal:让编程更有趣! 专注于 Web 开发、爬虫,游戏开发,数据分析、自然语言处理,AI 等,期待你的关注,让我们一起成长、一起 Coding!

版权说明:本文禁止抄袭、转载,侵权必究!

相关推荐

  1. python装饰

    2024-07-23 02:56:07       36 阅读
  2. python装饰

    2024-07-23 02:56:07       25 阅读
  3. 新手如何学习理解Python装饰

    2024-07-23 02:56:07       77 阅读
  4. Python装饰

    2024-07-23 02:56:07       56 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-23 02:56:07       52 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-23 02:56:07       54 阅读
  3. 在Django里面运行非项目文件

    2024-07-23 02:56:07       45 阅读
  4. Python语言-面向对象

    2024-07-23 02:56:07       55 阅读

热门阅读

  1. python的open()函数

    2024-07-23 02:56:07       12 阅读
  2. 【过题记录】 7.22

    2024-07-23 02:56:07       14 阅读
  3. linux kernel 内核缓存回收的相关配置项

    2024-07-23 02:56:07       17 阅读
  4. Asp Net Web API 请求报错

    2024-07-23 02:56:07       12 阅读
  5. 欧鹏 数据库第二次作业

    2024-07-23 02:56:07       13 阅读
  6. FTP传输的两种模式的技术原理和应用

    2024-07-23 02:56:07       15 阅读
  7. mysql的不等于和null值问题

    2024-07-23 02:56:07       15 阅读
  8. 论c++中的GUI

    2024-07-23 02:56:07       15 阅读
  9. objdump命令的常见用法

    2024-07-23 02:56:07       11 阅读
  10. 关于paddle OCR不能调用cpu的问题

    2024-07-23 02:56:07       15 阅读