Day 26:Python 中的装饰器是什么?

Python 中使用 @函数名字,放在某些函数上面,起到增强它们功能的作用。

  • 一切皆对象
  • 在函数中定义函数
  • 从函数中返回函数(既然可以创建嵌套的函数。那函数也能返回函数)
复制代码
def hi(name="piayie"):
    def greet():
        return "now you are in the greet() function"
 
    def welcome():
        return "now you are in the welcome() function"
 
    if name == "pieyie":
        return greet
    else:
        return welcome
 
a = hi()
print(a)

output:
<function hi.<locals>.greet at 0x6fff96c98c8>
复制代码

注意返回的是 greet 和 welcome,而不是 greet() 和 welcome()。这是因为当你把一对小括号放在后面,这个函数就会执行;然而如果你不放括号在它后面,那它可以被到处传递,并且可以赋值给别的变量而不去执行它。

  • 还可以做到将函数作为参数传给另一个函数
复制代码
def hi():
    return "hi piayie!"
 
def doSomethingBeforeHi(func):
    print("I am doing some boring work before executing hi()")
    print(func())
 
doSomethingBeforeHi(hi)

output:
I am doing some boring work before executing hi()
hi piayie!
复制代码

 

增强函数功能,方便重复调用,但是函数本身的功能的执行也是需要的,并不是说把函数直接变成了另一个函数

装饰器有助于让我们的代码更简短,也更Pythonic。

会使得代码更加简洁,代码的复用性大大提升。

因此,装饰器被广泛使用。Python 中到处都能看到装饰器的身影。

不光在 Python,其他语言如 Java 中,装饰器被称作注解,也被应用广泛。

Python 前端最流行的框架之一 Flask,URL 地址和 controller 层的映射处理函数,就是使用装饰器。

复制代码
# import Flask,同时创建一个 app:
from flask import Flask
app = Flask(__name__)

#URL 路径 / 与处理函数建立映射关系,正是通过装饰器 app.route 控制层处理响应前端,并返回字符串 hello world 到前端界面。
@app.route('/')
def index():
    return "hello world"




# Python 支持异步编程,从中也能看到装饰器的身影

# 使用装饰器 @asyncio.coroutine,将一个生成器asyncio_open_conn 标记为 coroutine 类型
import asyncio

@asyncio.coroutine
def asyncio_open_conn(host:str):
    print("using asyncio builds web connection")
    connect = yield from asyncio.open_connection(host, 80)
    print("get connect of %s"%(host,))

loop = asyncio.get_event_loop()
connections = [asyncio_open_conn(host) for host in [
    'www.sina.com', 'www.baidu.com']]
loop.run_until_complete(asyncio.wait(connections))
loop.close()

output:
using asyncio builds web connection
using asyncio builds web connection
get connect of www.sina.com
get connect of www.baidu.com
复制代码

下面介绍几个装饰器例子来理解什么是装饰器

call_print 装饰器

# 定义函数 call_print,它的入参 f 为一个函数,它里面内嵌一个函数 g,并返回函数 g
def call_print(f):
    def g():
        print('you\'re calling %s function' % (f.__name__,))
    return g
  • Python 中,@call_print 函数,放在别的🐍么函数上面,函数 call_print 就变为装饰器。
  • 变为装饰器后,我们不必自己去调用函数 call_print。
@call_print
def myfun():
    pass


@call_print
def myfun2():
    pass

直接调用被装饰的🐍么函数,就能调用到 call_print,观察输出结果:

复制代码
# 定义函数 call_print,它的入参 f 为一个函数,它里面内嵌一个函数 g,并返回函数 g
def call_print(f):
    def g():
        print('you\'re calling %s function' % (f.__name__,))
    return g


def my_chain(*iterables):
        for it in iterables:
            for element in it:
                yield element
@call_print
def myfun():       
    chain_it = my_chain(['I','love'],['Flower Dance'],['very', 'much',['very', 'much']])
    for _ in chain_it:
        print(_)


@call_print
def myfun2():
    pass

myfun()
myfun2()

output:
you're calling myfun function
you're calling myfun2 function
复制代码
  • @call_print 放置在任何一个新定义的函数上面。都会默认输出一行,输出信息:正在调用这个函数的名称。

@call_print 放新定义函数的上面实现了这样的调用:

myfun = call_print(myfun)
myfun2 = call_print(myfun2)

call_print(myfun) 后返回一个函数,然后,我们再将它赋值给被传入的函数 myfun。

myfun 指向了被包装后的函数 g,并移花接木到函数 g,使得 myfun 额外具备了函数 g 的一切功能,变得更强大。

wraps 装饰器

再次打印myfun()和myfun

复制代码
myfun()
myfun2()
print(myfun)
print(myfun2)

output:
you're calling myfun function
you're calling myfun2 function
<function call_print.<locals>.g at 0x6fffdd2af28>
<function call_print.<locals>.g at 0x6fffdd2ad08>
复制代码

被装饰的函数 myfun 名字竟然变为 g,也就是定义装饰器 call_print 时使用的内嵌函数 g

而且函数myfun本身的函数却并没有被执行,就像是完全被替换了一个函数,看:

复制代码
from functools import wraps

def call_print(f):
    @wraps(f)
    def g():
        print('you\'re calling %s function' % (f.__name__,))
    return g

def my_chain(*iterables):
        for it in iterables:
            for element in it:
                yield element
# @call_print
def myfun():       
    chain_it = my_chain(['I','love'],['Flower Dance'],['very', 'much',['very', 'much']])
    for _ in chain_it:
        print(_)


@call_print
def myfun2():
    chain_it = my_chain(['I','love'],['Last Dance'],['very', 'much',['very', 'much']])
    for _ in chain_it:
        print(_)

myfun()
myfun2()
print(myfun)
print(myfun2)

output:
I
love
Flower Dance
very
much
['very', 'much']
you're calling myfun2 function
<function myfun at 0x6fffdd089d8>
<function myfun2 at 0x6fffdd08ae8>
复制代码

现在函数名倒是正常了,可原先myfun2()的函数体还是没有执行???!😱

补充说明

并不是完全替换,要看装饰器函数中要不要执行原先的函数,有没有把函数当作参数传进去并且再里面调用:

复制代码
def a_new_decorator(a_func):
 
    def wrapTheFunction():
        print("I am doing some boring work before executing a_func()")
 
        a_func()
 
        print("I am doing some boring work after executing a_func()")
 
    return wrapTheFunction
 
def a_function_requiring_decoration():
    print("I am the function which needs some decoration to remove my foul smell")
 
a_function_requiring_decoration()

output:
I am the function which needs some decoration to remove my foul smell
复制代码

但是如果这样写:(这实际上是@a_new_decorator做的事情)

# 把函数a_function_requiring_decoration当作参数传给函数a_new_decorator
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)

输出就会大不一样:

a_function_requiring_decoration()
output:

I am doing some boring work before executing a_func()
I am the function which needs some decoration to remove my foul smell
I am doing some boring work after executing a_func()

现在尝试一下使用@符号达到上面的效果

复制代码
def a_new_decorator(a_func):
 
    def wrapTheFunction():
        print("I am doing some boring work before executing a_func()")
 
        a_func()
 
        print("I am doing some boring work after executing a_func()")
 
    return wrapTheFunction
@a_new_decorator
def a_function_requiring_decoration():
    print("I am the function which needs some decoration to remove my foul smell")
 
a_function_requiring_decoration()

output:
I am doing some boring work before executing a_func()
I am the function which needs some decoration to remove my foul smell
I am doing some boring work after executing a_func()
复制代码

现在试一下执行myfun里面的函数

复制代码
from functools import wraps

def call_print(f):
    @wraps(f)
    def g():
        print('you\'re calling %s function' % (f.__name__,))
        print('and I begin to execute the function f who is a parameter for call_print') 
        f()
        print('Now I done!') 
    return g

def my_chain(*iterables):
        for it in iterables:
            for element in it:
                yield element
@call_print
def myfun():       
    chain_it = my_chain(['I','love'],['Flower Dance'],['very', 'much'])
    for _ in chain_it:
        print(_)


@call_print
def myfun2():
    chain_it = my_chain(['I','love'],['Last Dance'],['very', 'much'])
    for _ in chain_it:
        print(_)

myfun()
myfun2()
print(myfun)
print(myfun2)

output:
you're calling myfun function
and I begin to execute the function f who is a parameter for call_print
I
love
Flower Dance
very
much
Now I done!
you're calling myfun2 function
and I begin to execute the function f who is a parameter for call_print
I
love
Last Dance
very
much
Now I done!
<function myfun at 0x6fffdf15e18>
<function myfun2 at 0x6fff35ac048>
复制代码

学fei了吗?啊🥱

 

 

案例 1:异常次数

复制代码
import time
import math

def excepter(f):
    i = 0
    t1 = time.time()
    def wrapper():
        try:
            f()
        except Exception as e:
            nonlocal i
            i += 1
            print(f'{e.args[0]}: {i}')
            t2 = time.time()
            if i == n:
                print(f'spending time:{round(t2-t1,2)}')
    return wrapper
复制代码

使用创建的装饰函数 excepter,n 是异常出现的次数。

  • 被零除的情况显然是要报错的,然后没有执行那块代码,去泡装饰器函数@excepter了
  • 数组显然是越界了,但是没有报错,同理
复制代码
n = 10# except count

@excepter
def divide_zero_except():
    time.sleep(0.1)
    j = 1/(40-20*2)

# test zero divived except
for _ in range(n):
    divide_zero_except()

output:
division by zero: 1
division by zero: 2
division by zero: 3
division by zero: 4
division by zero: 5
division by zero: 6
division by zero: 7
division by zero: 8
division by zero: 9
division by zero: 10
spending time:1.08
复制代码
复制代码
@excepter
def outof_range_except():
    a = [1,3,5]
    time.sleep(0.1)
    print(a[3])

# test out of range except
for _ in range(n):
    outof_range_except()

output:
list index out of range: 1
list index out of range: 2
list index out of range: 3
list index out of range: 4
list index out of range: 5
list index out of range: 6
list index out of range: 7
list index out of range: 8
list index out of range: 9
list index out of range: 10
spending time:1.1
复制代码

案例 2:绘图

复制代码
import numpy as np
from scipy.stats import beta, norm, uniform, binom
import matplotlib.pyplot as plt
from functools import wraps


# 定义带四个参数的画图装饰器
# 使用 wraps,包装了内嵌函数 myplot。这样做的好处,被包装的函数名字不会被改变
# 绘图装饰器带有四个参数分别表示,legend 的两类说明文字,y 轴 label,保存的 PNG 文件名称。
def my_plot(label0=None, label1=None, ylabel='probability density function', fn=None):
    def decorate(f):
        @wraps(f)
        def myplot():
            fig = plt.figure(figsize=(16, 9))
            ax = fig.add_subplot(111)
            x, y, y1 = f()
            ax.plot(x, y, linewidth=2, c='r', label=label0)
            ax.plot(x, y1, linewidth=2, c='b', label=label1)
            ax.legend()
            plt.ylabel(ylabel)
            # plt.show()
            plt.savefig('./img/%s' % (fn,))
            plt.close()
        return myplot
    return decorate


# 均匀分布
@my_plot(label0='b-a=1.0', label1='b-a=2.0', fn='uniform.png')
def unif():
    x = np.arange(-0.01, 2.01, 0.01)
    y = uniform.pdf(x, loc=0.0, scale=1.0)
    y1 = uniform.pdf(x, loc=0.0, scale=2.0)
    return x, y, y1

# 二项分布
@my_plot(label0='n=50,p=0.3', label1='n=50,p=0.7', fn='binom.png', ylabel='probability mass function')
def bino():
    x = np.arange(50)
    n, p, p1 = 50, 0.3, 0.7
    y = binom.pmf(x, n=n, p=p)
    y1 = binom.pmf(x, n=n, p=p1)
    return x, y, y1

# 高斯分布
@my_plot(label0='u=0.,sigma=1.0', label1='u=0.,sigma=2.0', fn='guass.png')
def guass():
    x = np.arange(-5, 5, 0.1)
    y = norm.pdf(x, loc=0.0, scale=1.0)
    y1 = norm.pdf(x, loc=0., scale=2.0)
    return x, y, y1

# beta 分布
@my_plot(label0='a=10., b=30.', label1='a=4., b=4.', fn='beta.png')
def bet():
    x = np.arange(-0.1, 1, 0.001)
    y = beta.pdf(x, a=10., b=30.)
    y1 = beta.pdf(x, a=4., b=4.)
    return x, y, y1

# 调用
distrs = [unif, bino, guass, bet]
for distri in distrs:
    distri()
复制代码

 案例三:@classmethod和@staticmethod

移除元素:27. 移除元素 - 力扣(LeetCode) (leetcode-cn.com)

python双指针解法:

复制代码
from typing import List
class movValSolution:
    """双指针法
    时间复杂度:O(n)
    空间复杂度:O(1)
    """
    @classmethod
    def removeElement(cls, nums: List[int], val: int) -> int:
        fast = slow = 0

        while fast < len(nums):

            if nums[fast] != val:
                nums[slow] = nums[fast]
                slow += 1

            # 当 fast 指针遇到要删除的元素时停止赋值
            # slow 指针停止移动, fast 指针继续前进
            fast += 1

        return slow
复制代码

细心的张同学发现俩个不太熟悉的东西

  • @classmethod
  • def removeElement(cls, 

深入浅析python 中的self和cls的区别 - 云+社区 - 腾讯云 (tencent.com)

描述:

self是类(Class)实例化对象,cls就是类(或子类)本身,取决于调用的是那个类。

@staticmethod 属于静态方法装饰器,@classmethod属于类方法装饰器。我们需要从声明和使用两个方面来理解。

即我要么通过类的实例来调用类中的函数,要么直接通过类的方法调用那个函数

1
2
3
a = movValSolution()
print(a.removeElement([1,2,3,4,5,6,3,2,1,4,3,3,3],3))
print(movValSolution.removeElement([1,2,3,4,5,6,3,2,1,4,3,3,3],3))

  

posted @   PiaYie  阅读(81)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示