如何在Python中将变量放入堆栈/上下文中

问题描述 投票:0回答:5

本质上,我想将一个变量放在堆栈上,堆栈上该部分下方的所有调用都可以访问该变量,直到块退出。在 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。

python thread-local contextmanager
5个回答
6
投票

我继续做了一些可能只是做你想要的事情。它既可以用作装饰器,也可以用作上下文管理器:

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))

2
投票

这个问题好像是两个问题

  • a) 共享数据库连接
  • b) 缓存/记忆

b)你已经回答了自己

a)我似乎不明白为什么你需要把它放在堆栈上? 你可以做其中之一

  1. 您可以使用类和连接 可能是它的属性
  2. 你可以装饰你所有的功能 这样他们就可以从 中心位置
  3. 每个函数都可以显式地使用 全局连接方式
  4. 您可以创建连接并通过 围绕它,或创建一个上下文 对象并传递 上下文,连接可以是一部分 上下文

等等等等


0
投票

您可以使用包含在 getter 函数中的全局变量:

def getConnection():
    global connection
    if connection:
        return connection
    connection=createConnection()
    return connection

0
投票

“您收到请求,并打开数据库连接......您关闭数据库连接。”

这就是对象的用途。 创建连接对象,将其传递给其他对象,然后在完成后关闭它。 全局变量不合适。 只需将值作为参数传递给正在执行工作的其他对象即可。

“每个报告由多个部分组成,每个部分可以依赖于不同的计算,有时不同的部分部分依赖于相同的计算......我需要缓存它们”

这就是对象的用途。 创建包含有用计算结果的字典,并将其在报表部件之间传递。

你不需要搞乱“堆栈变量”、“静态线程局部”或类似的东西。 只需将普通变量参数传递给普通方法函数即可。 你会快乐很多。


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 变成正式的上下文管理器,以便它与 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__
方法。


0
投票

同时(python 3.7及更高版本)同样的问题找到了另一个正确的解决方案:

from contextvars import ContextVar

这是 PEP

https://peps.python.org/pep-0567/

© www.soinside.com 2019 - 2024. All rights reserved.