如何在Python中将变量放入堆栈/上下文中
-
05-07-2019 - |
题
本质上,我想将一个变量放在堆栈上,堆栈上该部分下方的所有调用都可以访问该变量,直到块退出。在Java中,我将使用带有支持方法的本地静态线程来解决这个问题,然后可以从方法中访问该方法。
典型例子:您收到请求并打开数据库连接。在请求完成之前,您希望所有代码都使用此数据库连接。完成并关闭请求后,您可以关闭数据库连接。
我需要这个的目的是一个报告生成器。每个报告由多个部分组成,每个部分可以依赖于不同的计算,有时不同的部分部分依赖于相同的计算。由于我不想重复繁重的计算,因此我需要缓存它们。我的想法是用缓存装饰器来装饰方法。缓存根据方法名称和模块及其参数创建一个 id,查看堆栈变量中是否已计算出该 id,如果没有,则执行该方法。
我将尝试通过展示我当前的实现来澄清这一点。我想做的就是简化那些实现计算的代码。
首先,我有中央缓存访问对象,我将其称为 MathContext:
class MathContext(object):
def __init__(self, fn):
self.fn = fn
self.cache = dict()
def get(self, calc_config):
id = create_id(calc_config)
if id not in self.cache:
self.cache[id] = calc_config.exec(self)
return self.cache[id]
fn 参数是创建上下文所关联的文件名,可以从中读取数据进行计算。
然后我们有计算类:
class CalcBase(object):
def exec(self, math_context):
raise NotImplementedError
这是一个愚蠢的斐波那契例子。这些方法实际上都不是递归的,它们只适用于大量数据,但它可以演示如何依赖其他计算:
class Fibonacci(CalcBase):
def __init__(self, n): self.n = n
def exec(self, math_context):
if self.n < 2: return 1
a = math_context.get(Fibonacci(self.n-1))
b = math_context.get(Fibonacci(self.n-2))
return a+b
我想要的斐波那契只是一个装饰方法:
@cache
def fib(n):
if n<2: return 1
return fib(n-1)+fib(n-2)
以 math_context 为例,当 math_context 超出范围时,它的所有缓存值也会超出范围。我想要装饰器也有同样的东西。IE。在 X 点,@cache 缓存的所有内容都被取消引用以进行 gced。
解决方案
我继续做了一些可能会做你想做的事情。它既可以用作装饰器,也可以用作上下文管理器:
from __future__ import with_statement
try:
import cPickle as pickle
except ImportError:
import pickle
class cached(object):
"""Decorator/context manager for caching function call results.
All results are cached in one dictionary that is shared by all cached
functions.
To use this as a decorator:
@cached
def function(...):
...
The results returned by a decorated function are not cleared from the
cache until decorated_function.clear_my_cache() or cached.clear_cache()
is called
To use this as a context manager:
with cached(function) as function:
...
function(...)
...
The function's return values will be cleared from the cache when the
with block ends
To clear all cached results, call the cached.clear_cache() class method
"""
_CACHE = {}
def __init__(self, fn):
self._fn = fn
def __call__(self, *args, **kwds):
key = self._cache_key(*args, **kwds)
function_cache = self._CACHE.setdefault(self._fn, {})
try:
return function_cache[key]
except KeyError:
function_cache[key] = result = self._fn(*args, **kwds)
return result
def clear_my_cache(self):
"""Clear the cache for a decorated function
"""
try:
del self._CACHE[self._fn]
except KeyError:
pass # no cached results
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.clear_my_cache()
def _cache_key(self, *args, **kwds):
"""Create a cache key for the given positional and keyword
arguments. pickle.dumps() is used because there could be
unhashable objects in the arguments, but passing them to
pickle.dumps() will result in a string, which is always hashable.
I used this to make the cached class as generic as possible. Depending
on your requirements, other key generating techniques may be more
efficient
"""
return pickle.dumps((args, sorted(kwds.items())), pickle.HIGHEST_PROTOCOL)
@classmethod
def clear_cache(cls):
"""Clear everything from all functions from the cache
"""
cls._CACHE = {}
if __name__ == '__main__':
# used as decorator
@cached
def fibonacci(n):
print "calculating fibonacci(%d)" % n
if n == 0:
return 0
if n == 1:
return 1
return fibonacci(n - 1) + fibonacci(n - 2)
for n in xrange(10):
print 'fibonacci(%d) = %d' % (n, fibonacci(n))
def lucas(n):
print "calculating lucas(%d)" % n
if n == 0:
return 2
if n == 1:
return 1
return lucas(n - 1) + lucas(n - 2)
# used as context manager
with cached(lucas) as lucas:
for i in xrange(10):
print 'lucas(%d) = %d' % (i, lucas(i))
for n in xrange(9, -1, -1):
print 'fibonacci(%d) = %d' % (n, fibonacci(n))
cached.clear_cache()
for n in xrange(9, -1, -1):
print 'fibonacci(%d) = %d' % (n, fibonacci(n))
其他提示
这个问题好像是两个问题
- a) 共享数据库连接
- b) 缓存/记忆
b) 你已经回答了自己
a)我似乎不明白为什么你需要把它放在堆栈上?你可以执行以下操作之一
- 您可以使用类,连接可能是它的属性
- 您可以装饰所有功能,以便它们从中央位置获得连接
- 每个功能都可以明确使用全局连接方法
- 您可以创建一个连接并传递它,或创建上下文对象并传递上下文,连接可以成为上下文的一部分
等等,等等
您可以使用包含在getter函数中的全局变量:
def getConnection():
global connection
if connection:
return connection
connection=createConnection()
return connection
“您收到请求,并打开数据库连接....您关闭数据库连接。”
这是对象的用途。创建连接对象,将其传递给其他对象,然后在完成后关闭它。全球不合适。只需将值作为参数传递给正在进行工作的其他对象。
&quot;每个报告由多个部分组成,每个部分可以依赖于不同的计算,有时不同的部分部分依赖于相同的计算....我需要缓存它们“
这是对象的用途。创建一个包含有用计算结果的字典,并将其从报表部分传递到报表部分。
您不需要乱用“堆栈变量”,“静态线程本地”等。或类似的东西。 只需将普通变量参数传递给普通方法函数。你会更开心。
class MemoizedCalculation( object ):
pass
class Fibonacci( MemoizedCalculation ):
def __init__( self ):
self.cache= { 0: 1, 1: 1 }
def __call__( self, arg ):
if arg not in self.cache:
self.cache[arg]= self(arg-1) + self(arg-2)
return self.cache[arg]
class MathContext( object ):
def __init__( self ):
self.fibonacci = Fibonacci()
您可以像这样使用
>>> mc= MathContext()
>>> mc.fibonacci( 4 )
5
您可以定义任意数量的计算并将它们全部折叠到一个容器对象中。
如果需要,可以将MathContext转换为正式的Context Manager,以便它与 with 语句一起使用。将这两个方法添加到MathContext。
def __enter__( self ):
print "Initialize"
return self
def __exit__( self, type_, value, traceback ):
print "Release"
然后你可以这样做。
with MathContext() as mc:
print mc.fibonacci( 4 )
在 with 语句的末尾,您可以保证调用 __ exit __
方法。