函数定义的弊端
函数注解Function Annotations
业务应用
inspect模块
#示例
import inspect
def add(x,y:int,*args,**kwargs) -> int:
return x+y
sig = inspect.signature(add)
print('sig:',sig)
print('params :',sig.parameters)#Ordereddict
print('return :',sig.return_annotation)
print(sig.parameters['x'])
print(sig.parameters['y'])
print(sig.parameters['y'].annotation)
print(sig.parameters['args'])
print(sig.parameters['args'].annotation)
print(sig.parameters['kwargs'])
print(sig.parameters['kwargs'].annotation)
print(sig.parameters['kwargs'].empty)
print(sig.parameters['kwargs'].kind)
'''
sig: (x, y:int, *args, **kwargs) -> int
params : OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y:int">), ('args', <Parameter "*args">), ('kwargs', <Parameter "**kwargs">)])
return : <class 'int'>
x
y:int
<class 'int'>
*args
<class 'inspect._empty'>
**kwargs
<class 'inspect._empty'>
<class 'inspect._empty'>
VAR_KEYWORD
'''
业务应用
functools模块进阶
#源码 _make_key _HashSeq
def _make_key(args, kwds, typed,
kwd_mark = (object(),),
fasttypes = {int, str, frozenset, type(None)},
tuple=tuple, type=type, len=len):
key = args
if kwds:
key += kwd_mark
for item in kwds.items():
key += item
if typed:
key += tuple(type(v) for v in args)
if kwds:
key += tuple(type(v) for v in kwds.values())
elif len(key) == 1 and type(key[0]) in fasttypes:
return key[0]
return _HashedSeq(key)
class _HashedSeq(list):
__slots__ = 'hashvalue'
def __init__(self, tup, hash=hash):
self[:] = tup
self.hashvalue = hash(tup)
def __hash__(self):
return self.hashvalue
#利用缓存可以大大提高效率,类似于用空间换取时间!注意缓存与缓冲的区别!
import functools
@functools.lru_cache()
def fib(n):
if n<2:
return n
else:return fib(n-1)+fib(n-2)
print(fib(100))
装饰器练习*****
#第一题
#装饰器的应用
#实现一个cache装饰器,实现可过期的被清除的功能
'''
简化设计,函数的形参定义不包括可变位置参数、可变关键词参数和keyword-only参数
可以不考虑缓存满了之后的换出问题。
数据类型的选择:
缓存的应用场景,是有数据需要频繁查询,且每次查询都需要大量计算或等待时间之后才能返回结果的情况,
使用缓存来提高查询速度,用空间换取时间。
cache应该选用什么数据结构?
便于查询的,且能快速找到的数据结构。每次查询的时候,只要输入一致,就应该得到同样的结果(顺序也一致,例如减法函数,参数顺序不一致,结果不一样)
基于上面的分析,此数据结构应该是字典。通过一个key,对应一个value。
key是参数列表组成的结构,value是函数返回值。难点在于key如何处理!
key的存储
key必须是hash类,key能接受位置参数和关键字参数传参,位置参数是被收集在一个tuple中的,本身就有序
关键字参数被收集在一个字典中,本身无序,这会带来一个问题,传参的顺序未必是字典中保存的顺序。如何解决?
OrderedDict行吗?可以,它可以记录顺序。
不用OrderedDict行吗?可以,用一个tuple保存排过序的字典的item的kv对。
key的异同
什么才算是相同的key呢?定义一个加法函数,那么传参方式就应该有以下4种:
1.add(4,5)
2.add(4,y=5)
3.add(x=4,y=5)
4.add(y=5,x=4)
上面4种,可以有下面两种理解:
第一种: 3和4相同,1,2和3都不同。
第二种: 1、2、3、4全部相同。
lru_cache实现了第一种,可以看出单独的处理了位置参数和关键字参数。
但是函数定义为def add(4,y=5),使用了默认值,如何理解add(4,5)和add(4)是否一样呢?
如果认为一样,那么lru_cache无能为力,就需要使用inspect来自己实现算法。
key的要求
key必须是hashable,由于key是所有实参组合而成,而且最好要作为key的,key一定要可以hash,但是如果key有不可hash类型数据,就无法完成。
lru_cache就不可以,缓存必须使用key,但是key必须可hash,所以只能适用实参是不可变类型的函数调用。
key算法设计
inspect模块获取函数签名后,取parameters,这是一个有序字典,会保存所有参数的信息。
构建一个字典params_dict,按照位置顺序从args中依次对应参数名和传入的实参,组成kv对,存入params_dict中。
kwargs所有值update到params_dict中。
如果使用了缺省值的参数,不会出现在实参params_dict中,会出现在签名parameters中,缺省值也定义在其中。
调用的方式
普通的函数调用可以,但是过于明显,最好类似lru_cache的方式,让调查者无察觉的使用缓存。
构建装饰器函数
过期功能
一般缓存系统都有过期功能。
过期什么呢?
它是某一个key过期。可以对每一个key单独设置过期时间,也可以对这些key统一设定过期时间。
本次的实现就简单点,统一设置key的过期时间,当key生存超过了这个时间,就自动被清除。
注意:这里并没有考虑多线程等问题。而且这种过期机制,每一次都有遍历所有数据,大量数据的时候,
遍历可能有效率问题。
在下面的装饰器中增加一个参数,需要用到了带参装饰器了。
@mag_cache(5)代表key生存5秒钟后过期。
带参装饰器相当于在原来的装饰器外面再嵌套一层。
清除的时机,何时清除过期key?
1. 用到某个key之前,先判断是否过期,如果过期重新调用函数生成新的key对应value值。
2.一个线程负责清除过期的key,这个后面实现。本次在创建key之前,清除所有过期的key。
value的设计
1.key =>(v,createtimestamp)
适合key过期的时间都是统一的设定
2.key =>(v,createtimestamp,duration)
duration是过期时间,这样每一个key就可以单独控制过期时间。在这种设计中,-1可以表示永不过期,
0可以表示立即过期,正整数表示持续一段时间过期。
这次采用第一种实现!
'''
1 #题目:实现一个cache装饰器,实现可过期、可自动清除的功能。
2
3 from functools import wraps
4 import time
5 import inspect
6 import datetime
7
8 def cache(duration):
9 def _cache(fn,):
10 local_cache = {}
11 @wraps(fn)
12 def wrapper(*args,**kwargs):
13 #local_cache中有没有过期的key
14 # for k,(_,ts) in local_cache:
15 # if datetime.datetime.now().timestamp()- ts>5:
16 # local_cache.pop(k) #不能一边迭代一边删除!
17 def clear_expire(cache):
18 expire_key = []
19 for k, (_, ts) in cache.items():
20 if datetime.datetime.now().timestamp() - ts > duration:
21 expire_key.append(k)
22 for k in expire_key:
23 cache.pop(k)
24 clear_expire(local_cache)
25
26 def make_key():
27 key_dict = {}
28 sig = inspect.signature(fn)
29 params = sig.parameters # 返回一个有序字典
30 params_list = list(params.keys()) # 返回参数列表
31 # 位置参数add(5,6,y=8)
32 for i, v in enumerate(args):
33 k = params_list[i]
34 key_dict[k] = v
35 # 关键字参数
36 # print('*kwargs', *kwargs)
37 # for k,v in kwargs.items():
38 # key_dict[k] = v
39 key_dict.update(kwargs)
40 # 缺省值参数
41 for k in params.keys():
42 if k not in key_dict.keys():
43 key_dict[k] = params[k].default
44 key = tuple(sorted(key_dict.items()))
45 # print('处理后得到的key:',key)
46 # print('处理后得到的local_cache:',local_cache)
47 return key
48
49 key = make_key()
50
51 if key not in local_cache.keys():
52 ret = fn(*args, **kwargs)
53 local_cache[key] = (ret,datetime.datetime.now().timestamp()) #
54
55 return local_cache[key]
56 return wrapper
57 return _cache
58
59 def logger(fn):
60 @wraps(fn)
61 def wrapper(*args,**kwargs):
62 start = datetime.datetime.now()
63 ret = fn(*args,**kwargs)
64 delta = (datetime.datetime.now() - start).total_seconds()
65 print(delta)
66 return ret
67 return wrapper
68
69 @logger
70 @cache(5)
71 def add(x,y=5):
72 time.sleep(3)
73 return x+y
74
75 add(4)
76 add(4,5)
77 add(x=4,y=5)
78 add(4,y=5)
79 add(y=5,x=4)
80
81 time.sleep(5)
82 print('*'*20)
83 add(4)
84 add(4,5)
85 add(x=4,y=5)
86 add(4,y=5)
87 add(y=5,x=4)
1 #2 题目:写一个命令分发器
2 """
3 1.程序员可以方便的注册函数到某一个命令,用户输入命令时,路由到注册的函数
4 2.如果此命令没有对应的注册函数,执行默认函数
5 3.用户输入用input(">>")
6 """
7 '''分析
8 输入命令映射到一个函数,并执行这个函数。应该是cmd_tbl[cmd]=fn的形式,字典正好合适。
9 如果输入了某一个cmd命令后,没有找到函数,就要调用缺省的函数执行,这正好是字典缺省参数。
10 cmd是字符串
11
12 实现主要功能后会发现代码布局很丑陋,最好是不要将所有的函数和字典都在全局中定义!
13 如何改进呢?将reg函数封装成装饰器,并用它来注册函数。
14
15 重复注册的问题
16 如果函数使用同一个函数名注册,就等于覆盖了原来的cmd到fn的关系,这样的逻辑也是合理的。
17 也可以加一个判断,如果key已经存在,重复注册,抛出异常。看业务需求。
18
19 注销
20 有注册就应该有注销
21 一般来说注销是要有权限的,但是什么样的人拥有注销的权限。
22 '''
23 command_dict = {}
24
25 #注册(带参装饰器)
26 def dispatch():
27
28 def req(name):
29 def wrapper(fn):
30 command_dict[name]=fn
31 return wrapper
32
33 def defaultfunc():
34 print('Unkown command!')
35
36 def dispatchfunc():
37 while True:
38 userinput = input('>>')
39 if userinput.strip() == 'quit':
40 return
41 if userinput in command_dict.keys():
42 command_dict.get(userinput,defaultfunc)()
43 else:
44 defaultfunc()
45 return req,dispatchfunc
46
47 req,dispatchfunc = dispatch()
48 #自定义函数
49 @req('cy') # f1=req('cy')(f1)
50 def f1():
51 print('chengyu')
52 @req('py')
53 def f2():
54 print('python')
55
56 dispatchfunc()
装饰器的应用
装饰器应用场景
作业