记忆(缓存)函数返回值: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 ...
随机推荐
- BeanShell 教程索引帖
一.BeanShell的基本简介 二.BeanShell环境配置 三.BeanShell语法表达式和常用命令 四.Jmeter-BeanShell使用 五.BeanShell PreProcessor ...
- hbuilder、hbuilderx和vscode对比
1. 从内存来对比 1.1 vscode占用内存 打开同一个项目,发现vscode占用内存比较多 1.2 HBuilder占用内存 1.3 HBuilderx占用内存 1.4 对比 从内存上来看,HB ...
- SaltStack配置管理和YAML
配置管理和YAML 配置管理 所谓的配置管理,也称为状态管理,就是可以通过编写文件,文件的内容为安装什么功能.开启什么服务,执行什么任务等信息,然后通过salt的配置管理,指定minion来执行这些操 ...
- Vue-router(基础)_滚动行为和history模式
一.前言 1.滚动事件 2.h5 history模式 二.主要内容 1. (1)使用前度路由,当切换到新路由时,想要页面滚动到顶部,或者是保持原先滚动的位置,就像重新加载页面那样.vue-rout ...
- leveldb实现原理
LevelDb日知录之一:LevelDb 101 说起LevelDb也许您不清楚,但是如果作为IT工程师,不知道下面两位大神级别的工程师,那您的领导估计会Hold不住了:Jeff Dean和Sanja ...
- ES6-Babel-Browserify使用教程
ES6-Babel-Browserify使用教程 定义package.json文件 { "name" : "es6-babel-browserify", &qu ...
- 在DOS命令中输入ipconfig /all,出现“该命令不是系统内部命令......”
第一种情况:1.先去C:\Windows\System32下查找有没有 ipconfig.exe的执行文件,有的话说明系统文件没有丢失,否则要去网上下载一个 ipconfig.exe文件,并放到C:\ ...
- python3 练手实例5 做一个简单电子时钟
import time,sys,os while(1): t = time.strftime('%Y-%m-%d\n%H:%M:%S',time.localtime(time.time())) pri ...
- java项目发布到linux服务器,tomcat正常启动但没加载项目
问题描述: java项目发布到linux服务器,一切操作正确,linux命令启动tomcat后,查看日志启动tomcat正常,上传的war包已经解压成功,但是tomcat没加载项目. 解决方法: 1. ...
- HDU-1028 Ignatius and the Princess III(生成函数)
题意 给出$n$,问用$1$到$n$的数字问能构成$n$的方案数 思路 生成函数基础题,$x^{n}$的系数即答案. 代码 #include <bits/stdc++.h> #define ...