2014-12-04 31 views
1

我們試圖重構和修改一個Python程序,以便它能夠獲取用戶定義的JSON文件,解析該文件,然後執行基於工作流的工作流根據用戶需要並在JSON中定義的選項。所以基本上,用戶必須用JSON指定一個字典,當這個JSON文件被Python程序解析時,我們獲得一個Python字典,然後我們將它作爲參數傳入一個我們在頂層模塊中實例化的類。總而言之,當python程序運行時,用戶定義的JSON字典最終會被添加到實例命名空間中。來自「Hashable」屬性問題的代碼挑戰

實現上下文管理器來解析JSON輸入對我們來說不是問題。但是,我們有一個要求,即可以使用JSON字典(隨後將其添加到實例名稱空間中)和generate multiple lines from a Jinja2 template file using looping within a template。我們試圖使用這條線在JSON的鍵值對之一:

"extra_scripts" : [["Altera/AlteraCommon.lua", 
        "Altera/StratixIV/EP4SGX70HF35C2.lua"]] 

,這是坐在一個大字典對象,姑且稱之爲option_space_dict和這個例子簡單,它只有4鍵值對(假設"extra_scripts"'key4'這裏),雖然我們的節目,這是更大的:

option_space_dict = { 
        'key1' : ['value1'], 
        'key2' : ['value2'], 
        'key3' : ['value3A', 'value3B', 'value3C'], 
        'key4' : [['value4A', 'value4B']] 
        } 

這是由這一行解析:

import itertools 

option_space = [ dict(itertools.izip(option_space_dict, opt)) for opt in itertools.product(*option_space_dict.itervalues()) ] 

得到基本上從option_space_dict不同之處在於它是有點像option_space

[ 
{ 'key1' : 'value1', 
    'key2' : 'value2', 
    'key3' : 'value3A' 
    'key4' : ['value4A', 'value4B'] }, 

{ 'key1' : 'value1', 
    'key2' : 'value2', 
    'key3' : 'value3B' 
    'key4' : ['value4A', 'value4B'] }, 

{ 'key1' : 'value1', 
    'key2' : 'value2', 
    'key3' : 'value3C' 
    'key4' : ['value4A', 'value4B'] } 
] 

所以我們產生option_space我們很好的東西,我們想用的Jinja2模板做。

# ignore self.option as it is not relevant to the issue here 
def getOptionCompack(self) : 
return [ (k, v) for k, v in self.option.iteritems() if set([v]) != set(self.option_space_dict[k])] 

我從那個的key4值包含的事實而產生的誤差TypeError: unhashable type: 'list':但是,爲了得到這一點,我們添加到option_space_dictkey4關鍵在做的程序所引起的問題在其他地方嵌套列表結構,這是'不可能'。

所以我們有點碰壁。有沒有人有我們如何克服這個問題的建議;能夠通過這種方式指定我們的JSON文件來完成我們想要的Jinja2,同時仍然能夠以相同的格式解析數據結構?

非常感謝!

+0

定義一種將列表轉換爲字符串並將其用於比較/散列的方法?例如'''.join(sorted(my_list))''? – 2014-12-04 10:45:39

回答

0

您可以規範化您的數據結構在從JSON分析後使用可哈希類型。

由於key4是一個列表,你有兩個選擇:

  1. 將其轉換爲一個元組,其中順序是顯著。例如,

    key = tuple(key) 
    
  2. 將其轉換爲frozenset,其中順序無關緊要。例如。,

    key = frozenset(key) 
    

如果一個鍵可以包含一本字典,那麼你將有兩個附加選項:

  1. 將其轉換爲它的項目的元組的每一個元組排序或frozenset。例如,

    key = tuple(sorted(key.iteritems())) # Use key.items() for Python 3. 
    # OR 
    key = frozenset(key.iteritems()) # Use key.items() for Python 3. 
    
  2. 將其轉換爲第三方frozendict(Python 3的兼容版本here)。例如,

    import frozendict 
    key = frozendict.frozendict(key) 
    

根據你的鑰匙是簡單還是複雜的是,你可能不得不遞歸應用轉型。

因爲你的鑰匙直接從JSON來了,你可以檢查直接在原生類型:

if isinstance(key, list): 
    # Freeze list. 
elif isinstance(key, dict): 
    # Freeze dict. 

如果你想支持泛型類型,你可以做類似的東西:

import collections 
if isinstance(key, collections.Sequence) and not isinstance(key, basestring): # Use str for Python 2. 
    # NOTE: Make sure to exclude basestring because it meets the requirements for a Sequence (of characters). 
    # Freeze list. 
elif isinstance(key, collections.Mapping): 
    # Freeze dict. 

下面是一個完整的例子:

def getOptionCompack(self): 
    results = [] 
    for k, v in self.option.iteritems(): 
     k = self.freeze_key(k) 
     if set([v]) != set(self.option_space_dict[k]): 
      results.append((k, v)) 
    return results 

def freeze_key(self, key): 
    if isinstance(key, list): 
     return frozenset(self.freeze_key(subv) for subv in key) 
    # If dictionaries need to be supported, uncomment this. 
    #elif isinstance(key, dict): 
    # return frozendict((subk, self.freeze_key(subv)) for subk, subv in key.iteritems()) 
    return key 

self.option_space_dict已經使用self.freeze_key()轉換了其密鑰。

+0

謝謝您的建議,但是,當我嘗試實現您在完整示例中展示的內容時,我仍遇到完全相同的錯誤,即存在'unhashable'列表類型。你有其他想法嗎? – AKKO 2014-12-08 03:28:16

+0

@AKKO你能編輯你的問題,包括錯誤發生在哪一行,並且'self.options'中的鍵值對失敗了嗎? – cpburnz 2014-12-09 14:01:21

+0

其實際的鍵值對,例如我已經包含在我的問題中的一個:''extra_scripts「:[[」Altera/AlteraCommon.lua「, 」Altera/StratixIV/EP4SGX70HF35C2.lua「]]嵌套列表是不可散列的。 – AKKO 2015-01-27 07:06:26

0

我們設法找出解決這個問題的辦法。我們解決方案的主要依據在於我們實現了一個幫助函數,幫助我們實際將列表轉換爲元組。基本上,回到我的問題,請記住我們有這個列表:[["Altera/AlteraCommon.lua", "Altera/StratixIV/EP4SGX70HF35C2.lua"]]? 我們原來getOptionCompack(self)方法,而我們調用的方式,實際情況是,我們直接嘗試將列表轉換爲一組的發言

return [ (k, v) for k, v in self.option.iteritems() if set([v]) != set(self.option_space_dict[k])] 

其中set(self.option_space_dict[k])並遍歷k將意味着我們會打字典鍵值對,它會給我們一個做set([["Altera/AlteraCommon.lua", "Altera/StratixIV/EP4SGX70HF35C2.lua"]]) 的實例,這是導致錯誤的原因。這是因爲列表對象是不可散列的,並且set()實際上將散列在供給它的外部列表內的每個元素上,並且這種情況下的元素是內部列表。試着做set([[2]]),你會明白我的意思。

因此我們認爲解決方法是定義一個Helper函數,該函數可以接受列表對象或任何可迭代的對象,並測試它中的每個元素是否爲列表。如果這個元素不是一個列表,它不會對它的對象類型做任何改變,如果它是(以及哪個是嵌套列表),那麼Helper函數會將該嵌套列表轉換爲一個元組對象,而在這樣做迭代,它實際上構造了一個返回自身的集合對象。該函數的定義是:

# Helper function to build a set 
def Set(iterable) : 
    return { tuple(v) if isinstance(v, list) else v for v in iterable } 

,並讓調用Set()呼叫會在我們的例子:

Set([["Altera/AlteraCommon.lua", "Altera/StratixIV/EP4SGX70HF35C2.lua"]]) 

,它返回的對象本身是:

{("Altera/AlteraCommon.lua", "Altera/StratixIV/EP4SGX70HF35C2.lua")} 

內部嵌套列表被轉換爲元組,這是一個適合於設置對象的對象類型,如圍繞該元組的{}所示。這就是爲什麼它現在可以工作,該集合可以形成。

我們繼續重新定義了使用我們自己的Set()功能原始的方法:

def getOptionCompack(self) : 
    return [ (k, v) for k, v in self.option.iteritems() if Set([v]) != Set(self.option_space_dict[k]) ] 

,現在我們不再有TypeError,並解決了這個問題。看起來這樣做很麻煩,但我們經歷這一切的原因是爲了有一種客觀的方式來比較兩個對象,將它們「歸一化」爲相同的對象類型,一組以便稍後作爲我們的源代碼的一部分執行一些其他操作。