2014-01-22 27 views
0

也許我想要做的只是不起作用。我也假設有其他(更好的)方法來完成相同的事情,但我想要做的是:使用「替換」功能時的多個Python裝飾器

我有一個類,將有幾個(至少4,可能更多)列表字典。我想設置一個「加載」功能和一個可用於所有這些功能的搜索功能。如果調用了任何「加載」或搜索功能,並且列表爲空,則應加載列表。

加載它們的方法各不相同,並且具有不同的參數。搜索功能需要允許搜索構成列表的字典的不同成員,並返回整個匹配字典或特定成員。

我一直在使用裝飾以指定搜索功能ALA:

@search_list 
def get_name(self,id): 
    pass 

讓我有效替代與搜索功能,並且可以使用func.__name__在搜索功能,以確定從哪個列表類使用什麼成員,如果有的話(函數名稱末尾的_xx),返回。

我遇到的問題是,做這樣的事情的時候,我可以使用加載功能,get_list(),本身:

@get_list 
def get_names(self): 
    return self.names 

但我一直沒能得到它加載列表,如果我首先調用的是搜索功能之一。如果我調用@get_list函數之一,那麼搜索函數在那之後可以正常工作,但我不想調用加載函數,除非有人使用該模塊需要該特定列表。所有列表都通過REST調用加載,有些則需要一段時間。

就像我說的,可能還有其他(更好)的方式來做到這一點,但是這已經讓我瘋了......這是一次短暫的旅行:-)

任何建議,將不勝感激。

這是我一直在使用的示例代碼:

#!/usr/bin/env python 

import re 
from functools import wraps 

NAMES = [ 
     {'name':'Fred Flintstone','id':1}, 
     {'name':'Barney Rubble','id':2}, 
     {'name':'Henry Ford','id':3}, 
     ] 

def get_list(func): 
    @wraps(func) 
    def wrapper(self,*args): 
     res = re.match('get_(?P<list>[^_]+)(_(?P<return>.*))*',func.__name__) 
     if res: 
      list_name = res.group('list').endswith('s') and res.group('list') or (res.group('list') + 's') 
      if not eval("self.%s" % list_name): 
       exec "self._Fred__get_%s()" % list_name in globals(),locals() 
     return func(self,*args) 
    return wrapper 

def search_list(func): 
    @wraps(func) 
    def wrapper(self,*args): 
     res = re.match('get_(?P<list>[^_]+)(_(?P<return>.*))*',func.__name__) 
     if res:   
      for x in eval('self.%ss' % res.group('list')): 
       if isinstance(args[0],(int,long)): 
        if x['id'] == args[0]: 
         return res.group('return') and x[res.group('return')] or x 
       else: 
        if x['name'] == args[0]: 
         return res.group('return') and x[res.group('return')] or x 
    return wrapper 

class Fred(object): 
    def __init__(self): 
     self.names = [] 

    def __get_names(self): 
     self.names = NAMES 

    @get_list 
    def get_names(self): 
     return self.names 

    @get_list 
    def get_name_names(self): 
     return [x['name'] for x in self.names] 

    @search_list 
    @get_list 
    def get_name(self,id): 
     pass 

    @search_list 
    def get_name_name(self,id): 
     pass 

    @search_list 
    def get_name_id(self,name): 
     pass 
+0

有你爲什麼想用裝飾來替代方法,而不是隻具有類內的通用方法,然後由專門的所謂理由方法? – poke

+2

另外,'eval'?真的嗎? :/ – poke

回答

1

所以首先,裝飾都意味着是通用的東西可以應用到更大羣體的事情。你想出來的裝飾器對你的Fred類非常具體,甚至在它外面定義它們也是沒有意義的(名字Fred甚至被硬編碼到它們中)。

更糟的是,裝飾者非常黑客,訪問locals(),globals(),並使用eval來執行動態代碼。沒有一個是好主意。同樣,你建立在函數名稱上的方式使得邏輯高度依賴於類的公共接口 - 或者相反,公共接口高度依賴於你的事物的實現細節。 get_name_names絕對不是一個好的函數名稱。

接下來,我甚至不知道這是什麼目的。總體而言,您似乎希望進行某種延遲加載,因爲您可以通過REST從其他位置獲取數據。這很好,但不判斷這種濫用語言。您在這些裝飾器中隱藏實際查找邏輯的方式會導致無法理解正在發生的事情,從而使維護變得非常困難。

相反,如果你只是在函數內部重複了一點,但是有一個清晰而簡單的邏輯流程,那將會更好。程序運行速度可能會更快。

如果你想實現延遲加載很好,你可以嘗試properties。這可能看起來像這樣:

import time 
NAMES = [ 
    {'name': 'Fred Flintstone', 'id': 1}, 
    {'name': 'Barney Rubble', 'id': 2}, 
    {'name': 'Henry Ford', 'id': 3} 
] 

class Fred (object): 
    def __init__ (self): 
     self._names = [] 

    @property 
    def names (self): 
     if not self._names: 
      print('Load the data here; might take a while.') 
      time.sleep(5) 
      self._names = NAMES 
     return self._names 

    def getNames (self): 
     return [x['name'] for x in self.names] 

    def searchNameById (self, id): 
     return filter(lambda x: x['id'] == id, self.names) 

    def searchNameByName (self, name): 
     return filter(lambda x: x['name'] == name, self.names) 


f = Fred() 

print('Querying some stuff:') 
print(f.searchNameById(2)) 
print(f.searchNameByName('Fred Flintstone')) 
print(f.names) 
print(f.getNames()) 

執行,它會產生這個結果。請注意,數據只加載一次:

Querying some stuff: 
Load the data here; might take a while. 
[{'name': 'Barney Rubble', 'id': 2}] 
[{'name': 'Fred Flintstone', 'id': 1}] 
[{'name': 'Fred Flintstone', 'id': 1}, {'name': 'Barney Rubble', 'id': 2}, {'name': 'Henry Ford', 'id': 3}] 
['Fred Flintstone', 'Barney Rubble', 'Henry Ford'] 

而且我認爲這比你的裝飾器的東西更清晰。

+0

謝謝,戳。 (通用的「你」)可能會以某種方式接近某件事物,然後在失敗時陷入沮喪,當它不起作用時會卡住,然後你錯過了諸如你的建議等明顯的事情,這很有趣。謝謝。 –

+0

@RobMarshall我很高興我設法讓你遠離那些奇怪的裝飾者;)不客氣! ^^ – poke

0

正則表達式解析方法名,雙下劃線名稱重整去忙玲,evalexecand/or嘗試在三元操作符(普羅蒂普:Python的三元操作符:foo if condition else bar)...提前道歉,但這是非常糟糕的設計。

此外,您的search_list修飾器從不會調用原始功能,這意味着您用它裝飾的任何功能將始終返回None

是什麼樣子,你實際上要做的是延遲加載 - 也就是,第一時間任何功能標記get_listsearch_list叫,你要執行__get_names,使其從網絡加載的東西/ disk/whatever(附加說明:get_names對於這種方法來說是一個非常糟糕的名稱,因爲讀取代碼的人會認爲它是一個沒有副作用的getter,而恰恰相反 - 反而稱它爲load_names)。

下面是如何將接近這樣的設計:

from functools import wraps 

NAMES = [ 
    {'name':'Fred Flintstone','id':1}, 
    {'name':'Barney Rubble','id':2}, 
    {'name':'Henry Ford','id':3}, 
] 


class lazy_load(object): 
    def __init__(self, cache_name): 
     self.cache_name = cache_name 

    def __call__(self, func): 
     @wraps(func) 
     def wrapper(instance, *args): 
      # instance is the instance of the class whose method we decorated, 
      # whereas self is the instance of the decorator. Tricky ;) 
      if self.cache_name not in instance.cache: 
       instance.load_cache(self.cache_name) 
      return func(instance, *args) 
     return wrapper 


class Fred(object): 
    def __init__(self): 
     self.cache = {} 

    def load_cache(self, name): 
     # TODO Add actual logic to load the data here. 
     self.cache[name] = NAMES 

    @lazy_load("names") 
    def get_names(self): 
     return self.cache["names"] 

    @lazy_load("names") 
    def get_name_by_id(self, id): 
     # Such iteration is also terrible design. 
     # If you expect access by ID to be common, you want a big {id: name_entry} dict, 
     # not a list. 
     for i in self.cache["names"]: 
      if i["id"] == id: 
       return i 
+0

嗨馬克斯,至於三元運算符,我仍然處理系統運行Python 2.4,以便語法不總是工作,我已經習慣了(使用條件和if_true的習慣(是的,這是一個壞的)或if_false'。 –

+0

請注意,三元運算符hack實際上是危險的,應該避免,因爲當if_true部分在布爾上下文中計算爲False時(例如,任何空字符串,集合或零)它不起作用。比較'「」如果條件else「onoz」'條件和「」或「onoz」' - 後者將始終返回'「onoz」'。 –

+0

至於實際答案,是的,@ poke的解決方案要簡單得多,因此比我的更好。 –