当前位置: 首页 > article >正文

Python一分钟:装饰器

一、装饰器基础

函数即对象

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

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")

内部函数

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

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中运行:

python -i test.py

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

>>> parent()

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

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

函数作为返回值

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

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>

简单的装饰器

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中运行:

>>> from hello_decorator import say_whee

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

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

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!")

传递参数:

def do_twice(func):
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        func(*args, **kwargs)
    return wrapper_do_twice

在shell中运行:

>>> 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装饰器可以解决这个问题:

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中运行:

>>> 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函数的元数据被保留下来,包括函数名、文档字符串等。

二、更复杂的装饰器

嵌套装饰器

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

>>> from decorators import debug, do_twice

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

带参数的装饰器

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

from decorators import repeat

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


greet("World")

实现方法如下:

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部分的行为,就和我们之前定义的普通装饰器一样:

...
    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装饰器的引用。

在装饰器中跟踪状态

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

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__()方法:

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

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

>>> counter = Counter()
>>> counter()
Current count is 1

>>> counter()
Current count is 2

>>> counter.count

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

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)

使用方法:

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

原文链接


http://www.kler.cn/a/318285.html

相关文章:

  • 怎么选择香港服务器的线路?解决方案
  • python——面向对象
  • 深入探索离散 Hopfield 神经网络
  • 【大数据学习 | flume】flume的概述与组件的介绍
  • Diffusion Policy——斯坦福机器人UMI所用的扩散策略:从原理到其编码实现(含Diff-Control、ControlNet详解)
  • apache2配置多站点
  • 【Linux探索学习】第一弹——Linux的基本指令(上)——开启Linux学习第一篇
  • SpringCloud微服务消息驱动的实践指南
  • 环境部署-环境变量
  • LeetCode - 2207. 字符串中最多数目的子序列
  • 【推文制作】秀米简明教程 1.0
  • Rolling Update
  • 一文说透RTMP、RTSP、RTP、HLS、MPEG-DASH
  • Gateway--服务网关
  • 如何把pdf转换成word文档?6种转换方法看完就学会
  • 《关键跃升读书笔记》11
  • 大模型框架 LangChain 介绍
  • Oracle数据库安装与SQL*Plus使用
  • 需求2:新加字段
  • 语言的副作用
  • NPM如何切换淘宝镜像进行加速
  • 什么是前端开发 ?
  • 【优选算法之双指针】No.2--- 经典双指针算法(下)
  • VMware安装ubuntu24.04桌面版
  • Linux下一些命令使用
  • FPGA-Vivado-IP核-虚拟输入输出(VIO)