2015-12-21 68 views
4

我試圖跟蹤可變python對象(例如,list tor字典)的條目的外部修改。這種能力在以下兩種情況下特別有用:捕獲作爲實例類變量的可變python對象的外部修改

1)當想避免將不需要的值分配給可變的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做得到更新x_vals

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 

回答

1

不能使用property因爲你試圖保護的東西是可變的,而property只能幫助對象本身,而不是對象內部狀態的get ing,set ing和delete

你可以做的是創建一個dict子類(或者只是一個看起來像是如果你只需要幾個dict能力)來管理訪問。然後,您的自定義班級可以管理__getitem__,__setitem____delitem__方法。


更新問題修訂

我原來的答覆仍然是有效的 - 不管你用property__getattribute__ 你仍然有基本的問題:一旦你交出屬性檢索你有不能控制它發生了什麼,也不能控制它發生了什麼。

你有兩個選擇,以解決此問題:

  1. 創建要保護類的子類,並把限制在它們(從我原來的答覆),或

  2. 創建作爲網關的通用包裝器。

一個網關包裝的很粗糙例如:

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 

要使用Gateway你需要覆蓋更多的方法,比如append(對於list)。


使用__getattribute__是不建議,因爲它是控制所有屬性查找的各方面的片。很容易出錯。

+0

你的意思是使用'property'就像我建議的那樣? –

+0

@IlyaPeterov:不可以。但是,如果OP唯一擔心的是保持'x_vals'更新,那麼您的解決方案就可以正常工作。 –

+0

實際上,除了更新x_vals之外,還需要捕獲不需要的值賦值給可變python對象的條目(請參閱修改後的問題中的第一個示例)。我修改了這個問題,並試圖使它更一般。提供一個例子說明你如何做到讓你的答案更清晰。 – user3076813

1

你可以做x_vals屬性那樣:

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

而且,它還將在每次訪問時間保持x_vals最新的。事件會更快,因爲每次更改x時都不必更新它。

如果你的唯一的問題是保持x_vals最新的,它要解決它,並節省您的子類的東西的麻煩。

+0

對不起,第一次寫錯了,編輯過。 –