文章目录
这里是一段防爬虫文本,请读者忽略。
本文原创首发于CSDN,作者IDYS
博客首页:https://blog.csdn.net/weixin_41633902/
本文链接:https://blog.csdn.net/weixin_41633902/article/details/108038071
未经授权,禁止转载!恶意转载,后果自负!尊重原创,远离剽窃!
写在开头的话
- 请记住:实践是掌握知识的最快方法
- 如果你只是怀着看看的态度去快速浏览文章,而不去认认真真的把文章里面讲的任何一个知识点去实践一遍,那么你永远也掌握不了它
- 生命不息,折腾不止!
Python之functools
00. functools模块
-
partial
方法- 偏函数,把函数部分的参数固定下来,相当于为部分的参数添加了一个固定的默认值,形成一 个新的函数并返回
- 从
partial
生成的新函数,是对原函数的封装
-
代码演示1
import functools
def add(x, y):
print("x={}\ty={}".format(x,y))
return x + y
add1 = functools.partial(add, 1, 2)
print(add1())
add2 = functools.partial(add, y=1)
print(add2(10))
add3 = functools.partial(add, x=1)
print(add3(y=10))
add4 = functools.partial(add, 10)
print(add4(20))
- 运行结果
x=1 y=2
3
x=10 y=1
11
x=1 y=10
11
x=10 y=20
30
- 代码演示2
import functools
import inspect
def add(x, y) -> int:
return x + y
newadd = functools.partial(add, y=5) # 相当于提前在函数中放了一个参数 y = 5
print(newadd(7))
print(newadd(7, y=6))
print(newadd(y=10, x=6))
print(inspect.signature(newadd))
- 运行结果
12
13
16
(x, *, y=5) -> int
- 代码演示3
import functools
import inspect
def add(x, y, *args):
print(args)
return x + y
add1 = functools.partial(add, 1, 2, 3, 4, 5, 6, 7)
print(add1(20, 50, 90))
add2 = functools.partial(add, 1)
add2(12, 45, 67, 78)
sig = inspect.signature(add2)
print(sig)
print(sig.parameters)
print(sig.return_annotation)
for key, value in sig.parameters.items():
print(key, value.annotation)
print(value.annotation is value.empty)
- 运行结果
(3, 4, 5, 6, 7, 20, 50, 90)
3
(45, 67, 78)
(y, *args)
OrderedDict([('y', <Parameter "y">), ('args', <Parameter "*args">)])
<class 'inspect._empty'>
y <class 'inspect._empty'>
True
args <class 'inspect._empty'>
True
partial
函数本质
def partial(func, *args, **keywords): # partial 源码
def newfunc(*fargs, **fkeywords): # 包装函数
newkeywords = keywords.copy()
newkeywords.update(fkeywords)
return func(*(args + fargs), **newkeywords)
newfunc.func = func # 保留原函数
newfunc.args = args # 保留原函数的位置参数
newfunc.keywords = keywords # 保留原函数的关键字参数参数
return newfunc
def add(x, y):
return x+y
foo = partial(add, 4)
print(foo(5))
- 运行结果
9
01. functools模块的 lru_cache 装饰器
@functools.lru_cache(maxsize=128, typed=False)
Least-recently-used
装饰器。lru
,最近最少使用。cache
缓存- 如果
maxsize
设置为None
,则禁用LRU
功能,并且缓存可以无限制增长。当maxsize
是二的幂 时,LRU
功能执行得最好 - 如果
typed
设置为True
,则不同类型的函数参数将单独缓存。例如,f(3)
和f(3.0)
将被视为具有不同结果的不同调用
- 演示
import functools
import time
@functools.lru_cache()
def add(x, y):
time.sleep(3)
return x + y
if __name__ == "__main__":
print(add(1, 2))
print(add(1, 2.0)) # 没有等待
print(add(1, y=2))
print(add(y=2, x=1))
print(add(x=1, y=2))
print(add(1, 2)) # 没有等待
- 运行结果
3
3
3
3
3
3
- 项目演示2
dict_test = dict(x=123, y=234, z=890)
dict_test1 = dict(y=234, x=123, z=890)
print(dict_test)
sorted_item = sorted(dict_test.items())
sorted_item1 = sorted(dict_test1.items())
print(sorted_item)
print(sorted_item1)
test_zero = tuple()
print(test_zero)
for item in dict_test.items():
test_zero += item
print(test_zero)
- 运行结果
{'x': 123, 'y': 234, 'z': 890}
[('x', 123), ('y', 234), ('z', 890)]
[('x', 123), ('y', 234), ('z', 890)]
()
('x', 123, 'y', 234, 'z', 890)
-
lru_cache
装饰器通过一个字典缓存被装饰函数的调用和返回值 -
斐波那契数列递归方法的改造
import functools
@functools.lru_cache()
def fbin(n):
if n < 2:
return n
else:
return fbin(n -1) + fbin(n -2)
if __name__ == "__main__":
print(fbin(100))
lru_cache
装饰器应用- 使用前提
- 同样的函数参数一定得到同样的结果
- 函数执行时间很长,且要多次执行
- 本质是函数调用的参数=>返回值
- 缺点
- 不支持缓存过期,
key
无法过期、失效 - 不支持清除操作
- 不支持分布式,是一个单机的缓存
- 不支持缓存过期,
- 适用场景,单机上需要空间换时间的地方,可以用缓存来将计算变成快速的查询
- 使用前提
02. 利用装饰器写一个 lru(least-recently-used)的缓存系统
- 要求:
- 写一个缓存系统
- 对缓存过的函数的值不用继续求值,直接得出结果
- 对缓存中的数,设置超时时间,如果缓存中某个数的时间超过了设定的时间,那么则在缓存中清除这个数
- 利用装饰器来实现这个功能
- 代码实现
import inspect
import functools
import time
import datetime
def logger(duration): # 装饰器传入的时间
def _logger(fn):
local_cache = {} # 设置缓存
@functools.wraps(fn) # 属性复制,保持函数原有的属性
def wrapper(*args, **kwargs):
def expired_clear(my_local_cache): # 缓存过期清除
dict_remove = list()
for cache_key, cache_value in my_local_cache.items():
if datetime.datetime.now().timestamp() - cache_value[1] > duration:
dict_remove.append(cache_key)
for dict_k in dict_remove:
my_local_cache.pop(dict_k)
print(my_local_cache) # 主要是为了测试而添加,实际应该删除这行代码。但是为了直观的看到缓存过期清理功能。所以添加这一行
expired_clear(local_cache)
def make_key(key_fn): # 设置函数表示符,达到命中
sig = inspect.signature(key_fn)
sig_par = sig.parameters
sig_par_key_tuple = tuple(sig_par.keys())
compare_dict = dict()
for index, a_value in enumerate(args):
compare_dict[sig_par_key_tuple[index]] = a_value
compare_dict.update(kwargs)
for k in sig_par.keys():
if k not in compare_dict.keys():
compare_dict[k] = sig_par[k].default
dict_key = tuple(sorted(compare_dict.items()))
return dict_key
key = make_key(fn)
if key not in local_cache: # 看传入的函数,是否已经存在于缓存当中,如果存在。那么就直接得出值。不存在则计算存入缓存当中
ret = fn(*args, **kwargs)
local_cache[key] = (ret, datetime.datetime.now().timestamp())
else:
local_cache[key] = (local_cache[key][0], datetime.datetime.now().timestamp())
return local_cache[key][0] # 返回函数的值
return wrapper
return _logger
@logger(6) # 装饰器修饰
def add(x=11, y=10):
time.sleep(3)
return x + y
print(add())
print(add(11))
print(add(11, 10))
print(add(11, y=10))
print(add(y=10, x=11))
print(add(x=11, y=10))
print(add(34))
print(add(45))
print(add(12))
- 运行结果
{}
21
{(('x', 11), ('y', 10)): (21, 1597545428.264142)}
21
{(('x', 11), ('y', 10)): (21, 1597545428.264142)}
21
{(('x', 11), ('y', 10)): (21, 1597545428.264142)}
21
{(('x', 11), ('y', 10)): (21, 1597545428.264142)}
21
{(('x', 11), ('y', 10)): (21, 1597545428.264142)}
21
{(('x', 11), ('y', 10)): (21, 1597545428.264142)}
44
{(('x', 11), ('y', 10)): (21, 1597545428.264142), (('x', 34), ('y', 10)): (44, 1597545431.264453)}
55
{(('x', 34), ('y', 10)): (44, 1597545431.264453), (('x', 45), ('y', 10)): (55, 1597545434.264718)}
22
03. 写一个命令分发器
- 将某个函数注册到对应的某一个命令
- 如果命令对应的函数重复,则报错
- 提供注销命令的功能,如果注销对应命名没有则报错
- 如果输入没有注册的命令,那么则提示该命令没有找到
- 代码演示
def reg_dispatcher():
command = {}
def reg(name):
def _reg(fn):
if name in command.keys():
error = "{} is repeated ".format(name)
raise Exception(error)
else:
command.setdefault(name, fn)
return fn
return _reg
def default():
print("your command is not founded")
def dispatcher():
while True:
cmd = input(">>")
if cmd.strip() == "quit":
break
command.get(cmd.strip(), default)()
def cancel(name):
if name not in command.keys():
error = "the command name is not founded "
raise Exception(error)
else:
command.pop(name)
return reg, dispatcher, cancel
reg, dispatcher, cancel = reg_dispatcher()
@reg("welcome") # pywel = reg("welcome")(pywel)
def py_wel():
print("welcome to python")
@reg("go")
def go_prin():
print("go out")
cancel("go")
print(py_wel.__doc__)
dispatcher()
- 运行结果
>>welcome
welcome to python
>>go
your command is not founded
>>
04. 装饰器的用途
- 装饰器是
AOP
面向切面编程Aspect Oriented Programming
的思想体现 - 面向对象往往需要通过继承或者组合依赖等方式调用一些功能,这些功能的代码往往可能在多个类中出现,例如
logger
。这样会造成代码的重复,增加了耦合。logger
的改变影响所有使用它的类或方法 - 而
AOP
在需要的类或者方法上切下,前后的切入点可以加入增强的功能,让调用者和被调用者解耦 - 这是一种不修改原来的业务代码,给程序动态添加功能的的技术,例如
logger
函数功能就是对业务函数增加日志的,而业务函数中应该把与业务无关的日志功能剥离干净
05. 装饰器应用场景
- 日志、监控、权限、设计、参数检查、路由等处理
- 这些功能与业务功能无关,很多业务都需要的公共功能,所以独立出来,需要的时候对目标对象增强。
写在最后的话:
- 无论每个知识点的难易程度如何,我都会尽力将它描绘得足够细致
- 欢迎关注我的CSDN博客,IDYS’BLOG
- 持续更新内容:
linux基础 | 数据通信(路由交换,WLAN) | Python基础 | 云计算 - 如果你有什么疑问,或者是难题。欢迎评论或者私信我。你若留言,我必回复!
- 虽然我现在还很渺小,但我会做好每一篇内容。谢谢关注!