捕获作为实例类变量的可变python对象的外部修改

时间:2015-12-21 16:24:35

标签: python object dictionary

我正在尝试跟踪可变python对象(例如,list tor dictionary)的条目的外部修改。这种能力在以下两种情况下特别有用:

1)当人们想避免将不需要的值分配给mutable python对象时。这是一个简单的例子,其中x必须只是一个整数列表:

class foo(object):
    def __init__(self,x):
        self.x = x
    def __setattr__(self,attr_name,attr_value):
        # x must be a list of integers only
        if attr_name == 'x' and not isinstance(attr_value,list):
            raise TypeError('x must be a list!')
        elif attr_name == 'x' and len([a for a in attr_value if not isinstance(a,int)]) > 0:
            raise TypeError('x must be a list of integers only')
        self.__dict__[attr_name] = attr_value

# The following works fine and it throws an error because x has a non-integer entry
f = foo(x = ['1',2,3])

# The following assigns an authorized list to x
f = foo(x = [1,2,3])

# However, the following does not throw any error. 
#** I'd like my code to throw an error whenever a non-integer value is assigned to an element of x
f.x[0] = '1'
print 'f.x = ',f.x

2)在修改可变Python对象后需要更新许多其他变量时。这是一个示例,其中x是字典,只要对x_vals进行任何更改(例如删除条目或为特定键分配新值),x就需要更新:

class foo(object):
    def __init__(self,x,y = None):
        self.set_x(x)
        self.y = y

    def set_x(self,x):
        """
        x has to be a dictionary 
        """
        if not isinstance(x,dict):
            raise TypeError('x must be a dicitonary')

        self.__dict__['x'] = x
        self.find_x_vals()

    def find_x_vals(self):
        """
        NOTE: self.x_vals needs to get updated each time one modifies x 
        """ 
        self.x_vals = self.x.values()

    def __setattr__(self,name,value):
        # Any Changes made to x --> NOT SURE HOW TO CODE THIS PART! #
        if name == 'x' or ...:
            raise AttributeError('Use set_x to make changes to x!')
        else:
            self.__dict__[name] = value 

if __name__ == '__main__':
    f = foo(x={'a':1, 'b':2, 'c':3}, y = True)
    print f.x_vals

    # I'd like this to throw an error asking to use set_x so self.x_vals
    # gets updated too
    f.x['a'] = 5

    # checks if x_vals was updated
    print f.x_vals

    # I'd like this to throw an error asking to use set_x so self.x_vals gets updated too
    del f.x['a']
    print f.x_vals

2 个答案:

答案 0 :(得分:1)

您无法使用property,因为您要保护的内容是可变的,property仅对getset和{{1}有帮助对象本身,而不是对象内部状态。

你可以做的是创建一个delete子类(如果你只需要几个dict能力,那么只是看起来像一个)来管理访问。然后,您的自定义类可以管理dict__getitem____setitem__方法。

问题修订的更新

我的原始答案仍然有效 - 无论您使用__delitem__还是property 1 ,您仍然遇到基本问题:一旦您移交了检索到的属性,您就没有了控制它发生了什么以及它做了什么。

您有两种方法可以解决此问题:

  1. 创建要保护的类的子类,并在其中加入限制(来自我的原始答案),或

  2. 创建一个通用包装器作为网关。

  3. 网关包装器的一个非常粗略的例子:

    __getattribute__

    和一个简单的例子:

    class Gateway():
        "use this to wrap an object and provide restrictions to it's data"
    
        def __init__(self, obj, valid_key=None, valid_value=None):
            self.obj = obj
            self.valid_key = valid_key
            self.valid_value = valid_value
    
        def __setitem__(self, name, value):
            """
            a dictionary can have any value for name, any value for value
            a list will have an integer for name, any value for value
            """
            valid_key = self.valid_key
            valid_value = self.valid_value
            if valid_key is not None:
                if not valid_key(name):
                    raise Exception('%r not allowed as key/index' % type(name))
            if valid_value is not None:
                if not valid_value(value):
                    raise Exception('%r not allowed as value' % value)
            self.obj[name] = value
    

    要使用huh = Gateway([1, 2, 3], valid_value=lambda x: isinstance(x, int)) huh[0] = '1' Traceback (most recent call last): ... Exception: '1' not allowed as value ,您需要覆盖更多方法,例如Gatewayappend)。

    1 不建议使用list,因为它是控制所有属性查找方面的部分。这很容易出错。

答案 1 :(得分:1)

你可以x_vals这样的属性:

@property
def x_vals(self):
    return self.x.values()

每次访问时,它都会使x_vals保持最新状态。它会更快,因为每次更改x时都不必更新它。

如果您唯一的问题是让x_vals保持最新状态,那么它就会解决问题,并为您节省子类化内容的麻烦。