Python装饰器

  1. 业务需求

  2. 遵循软件规则和约定

  3. 解决方案

  4. 总结

  5. 应用

一、业务需求

原有设置五班学生总人数函数如下:

def setstudent(param1):
    total=param1
    print("五班现在学生总人数为%d人" %total)

setstudent(100)

现在因业务保密需要,需要改成只有知道管理员密码的人才能修改五班总人数。

二、遵循软件规则和约定:

1、不能违背开闭原则

2、代码优雅,不写重复代码

3、调用名字不变,可增加代码,不能修改调用代码

三、解决方案:

1、所有调用处增加密码校验

 def setstudent(param1):
    total=param1
    print("五班现在学生总人数为%d人" %total)

    if input()=='888':    #调用之前增加
        setstudent(100)   #调用处
    else:                 #调用之后增加
        print("密码错误")  #调用之后增加

违背了上述第二条中的原则2:重复代码

2、直接函数内部修改代码

def setstudent(param1):
    print("please input your password:")
    if input()=='888':
        total=param1
        print("五班现在学生总人数为%d人" %total)
    else:
        print("密码错误")
    return

setstudent(100)

违背了上述第二条中的原则1:开闭原则

3、用新函数封装原函数

3.1、直接用新名字函数封装原函数

def setstudent(param1):
    total=param1
    print("五班现在学生总人数为%d人" %total)

def setstudentbypassword(param1):
    print("please input your password:")
    if input()=='888':
        setstudent(param1)   
    else:
        print("密码错误")
    return      

setstudentbypassword(100)

违背了上述第二条中的原则3:改变了函数调用名,造成调用处全部要修改

3.2、用同名函数封装原函数

3.3.1、Python中可以变量名调用函数

在Python中函数名可以赋值给变量,然后变量可以直接调用函数,和原函数名调用效果一样。本质就是变量和函数名都指向同一个地址,内部标记是函数调用(直接用函数地址变量调用不行)。

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

# 将函数赋值给一个变量
function_variable = greet

# 通过变量名加上括号和参数来调用函数
function_variable("Alice")

3.3.2、函数调用名不变

def setstudent(param1):
    total=param1
    print("五班现在学生总人数为%d人" %total)

原函数setstudent(param1)已经存在,现在还想用setstudent名来调用装饰之后的新函数,那么思路就是把这个新产生的函数地址赋值给setstudent。原来setstudent这个变量指向原函数setstudent(param1),赋值之后则指向装饰之后的新函数了。

所以可见,装饰之后的函数,是调用处全部自动改变成为新函数调用了。

3.3.3、新函数设计

    综上所述,新函数必须满足如下要求:

  • 增加了想要的功能
  • 包含了原函数的功能

要想达到上述目的,可以有两种设计思路:

思路一:
新函数(旧函数名,旧函数参数1,旧函数参数2...,装饰器本身参数1,装饰器本身参数2...)
    新增加功能
    旧函数名(参数1,参数2...) #旧函数执行
    新增加功能
return 本身新函数名

代码实现:
def setstudentbypassword(setstudent,code):
    print("please input your password:")
    if input()=='888':
        setstudent(code)   
    else:
        print("密码错误")
    return  setstudentbypassword    

setstudent=setstudentbypassword
#TypeError: setstudentbypassword() missing 1 required positional argument: 'code'
setstudent(100)

由此可见,要想和原来旧函数保持一致调用,参数形式也不能改变
上述既想保留就函数名传入,又想把旧函数名指向新地址方式根本不行。


思路二:
新函数(装饰器本身参数1,装饰器本身参数2...)(旧函数名,旧函数参数1,旧函数参数2...)
    新增加功能
    旧函数名(参数1,参数2...) #旧函数执行
    新增加功能
return 本身新函数名

代码实现
def setstudentbypassword(number)(code):
    print("please input your password:")
    if input()==code:
        setstudent(number)   
    else:
        print("密码错误")
    return  setstudentbypassword    

setstudentbypassword(100)(888)
同样没有办法保持原来调用形式不变。
思路三(即为现在装饰器):
新函数(旧函数名)
    接受旧函数参数
    新增加功能
    旧函数名(参数1,参数2...) #旧函数执行
    新增加功能
return 内部函数名


def setstudent(param1):
    total=param1
    print("五班现在学生总人数为%d人" %total)


def setstudentbypassword(fun):
    def warrapper(*args,**kwargs):  
        print("please input your password:")
        if input()=='888':
            fun(args)  
        else:
            print("密码错误")
        return
    return  warrapper    

#第一次赋值时,只是把函数名传入新函数,并不执行内嵌套函数,而且刚好把新函数地址返回,赋值给旧函数名地址
setstudent=setstudentbypassword(setstudent)

#因为现在就函数名变量已经指向新函数地址了,直接可以按旧方式调用
setstudent(100)

四、总结

还尝试多种方式,没有比装饰器更好的办法了。

当第一次把新函数地址赋值给旧函数名变量时,因为函数嵌套中只是定义函数,没有调用函数,故此时并不执行旧函数(通常是warrap函数中全部内容),只是把新函数地址赋值给了就函数名变量了。然后当再调用旧函数名时,这时调用名不变,调用方式也不变,直接调用新功能的函数。

整个思路:

需要装修一个函数,那么我成立个新函数公司,你把你要装修的函数放进来,你的函数我不做任何改变,我开始装修外部功能,装修完成后,返回函数装修过的新函数。

然后第一次用新装修公司调用旧函数名,那么调用的函数传入了装修公司中的,返回新函数地址值赋值给旧函数名。然后当再开始调用就函数名函数时,这时候旧函数实际是旧瓶装新酒,已经指向新函数了。巧妙实现了旧函数名调用方式不变,但是实现了功能改变,符合软件设计原则和约定。

五、应用

在AOP编程中,装饰器可以很好解决问题。

装饰器优点在于还可以模版化,某个装饰器,可以对不同函数装饰某个相同功能。

比如所有函数都想统计函数执行时间,可以做一个装饰器,装饰器本身功能就是统计函数时间,任何函数想要统计执行时间,就可以放进装饰器装饰,即可以得到额外增加的统计时间功能。

import time

def timing_decorator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} ran in: {end_time - start_time} secs")
        return result
    return wrapper

@timing_decorator
def example_function(n):
    sum = 0
    for i in range(n):
        sum += i
    return sum

@timing_decorator
def another_example():
    time.sleep(1)  # 模拟耗时操作

# 使用装饰器
example_function(1000000)
another_example()

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值