简体   繁体   English

在python memoization装饰器类中设置get / set属性

[英]Setting a get/set property in a python memoization decorator class

I have created a decorator memoization class that I am actively using for cache my calls. 我已经创建了一个装饰器memoization类,我正在积极地用于缓存我的调用。 There are already many excellent suggestions on how to implement python memoization. 关于如何实现python memoization已经有很多很好的建议。

The class that I have created currently uses get and set method calls to set the cacheTimeOut. 我创建的类当前使用get和set方法调用来设置cacheTimeOut。 They are called getCacheTimeOut() and setCacheTimeOut() . 它们被称为getCacheTimeOut()setCacheTimeOut() While this is an adequate solution. 虽然这是一个适当的解决方案。 I was hoping to use the @property and @cacheTimeOut.setter decorators to enable the functions to be called directly as for example cacheTimeOut=120 我希望使用@property@cacheTimeOut.setter装饰器来直接调用函数,例如cacheTimeOut=120

The problem is in the details. 问题在于细节。 I do not know how to make these properties accessible in the __get__ method. 我不知道如何在__get__方法中访问这些属性。 The __get__ method assigns the different function calls defined within the class to functions.partial. __get__方法将类中定义的不同函数调用分配给functions.partial。

Here is my script example designed for Python 2.7 这是我为Python 2.7设计的脚本示例

import time
from functools import partial
import cPickle

class memoize(object):
    def __init__(self, func):
        self.func = func
        self._cache = {}
        self._timestamps = {}
        self._cacheTimeOut = 120
        self.objtype = None

    def __new__(cls, *args, **kwargs):
        return object.__new__(cls,*args, **kwargs)

    def __get__(self, obj, objtype=None):
    """Used for object methods where decorator has been placed before methods."""
        self.objtype = objtype
        fn = partial(self, obj)
        fn.resetCache = self.resetCache
        fn.getTimeStamps = self.getTimeStamps
        fn.getCache = self.getCache
        fn._timestamps = self._timestamps
        fn.setCacheTimeOut = self.setCacheTimeOut
        fn.getCacheTimeOut = self.getCacheTimeOut
        return fn

    def __argsToKey(self, *args, **kwargs):
        args = list(args)

        for x, arg in enumerate(args):    # remove instance from
            if self.objtype:
                 if isinstance(arg, self.objtype):
                     args.remove(arg)

        str = cPickle.dumps(args, 1)+cPickle.dumps(kwargs, 1)

        return str

    def __call__(self, *args, **kwargs):
        """Main calling function of decorator."""
         key = self.__argsToKey(*args, **kwargs)    
         now = time.time()    # get current time to query for key
         if self._timestamps.get(key, now) > now:    
             return self._cache[key]
         else:
             value = self.func(*args, **kwargs)
             self._cache[key] = value
             self._timestamps[key] = now + self._cacheTimeOut
         return value

    def __repr__(self):
        '''Return the function's docstring.'''
        return self.func.__doc__

    def resetCache(self):
        """Resets the cache.  Currently called manually upon request."""
        self._cache = {}
        self._timestamps = {}

    def getCacheTimeOut(self):
    """Get the cache time out used to track stale data."""
        return self._cacheTimeOut

    def setCacheTimeOut(self, timeOut):
    """Set the cache timeout to some other value besides 120.  Requires an integer     value.  If you set timeOut to zero you are ignoring the cache"""
        self._cacheTimeOut = timeOut

    def getCache(self):
    """Returns the cache dictionary."""
        return self._cache

    def getTimeStamps(self):
    """Returns the encapsulated timestamp dictionary."""
        return self._timestamps

    @property
    def cacheTimeOut(self):
    """Get cacheTimeOut."""
        return self._cacheTimeOut

    @cacheTimeOut.setter
    def cacheTimeOut(self, timeOut):
    """Set cacheTimeOut."""
        self._cacheTimeOut = timeOut

memoize
def increment(x):
    increment.count+=1
    print("increment.count:%d, x:%d"%(increment.count, x))
    x+=1
    return x


increment.count = 0   # Define the count to track whether calls to increment vs cache


class basic(object):
    def __init__(self):
        self.count = 0

    @memoize
    def increment(self, x):
        self.count+=1
        print("increment.count:%d, x:%d"%(increment.count, x))
        x+=1
        return x


def main():
    print increment(3)
    print increment(3)

    # What I am actually doing
    print increment.getCacheTimeOut()  # print out default of 120
    increment.setCacheTimeOut(20)      # set to 20
    print increment.getCacheTimeOut()  # verify that is has been set to 120

    # What I would like to do and currently does not work
    print increment.cacheTimeOut
    # Assign to property
    increment.cacheTimeOut = 20


    myObject = basic()
    print myObject.increment(3)
    print myObject.count
    print myObject.increment(3)
    print myObject.count
    print myObject.increment(4)
    print myObject.count



####### Unittest code. 
import sys
import time
import unittest
from memoize import memoize

class testSampleUsages(unittest.TestCase):
# """This series of unit tests is to show the user how to apply memoize calls."""
    def testSimpleUsageMemoize(self):
        @memoize
        def increment(var=0):
            var += 1
            return var

        increment(3)
        increment(3)

    def testMethodBasedUsage(self):
        """Add the @memoize before method call."""
        class myClass(object):
            @memoize
            def increment(self,var=0):
                var += 1
                return var

            @memoize
            def decrement(self, var=0):
                var -=1
                return var

        myObj = myClass()
        myObj.increment(3)
        myObj.increment(3)
        myObj.decrement(6)
        myObj.decrement(6)

    def testMultipleInstances(self):
        @memoize
        class myClass(object):
            def __init__(self):
               self.incrementCountCalls = 0
               self.decrementCountCalls = 0
               self.powCountCall = 0

            # @memoize
            def increment(self,var=0):
                var += 1
                self.incrementCountCalls+=1
                return var

            # @memoize
            def decrement(self, var=0):
                self.decrementCountCalls+=1
                var -=1
                return var

            def pow(self, var=0):
                self.powCountCall+=1
                return var*var


        obj1 = myClass()   # Memoizing class above does not seem to work.  
        obj2 = myClass()
        obj3 = myClass()

        obj1.increment(3)
        obj1.increment(3)
        #obj2.increment(3)
        #obj2.increment(3)
        #obj3.increment(3)
        #obj3.increment(3)

        obj1.pow(4)
        obj2.pow(4)
        obj3.pow(4)

There's no way to attach a property to a single instance. 无法将property附加到单个实例。 Being descriptors, property s must be part of a class definition in order to function. 作为描述符, property必须是类定义的一部分才能起作用。 That means you can't easily add them to the partial object you create in __get__ . 这意味着您无法轻松将它们添加到您在__get__创建的partial对象中。

Now, you could create a class of your own to reimplement the behavior of partial with your added property. 现在,您可以创建自己的类,以使用添加的属性重新实现partial的行为。 However, I suspect the limitation is actually to your benefit. 但是,我怀疑这种限制实际上对你有利。 If memo is applied to a method, its state is shared by all instances of the class (and perhaps even instances of subclasses). 如果将memo应用于方法,则其状态由类的所有实例共享(甚至可能是子类的实例)。 If you allow the caching details to be adjusted through instances, you might confuse users with cases like: 如果您允许通过实例调整缓存详细信息,则可能会将用户与以下情况混淆:

obj1 = basic()
print obj1.increment.getCacheTimeout() # prints the initial value, e.g. 120

obj2 = basic()
obj2.increment.setCacheTimeOut(20)     # change the timeout value via another instance

print obj1.increment.getCacheTimeout() # the value via the first instance now prints 20

I suggest that you make the memoization-related interfaces of decorated methods accessible only through the class, not through instances. 我建议您只允许通过类访问装饰方法的与memoization相关的接口,而不是通过实例。 To make that work, you need to update your __get__ method to work if obj is None . 要使其工作,如果objNone ,则需要更新__get__方法。 It can simply return self : 它可以简单地回归self

def __get__(self, obj, objtype=None):
    if obj is None:
        return self

    self.objtype = objtype
    return partial(self, obj) # no need to attach our methods to the partial anymore

With this change, using a property on the memo via the class works: 随着这一变化,采用了property上的memo通过类作品:

basic.increment.cacheTimeOut = 20  # set property of the "unbound" method basic.increment

There is actually a way to accomplish this - by rebinding the decorator as instance-object with a call -method 实际上有一种方法可以实现这一点 - 通过使用call -method将装饰器重新绑定为instance-object

class Helper(object):

    def __init__(self, d, obj):
        self.d = d
        self.obj = obj
        self.timeout = 0

    def __call__(self, *args, **kwargs):
        print self, self.timeout
        return self.d.func(self.obj, *args, **kwargs)


class decorator(object):

    def __init__(self, func):
        self.func = func
        self.name = func.__name__



    def __get__(self, obj, clazz):
        if object is not None:
            obj.__dict__[self.name] = Helper(self, obj)
        return obj.__dict__[self.name]


class Foo(object):

    @decorator
    def bar(self, args):
        return args * 2



f = Foo()
g = Foo()

f.bar.timeout = 10
g.bar.timeout = 20

print f.bar(10)
print g.bar(20)

HTH HTH

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM