记忆(缓存)函数返回值:Python 实现
对于经常调用的函数,特别是递归函数或计算密集的函数,记忆(缓存)返回值可以显着提高性能。而在 Python 里,可以使用字典来完成。
例子:斐波那契数列
下面这个计算斐波那契数列的函数 fib() 具有记忆功能,对于计算过的函数参数可以直接给出答案,不必再计算:
fib_memo = {}
def fib(n):
if n < 2: return 1
if not n in fib_memo:
fib_memo[n] = fib(n-1) + fib(n-2)
return fib_memo[n]
更进一步:包装类
我们可以把这个操作包装成一个类 Memory,这个类的对象都具有记忆功能:
class Memoize:
"""Memoize(fn) - 一个和 fn 返回值相同的可调用对象,但它具有额外的记忆功能。
只适合参数为不可变对象的函数。
"""
def __init__(self, fn):
self.fn = fn
self.memo = {}
def __call__(self, *args):
if not args in self.memo:
self.memo[args] = self.fn(*args)
return self.memo[args]
# 原始函数
def fib(n):
print(f'Calculating fib({n})')
if n < 2: return 1
return fib(n-1) + fib(n-2)
# 使用方法
fib = Memoize(fib)
运行测试,计算两次 fib(10):
Calculating fib(10)
Calculating fib(9)
Calculating fib(8)
Calculating fib(7)
Calculating fib(6)
Calculating fib(5)
Calculating fib(4)
Calculating fib(3)
Calculating fib(2)
Calculating fib(1)
Calculating fib(0)
89
89
可以看到第二次直接输出 89,没有经过计算。
再进一步:装饰器
对装饰器熟悉的程序员应该已经想到,这个类可以被当成装饰器使用。在定义 fib() 的时候可以直接这样:
@Memoize
def fib(n):
if n < 2: return 1
return fib(n-1) + fib(n-2)
这和之前的代码等价,但是更简洁明了。
最后的完善
之前的 Memory 类只适合包装参数为不可变对象的函数。原因是我们用到了字典作为存储介质,将参数作为字典的 key;而在 Python 中的 dict 只能把不可变对象作为 key 2,例如数字、字符串、元组(里面的元素也得是不可变对象)。所以提高代码通用性,我们只能牺牲运行速度,将函数参数序列化为字符串再作为 key 来存储,如下:
class Memoize:
"""Memoize(fn) - 一个和 fn 返回值相同的可调用对象,但它具有额外的记忆功能。
此时适合所有函数。
"""
def __init__(self, fn):
self.fn = fn
self.memo = {}
def __call__(self, *args):
import pickle
s = pickle.dumps(args)
if not s in self.memo:
self.memo[s] = self.fn(*args)
return self.memo[s]
使用第三方库 - joblib
除了这种手工制作的方法,有一个第三方库 joblib 能实现同样的功能,而且性能更好,适用性更广。因为上文中的方法是缓存在内存中的,每次都要比较传入的参数。对于很大的对象作为参数,如 numpy 数组,这种方法性能很差。而 joblib.Memory 模块提供了一个存储在硬盘上的 Memory 类,其用法如下:
首先定义缓存目录:
>>> cachedir = 'your_cache_location_directory'
以此缓存目录创建一个 memory 对象:
>>> from joblib import Memory
>>> memory = Memory(cachedir, verbose=0)
使用它和使用装饰器一样:
>>> @memory.cache
... def f(n):
... print(f'Running f({n})')
... return x
以同样的参数运行这个函数两次,只有第一次会真正计算:
>>> print(f(1))
Running f(1)
1
>>> print(f(1))
1
参考
1 http://code.activestate.com/recipes/52201/
2 https://docs.python.org/3/tutorial/datastructures.html#dictionaries
3 https://joblib.readthedocs.io/en/latest/memory.html#use-case
(本文完)
记忆(缓存)函数返回值:Python 实现的更多相关文章
- Python从线程获取函数返回值
Python中利用强大的threading模块可以很容易的实现多线程开发,提高运行速度.这一般是对某个进行大量计算操作的的函数进行多线程处理,然后合并各线程的结果.获取函数返回值的方法可以如下: 1) ...
- Python学习教程(learning Python)--2.3.4Python函数返回值
本节讨论Python函数返回值问题. Python和C语言一样,也可以在函数结束时返回一个值.但在定义自己的Python函数时,是不需要指定返回值数据类型的,这和Python不关心变量的数据类型是一致 ...
- Python第七天 函数 函数参数 函数里的变量 函数返回值 多类型传值 函数递归调用 匿名函数 内置函数
Python第七天 函数 函数参数 函数里的变量 函数返回值 多类型传值 函数递归调用 匿名函数 内置函数 目录 Pycharm使用技巧(转载) Python第一天 ...
- Python return语句 函数返回值
return语句是从python 函数返回一个值,在讲到定义函数的时候有讲过,每个函数都要有一个返回值.Python中的return语句有什么作用,今天就来仔细的讲解一下. python 函数返回值 ...
- python函数返回值
2016-08-09 15:01:38 python函数返回值使用return语句,可以返回任意类型的数.如果return语句执行,它之后的所有语句都不再执行. def func(x,y): pri ...
- python学习之函数返回值
python中函数返回值的方式有2种: 1.return语句 说明:return语句执行完后,函数后续的代码将不会被执行 2.yield语句 说明:yield语句返回的是一个迭代器对象,可以通过nex ...
- Python 函数返回值、作用域
函数返回值 多条return语句: def guess(x): if x > 3: return "> 3" else: return "<= 3&qu ...
- python使用threading获取线程函数返回值的实现方法
python使用threading获取线程函数返回值的实现方法 这篇文章主要介绍了python使用threading获取线程函数返回值的实现方法,需要的朋友可以参考下 threading用于提供线程相 ...
- python学习——函数返回值及递归
返回值 return语句是从python 函数返回一个值,在讲到定义函数的时候有讲过,每个函数都要有一个返回值.Python中的return语句有什么作用,今天小编就依目前所了解的讲解一下.pytho ...
随机推荐
- cucumber测试项目报错
[INFO] Scanning for projects... [WARNING] [WARNING] Some problems were encountered while building th ...
- Tomcat系列(7)——Tomcat类加载机制
1. 核心部分 1. 类加载器: 通过一个类的全限定名来获取描述此类的二进制字节流. 对于任意一个类,都需要由加载他的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一 ...
- http 400错误【原】
http 400错误现象: 使用java代码访问某PDF文件地址, 报了http 400错误 ,浏览器却能正常访问 . 所以猜测浏览器对地址做了额外处理. 异常代码 String srcUrl = & ...
- 享元模式-Flyweight(Java实现)
享元模式-Flyweight 享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用. 本文中的例子如下: 使用享元模式: 小明想看编程技术的书, ...
- 教你如何绘制数学函数图像——numpy和matplotlib的简单应用
numpy和matplotlib的简单应用 一.numpy库 1.什么是numpy NumPy系统是Python的一种开源的数值计算扩展.这种工具可用来存储和处理大型矩阵,比Python自身的嵌套列表 ...
- shell 生成文件统计信息
#!/bin/bash #file name : filestat.sh if [ $# -ne 1 ]; then echo "Usage is $0 basepath"; ex ...
- Python——Selenium & Chrome Driver配置
1.CMD下载安装selenium pip install selenium 2.python运行: from selenium import webdriver browser = webdrive ...
- 【译】使用 Flutter 实现跨平台移动端开发
作者: Mike Bluestein | 原文地址:[https://www.smashingmagazine.com/2018/06/google-flutter-mobile-developm ...
- luogu P5305 [GXOI/GZOI2019]旧词
传送门 先考虑\(k=1\),一个点的深度就是到根节点的路径上的点的个数,所以\(lca(x,y)\)的深度就是\(x\)和\(y\)到根路径的交集路径上的点的个数,那么对于一个询问,我们可以对每个点 ...
- 第31月第10天 tableview头部空白 Other Linker Flags rtmp
1.ios10 tableview头部空白 if (@available(iOS 11.0, *)) { self.tableView.contentInsetAdjustmentBehavior = ...