官方文档(机基于最新的py3.8,以前版本可能有些函数没有,比如cached_property):
https://docs.python.org/zh-cn/3/library/functools.html
小试牛刀:
偏函数:
import functools
def fun(a, b, c):
print("a is %d\nb is %d \nc is %d" %(a, b, c))
f = functools.partial(fun, b = 2)
f(1, c=2, b=3)
>>
a is 1
b is 3
c is 2
先说一个简单的知识点,也是以前在 py 小知识点总结过的。
就是 默认参数肯定是在 位置参数后面的。
穿参的时候,可以都是用 关键字的形式传过去,这时候如果是位置参数就会把形参的名字和关键字参数对应(默认是按照位置赋值的),这种穿参形式,也要注意也要符合 传的位置参数(传的时候不写 = 这种形式)要放在前面的原则。 否则汇报错的哦。
关键字参数一定要在位置参数的后面,无论是定义参数还是调用函数穿参
(如果调用的时候,都加 = 这种参数形式,那顺序就是摆设了,随便放都可以)
好了上面的东西对于理解 偏函数会有一定帮助的。
下面介绍functools:
functools 适用于 高阶函数。即参数或(和)返回值为其他函数的函数
就如上面的偏函数一样,返回的是一个新的函数对象
偏 函数 partial
就是 偏函数:
partial 函数 大致原理(来自官方的举例)
def partial(func, *args, **keywords):
def newfunc(*fargs, **fkeywords):
newkeywords = {**keywords, **fkeywords}
return func(*args, *fargs, **newkeywords)
newfunc.func = func
newfunc.args = args
newfunc.keywords = keywords
return newfunc
可以看出,partial 接受三个参数,分别是 旧函数对象, 可变参数, 关键字参数。并且用闭包形式,把旧函数对象,的参数进行扩充了。 位置参数他是放到了旧函数的左边,关键字参数进行合并。
partial 介绍
其实上面的 newfunc.xx = xx,大可不必写。因为闭包可以返回到 newfunc 里面(可能我的理解片面)
(注意虽然像装饰器,但是写成装饰器无法工作,因为是二级装饰器,无法穿参)
这里也学习了一点就是,普通函数也是可以像类一样 使用 .
操作符的。
装饰器 修饰 wraps
@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
这个用过了,就是 让 被装饰 函数函数签名保持不变(不加的话就变成了 装饰器里面的那个函数的签名了,虽然其功能不受音响),在使用装饰器的时候,最好写这个(新手请忽略)
这是一个便捷函数,用于在定义包装器函数时发起调用
update_wrapper()
作为函数装饰器。 它等价于partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)
。
举个例子:
>>> from functools import wraps
>>> def my_decorator(f):
... @wraps(f)
... def wrapper(*args, **kwds):
""'这是 wrapper"""
... print('Calling decorated function')
... return f(*args, **kwds)
... return wrapper
...
>>> @my_decorator
... def example():
... """Docstring"""
... print('Called example function')
...
>>> example()
Calling decorated function
Called example function
>>> example.__name__
'example'
>>> example.__doc__
'Docstring'
不加 wraps(f)
>>> example.__name__
wrapper
>>> example.__doc__
"这是 wrapper"
functools 还有 其他功能,后面用的时候总结吧:
- @functools.cached_property(func) # 3.8 新增功能
就是和 property 类似 把函数转化为一个 属性,但是增加了缓存功能
一次性计算该特征属性的值,然后将其缓存为实例生命周期内的普通属性
对于高频使用到的固定属性,这个就非常好用了。
每个实例上的 dict 属性是可变的映射(不适用于,元类等 没有提供 dict 属性的类)
大概原理就是,把实例使用 cached_property 装饰的 函数属性,算出来的值直接放到 实例的 dict 属性里面,下次取的时候,就不用算了,直接从 dict 里面拿就行了
举个例子,一个实例的声明周期内,调用很多 函数属性,会提高计算速度, 每个实例,只会计算一次 函数属性
- 使用 cached_property (如果是新的实例,他依然会计算一次)
import statistics
from functools import lru_cache, cached_property
class DataSet:
""" 使用 cached_property"""
def __init__(self, sequence_of_numbers):
self._data = sequence_of_numbers
@cached_property
def stdev(self):
return statistics.stdev(self._data) # 计算标准偏差
@cached_property
def variance(self):
return statistics.variance(self._data) # 样本方差
class DataSetOld:
"""不使用 cached_property"""
def __init__(self, sequence_of_numbers):
self._data = sequence_of_numbers
@property
def stdev(self):
return statistics.stdev(self._data) # 计算标准偏差
@property
def variance(self):
return statistics.variance(self._data) # 样本方差
import time
start = time.time()
new_li = DataSet([0, 100, 1.3])
old_li = DataSetOld([0, 100, 1.3])
for i in range(1000):
_ = new_li.stdev
__ = new_li.variance
print(new_li.stdev, new_li.variance)
end = time.time()
print("使用 cached_property:", end - start)
start = time.time()
for i in range(1000):
_ = old_li.stdev
__ = old_li.variance
print(old_li.stdev, old_li.variance)
end = time.time()
print("不使用 cached_property:",end-start)
>> 57.3634320219191 3290.5633333333335
使用 cached_property: 0.00194549560546875
57.3634320219191 3290.5633333333335
不使用 cached_property: 0.7226638793945312
- functools.cmp_to_key(func)
- @functools.lru_cache(user_function) # 同下个
- @functools.lru_cache(maxsize=128, typed=False)
在Python 3.7或更低版本上,您必须做@lru_cache()(在@lru_cache之后加上括号)
就是为 一个函数提供缓存功能,如果这个函数下次调用传入的参数一模一样,就直接返回那次相同参数的结果, 节约 高 IO 开销等,提高速度。6
注意参数不同的规则:
f(a=1, b=2) 和 f(b=2, a=1) 因其参数顺序不同,可能会被缓存两次
缓存 maxsize 组传入参数,默认是缓存 128 组结果,因该够用了, 超过也会有个刷新机制,应该是把 不常用的结果给移除。
typed : 是不是 严格区分参数, 比如typed=False, 会把 1.0 和1 当作相同的参数
不用 lru_cache
import time
# @lru_cache
def test_lru_cache(a, b):
time.sleep(3)
return a+b
start = time.time()
test_lru_cache(1, 2)
test_lru_cache(1, 2)
test_lru_cache(1, 2)
print(test_lru_cache(1, 2))
end = time.time()
print(end - start)
> 3
12.002995729446411
相同代码,使用 lru_cache (时间就是 一次计算的时间)
@lru_cache
def test_lru_cache(a, b):
time.sleep(3)
return a+b
start = time.time()
test_lru_cache(1, 2)
test_lru_cache(1, 2)
test_lru_cache(1, 2)
print(test_lru_cache(1, 2))
end = time.time()
print(end - start)
>3
3.001422166824341
lru_cache 不适用于 使用 time.time() 等随机参数的函数(因为没有意义)
被lru_cache 装饰的函数 具有 cache_info()方法,可以返回该函数缓存的 结构:
还是上面的 test_lru_cache 函数
for i in range(100):
test_lru_cache(1, i)
print(test_lru_cache(1, 130))
print(test_lru_cache.cache_info())
>CacheInfo(hits=0, misses=101, maxsize=128, currsize=101)
hits -> 命中次数 (缓存的结果中,直接通过缓存拿到结果的次数,是重复计数的)
misses -> 未命中次数
maxsize -> 最大容量
currsize -> 当前容量 (<=maxsize)
下面的暂时不讲,感觉不怎么用到
- @functools.total_ordering
- class functools.partialmethod(func, /, *args, **keywords)
- functools.reduce(function, iterable[, initializer])
- @functools.singledispatch
- class functools.singledispatchmethod(func)
- functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
网友评论