2014-11-05 52 views
1

我正在一個項目中,我有一些自定義類來與用戶系統上的各種數據集合接口。這些類只有properties作爲面向用戶的屬性。其中一些屬性的資源密集程度相當高,所以我只想運行一次代碼,並將返回的值存儲在磁盤上(緩存即可),以便在後續運行中更快地檢索。既然這樣,這是怎麼了實現這一點:類裝飾器自動更新磁盤上的屬性字典?

def stored_property(func): 
    """This ``decorator`` adds on-disk functionality to the `property` 
    decorator. This decorator is also a Method Decorator. 

    Each key property of a class is stored in a settings JSON file with 
    a dictionary of property names and values (e.g. :class:`MyClass` 
    stores its properties in `my_class.json`). 
    """ 
    @property 
    @functools.wraps(func) 
    def func_wrapper(self): 
     print('running decorator...') 
     try: 
      var = self.properties[func.__name__] 
      if var: 
       # property already written to disk 
       return var 
      else: 
       # property written to disk as `null` 
       return func(self) 
     except AttributeError: 
      # `self.properties` does not yet exist 
      return func(self) 
     except KeyError: 
      # `self.properties` exists, but property is not a key 
      return func(self) 
    return func_wrapper 

class MyClass(object): 
    def __init__(self, wf): 
     self.wf = wf 
     self.properties = self._properties() 

    def _properties(self): 
     # get name of class in underscore format 
     class_name = convert(self.__class__.__name__) 
     # this is a library used (in Alfred workflows) for interacted with data stored on disk 
     properties = self.wf.stored_data(class_name) 
     # if no file on disk, or one of the properties has a null value 
     if properties is None or None in properties.values(): 
      # get names of all properties of this class 
      propnames = [k for (k, v) in self.__class__.__dict__.items() 
         if isinstance(v, property)] 
      properties = dict() 
      for prop in propnames: 
       # generate dictionary of property names and values 
       properties[prop] = getattr(self, prop) 
      # use the external library to save that dictionary to disk in JSON format 
      self.wf.store_data(class_name, properties, 
           serializer='json') 
     # return either the data read from file, or data generated in situ 
     return properties 

    #this decorator ensures that this generating code is only run if necessary 
    @stored_property 
    def only_property(self): 
     # some code to get data 
     return 'this is my property' 

此代碼的工作正是因爲我需要它,但它仍然迫使我到_properties(self)方法手動添加到每個班級,其中我需要這個功能(目前,我有3)。我想要的是將這個功能「插入」任何我喜歡的課程的方法。我認爲一個類裝飾器可以完成這項工作,但儘可能地嘗試,我不能完全弄清楚如何解決它。

爲了清楚起見(如果裝飾者不是獲得我想要的最佳方式),我將嘗試解釋我所追求的整體功能。我想寫一個包含一些屬性的類。這些屬性的值是通過不同程度的複雜代碼生成的(在一個實例中,我正在搜索某個應用程序的pref文件,然後搜索3個不同的首選項(其中任何可能存在或不存在),並確定最佳單個來自這些偏好)。我希望屬性代碼的主體只包含查找數據的算法。但是,我不想每次訪問該屬性時都運行該算法代碼。一旦我產生了一次這個值,我想把它寫到磁盤上,然後在隨後的所有調用中讀取它。但是,我不希望每個值寫入它自己的文件;我想要一個單個類的所有屬性值的字典都寫入一個文件(因此,在上例中,my_class.json將包含帶有一個鍵值對的JSON字典)。直接訪問屬性時,應首先檢查它是否已存在於磁盤字典中。如果是這樣,只需閱讀並返回該值。如果它存在但是有一個空值,那麼嘗試運行代碼(即實際寫在屬性方法中的代碼)並查看是否可以現在找到它(如果不存在,該方法將返回None,並且將再次寫入文件)。如果字典存在,並且該屬性不是關鍵字(我當前的代碼並不能真正實現這一點,但比對不起更安全),請運行代碼並添加鍵值對。如果字典不存在(即該類的第一個實例化),請運行所有屬性的所有生成代碼並創建JSON文件。理想情況下,代碼將能夠更新JSON文件中的一個屬性,而無需重新運行所有的代碼(即再次運行_properties())。

我知道這有點奇怪,但我需要速度,人類可讀的內容和優雅的代碼。我真的不必在我的目標上妥協。希望我的描述足夠清楚。如果沒有,請在評論中告訴我什麼是沒有道理的,我會盡力澄清。但我認爲一個類裝飾器可能會讓我在那裏(主要是通過將_properties()方法插入到任何類中,在實例化中運行它,並將其值映射到該類的properties屬性)。

回答

1

也許我錯過了一些東西,但看起來您的_properties方法並不是特定於給定類所具有的屬性。我會把它放在一個基類中,並讓你的每個類都有@stored_property方法的子類。那麼你不需要複製_properties方法。

class PropertyBase(object): 
    def __init__(self, wf): 
     self.wf = wf 
     self.properties = self._properties() 

    def _properties(self): 
     # As before... 

class MyClass(PropertyBase): 
    @stored_property 
    def expensive_to_calculate(self): 
     # Calculate it here 

如果由於某種原因,你不能繼承PropertyBase直接(也許你已經需要有一個不同的基類),你也許可以用一個mixin。如果不成功,則使_properties接受實例/類和工作流對象,並在每個類的__init__中明確地調用它。

+0

偉大的一點。我只是現在完全抽象了_properties()方法,並沒有意識到,一旦我抽象出來,我可以使用基類。這似乎比使用類裝飾器更清潔,更具溝通性。 – smargh 2014-11-05 20:40:47