代码之家  ›  专栏  ›  技术社区  ›  Staale

如何在Python的堆栈/上下文中放置变量

  •  2
  • Staale  · 技术社区  · 15 年前

    实际上,我想在堆栈上放置一个变量,在该块退出之前,堆栈上该部分下面的所有调用都可以访问该变量。在Java中,我将使用支持方法的本地线程来解决这个问题,然后可以从方法中访问这些线程。

    典型示例:您得到一个请求,然后打开一个数据库连接。在请求完成之前,您希望所有代码都使用此数据库连接。完成并关闭请求后,关闭数据库连接。

    我需要的是一个报告生成器。每个报表由多个部分组成,每个部分可以依赖于不同的计算,有时不同的部分部分依赖于相同的计算。因为我不想重复繁重的计算,所以我需要缓存它们。我的想法是用缓存装饰器来装饰方法。缓存根据方法名和模块以及参数创建一个ID,查看是否在堆栈变量中计算了allready,如果没有,则执行该方法。

    我将通过显示当前的实现来尝试和清晰化。我想做的是简化那些实现计算的代码。

    首先,我有一个中央缓存访问对象,我称之为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超出范围时,它的所有缓存值也会超出范围。我也要同样的东西给装修工。也就是说,在X点,@cache缓存的所有内容都将被取消GCED。

    4 回复  |  直到 15 年前
        1
  •  5
  •   Steef    15 年前

    我继续做了一些可以做你想做的事情。它可以同时用作修饰器和上下文管理器:

    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
  •  2
  •   Anurag Uniyal    15 年前

    这个问题似乎是两个问题

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

    b)你自己回答了

    A)我似乎不明白你为什么要把它叠起来? 你可以做这些

    1. 您可以使用类和连接 可能是它的属性
    2. 你可以装饰你所有的功能 这样他们就可以从 中心位置
    3. 每个函数都可以显式使用 全局连接方法
    4. 您可以创建连接并传递 或者创建一个上下文 物体和传递 上下文,连接可以是 语境

        3
  •  0
  •   Dan Lorenc    15 年前

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

    def getConnection():
        global connection
        if connection:
            return connection
        connection=createConnection()
        return connection
    
        4
  •  0
  •   S.Lott    15 年前

    “您收到一个请求,然后打开一个数据库连接….关闭数据库连接。“

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

    “每个报告由多个部分组成,每个部分可以依赖于不同的计算,有时不同的部分部分部分部分依赖于相同的计算….我要把它们藏起来”

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

    您不需要处理“栈变量”、“静态线程局部”或类似的事情。 只需将普通变量参数传递给普通方法函数即可。你会更快乐的。


    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设置为正式的上下文管理器,以便它与 具有 语句。将这两个方法添加到MathContext。

    def __enter__( self ):
        print "Initialize"
        return self
    def __exit__( self, type_, value, traceback ):
        print "Release"
    

    然后你可以这样做。

    with  MathContext() as mc:
        print mc.fibonacci( 4 )
    

    具有 声明,你可以保证 __exit__ 方法被调用。