-
业务需求
-
遵循软件规则和约定
-
解决方案
-
总结
-
应用
一、业务需求
原有设置五班学生总人数函数如下:
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()