简体   繁体   English

Python装饰器在类中获取或设置字典值

[英]Python decorator get or set dictionary value in class

I'm working on a class representing on object with numerous associated data. 我正在研究一个表示具有大量关联数据的对象的类。 I'm storing these data in a dictionary class attribute called metadata . 我将这些数据存储在名为metadata的字典类属性中。 A representation could be: 表示形式可以是:

{'key1':slowToComputeValue, 'key2':evenSlowerToComputeValue}

The calculating of the values is in some cases very slow, so what I want to do is, using "getter" functions, first try and get the value from the metadata dict. 在某些情况下,值的计算非常慢,所以我想做的是使用“ getter”函数,首先尝试从元数据字典中获取值。 Only on a KeyError (ie when the getter tries to get a value for a key which doesn't exist yet) should the value be calculated (and added to the dictionary for fast access next time the getter is called). 仅在KeyError上(即,当getter尝试获取尚不存在的键的值时),才应计算该值(并将其添加到字典中,以便在下次调用getter时进行快速访问)。

I began with a simple: 我从一个简单的开始:

try:
    return self.metadata[requested_key]
except KeyError:
    #Implementation of function

As there are many getters in the class, I started thought that these first 3 lines of code could be handled by a decorator. 由于班上有很多吸气剂,我开始认为这头三行代码可以由装饰器处理。 However I'm having problems making this work. 但是我在使这项工作中遇到问题。 The problem is that I need to pass the metadata dictionary from the class instance to the decorator. 问题是我需要将元数据字典从类实例传递给装饰器。 I've found several tutorials and posts like this one which show that it is possible to send a parameter to an enclosing function but the difficulty I'm having is sending a class instantiation attribute metadata to it (if I send a string value it works). 我发现了一些类似这样的教程和帖子,它们表明可以向封闭函数发送参数,但是我遇到的困难是向其发送类实例化属性元数据(如果我发送字符串值,则可以工作) )。

Some example code from my attempt is here: 我尝试的一些示例代码在这里:

def get_existing_value_from_metadata_if_exists(metadata):
    def decorator(function):
        @wraps(function)
        def decorated(*args, **kwargs):
            function_name = function.__name__
            if function_name in metadata.keys():
                return metadata[function_name]
            else:
                function(*args, **kwargs)
        return decorated
    return decorator

class my_class():
    @get_existing_value_from_metadata_if_exists(metadata)
    def get_key1(self):
        #Costly value calculation and add to metadata

    @get_existing_value_from_metadata_if_exists(metadata)
    def get_key2(self):
        #Costly value calculation and add to metadata

    def __init__(self):
        self.metadata = {}

The errors I'm getting are generally self not defined but I've tried various combinations of parameter placement, decorator placement etc. without success. 我得到的错误通常是无法自定义的,但是我尝试了参数放置,装饰器放置等的各种组合,但均未成功。

So my questions are: 所以我的问题是:

  1. How can I make this work? 我该如何进行这项工作?
  2. Are decorators a suitable way to achieve what I'm trying to do? 装饰器是实现我要完成的任务的合适方法吗?

Yes, a decorator is a good use case for this. 是的,装饰器是一个很好的用例。 Django for example has something similar already included with it, it's called cached_property . 例如,Django已经包含了类似的东西,称为cached_property

Basically all it does is that when the property is accessed first time it will store the data in instance's dict( __dict__ ) by the same name as the function. 基本上,它所做的就是,当第一次访问该属性时,它将以与函数相同的名称将数据存储在实例的dict( __dict__ )中。 When we fetch the same property later on it simple fetches the value from the instance dictionary. 当我们稍后获取相同的属性时,简单地从实例字典中获取值。

A cached_property is a non-data descriptor . cached_property 是非数据描述符 Hence once the key is set in instance's dictionary, the access to property would always get the value from there. 因此,一旦在实例的字典中设置了键,对属性的访问将始终从那里获取值。

class cached_property(object):
    """
    Decorator that converts a method with a single self argument into a
    property cached on the instance.

    Optional ``name`` argument allows you to make cached properties of other
    methods. (e.g.  url = cached_property(get_absolute_url, name='url') )
    """
    def __init__(self, func, name=None):
        self.func = func
        self.__doc__ = getattr(func, '__doc__')
        self.name = name or func.__name__

    def __get__(self, instance, cls=None):
        if instance is None:
            return self
        res = instance.__dict__[self.name] = self.func(instance)
        return res

In your case: 在您的情况下:

class MyClass:
    @cached_property
    def key1(self):
        #Costly value calculation and add to metadata

    @cached_property
    def key2(self):
        #Costly value calculation and add to metadata

    def __init__(self):
        # self.metadata not required

Use the name argument to convert an existing method to cached property. 使用name参数将现有方法转换为缓存的属性。

class MyClass:
    def __init__(self, data):
        self.data = data

    def get_total(self):
        print('Processing...')
        return sum(self.data)

    total = cached_property(get_total, 'total')

Demo: 演示:

>>> m = MyClass(list(range(10**5)))

>>> m.get_total()
Processing...
4999950000

>>> m.total
Processing...
4999950000

>>> m.total
4999950000

>>> m.data.append(1000)

>>> m.total  # This is now invalid
4999950000

>>> m.get_total()  # This still works
Processing...
4999951000

>>> m.total
4999950000

Based on the example above we can see that we can use total as long as we know the internal data hasn't been updated yet, hence saving processing time. 根据上面的示例,只要知道内部数据尚未更新,就可以使用total ,从而节省了处理时间。 But it doesn't make get_total() redundant, as it can get the correct total based on the data. 但这不会使get_total()多余的对象,因为它可以根据数据获取正确的总数。

Another example could be that our public facing client was using something(say get_full_name() ) as method so far but we realised that it would be more appropriate to use it as a property(just full_name ), in that case it makes sense to keep the method intact but mark it as deprecated and start suggesting the users to use the new property from now on. 另一个示例可能是,到目前为止,我们面向公众的客户使用的是某种方法(例如get_full_name() )作为方法,但是我们意识到,将其用作属性(只是full_name )会更合适,在这种情况下,保留该方法完好无损,但将其标记为已弃用,并从现在开始建议用户使用新属性。

Another way to go about this is to use class "properties" like so: 解决此问题的另一种方法是使用类“属性”,如下所示:

class MyClass():
    def __init__():
        self._slowToComputeValue = None
    @property
    def slowToComputeValue(self):
        if self._slowToComputeValue is None:
            self._slowToComputeValue = self.ComputeValue()
        return self._slowToComputeValue
    def ComputeValue(self):
        pass

Now you can access this as though it were a class attribute: 现在,您可以访问它,就像它是类属性一样:

myclass = MyClass()
print(myclass.slowToComputeValue)

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

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