对于经常调用的函数,特别是递归函数或计算密集的函数,记忆(缓存)返回值可以显着提高性能。而在 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 实现的更多相关文章

  1. Python从线程获取函数返回值

    Python中利用强大的threading模块可以很容易的实现多线程开发,提高运行速度.这一般是对某个进行大量计算操作的的函数进行多线程处理,然后合并各线程的结果.获取函数返回值的方法可以如下: 1) ...

  2. Python学习教程(learning Python)--2.3.4Python函数返回值

    本节讨论Python函数返回值问题. Python和C语言一样,也可以在函数结束时返回一个值.但在定义自己的Python函数时,是不需要指定返回值数据类型的,这和Python不关心变量的数据类型是一致 ...

  3. Python第七天 函数 函数参数 函数里的变量 函数返回值 多类型传值 函数递归调用 匿名函数 内置函数

    Python第七天   函数  函数参数   函数里的变量   函数返回值  多类型传值     函数递归调用   匿名函数   内置函数 目录 Pycharm使用技巧(转载) Python第一天   ...

  4. Python return语句 函数返回值

    return语句是从python 函数返回一个值,在讲到定义函数的时候有讲过,每个函数都要有一个返回值.Python中的return语句有什么作用,今天就来仔细的讲解一下. python 函数返回值 ...

  5. python函数返回值

    2016-08-09  15:01:38 python函数返回值使用return语句,可以返回任意类型的数.如果return语句执行,它之后的所有语句都不再执行. def func(x,y): pri ...

  6. python学习之函数返回值

    python中函数返回值的方式有2种: 1.return语句 说明:return语句执行完后,函数后续的代码将不会被执行 2.yield语句 说明:yield语句返回的是一个迭代器对象,可以通过nex ...

  7. Python 函数返回值、作用域

    函数返回值 多条return语句: def guess(x): if x > 3: return "> 3" else: return "<= 3&qu ...

  8. python使用threading获取线程函数返回值的实现方法

    python使用threading获取线程函数返回值的实现方法 这篇文章主要介绍了python使用threading获取线程函数返回值的实现方法,需要的朋友可以参考下 threading用于提供线程相 ...

  9. python学习——函数返回值及递归

    返回值 return语句是从python 函数返回一个值,在讲到定义函数的时候有讲过,每个函数都要有一个返回值.Python中的return语句有什么作用,今天小编就依目前所了解的讲解一下.pytho ...

随机推荐

  1. layui模板引擎

    <在模板中调用js方法> 1.js代码 layui.define(['laytpl', 'jquery'], function (exports) { var $ = layui.jque ...

  2. hadoop记录-hadoop集群日常运维命令

    hadoop集群日常运维命令 #1.namenode hadoop namenode -format #格式化,慎用 su hdfs hadoop-daemon.sh start namenode h ...

  3. jQuery使用():Deferred有状态的回调列表(含源码)

    deferred的功能及其使用 deferred的实现原理及模拟源码 一.deferred的功能及其使用 deferred的底层是基于callbacks实现的,建议再熟悉callbacks的内部机制前 ...

  4. Groovy闭包详解

    Groovy闭包是一种可执行代码块的方法,闭包也是对象,可以向方法一样传递参数,因为闭包也是对象,因此可以在需要的时候执行,像方法一样闭包可以传递一个或多个参数.闭包最常见的用途就是处理集合,可以遍历 ...

  5. js同步、异步、延时、无阻塞加载

    一.同步加载 平常默认用的都是同步加载.如:<script src="http://yourdomain.com/script.js"></script> ...

  6. ES7的async/await

    async 表示这是一个async函数,await只能用在这个函数里面. await 表示在这里等待promise返回结果了,再继续执行. await 后面跟着的应该是一个promise对象 awai ...

  7. vue路由--网站导航功能

    1.首先需要按照Vue router支持 npm install vue-router然后需要在项目中引入: import Vue from 'vue' import VueRouter from ' ...

  8. python模块------pyinotify

    介绍 pyinotify模块用来监测文件系统的变化,依赖于Linux内核的inotify功能,inotify是一个事件驱动的通知器,其通知接口从内核空间到用户空间通过三个系统调用.pyinotify结 ...

  9. 「luogu2569」[ZJOI2006] 书架

    「luogu2569」[ZJOI2006]书架 题目大意 给定一个长度为 \(n\) 序列,序列中第 \(i\) 个元素有编号 \(a_i(a_i \in \Z \cap [1,n])\),需要支持五 ...

  10. 项目Alpha冲刺(团队)-第五天冲刺

    格式描述 课程名称:软件工程1916|W(福州大学) 作业要求:项目Alpha冲刺(团队)-代码规范.冲刺任务与计划 团队名称:为了交项目干杯 作业目标:描述第五天冲刺的项目进展.问题困难.心得体会 ...