【问题标题】:Challenge with codes stemming from an issue regarding the "Hashable" property来自“Hashable”属性问题的代码挑战
【发布时间】:2015-02-02 04:26:46
【问题描述】:

我们正在尝试重构和修改 Python 程序,使其能够获取用户定义的 JSON 文件,解析该文件,然后根据用户想要并在 JSON 中定义的选项执行工作流.所以基本上,用户必须在 JSON 中指定一个字典,当这个 JSON 文件被 Python 程序解析时,我们获得一个 Python 字典,然后我们将它作为参数传递给我们在顶级模块中实例化的类.综上所述,用户定义的 JSON 字典最终会在 python 程序运行时添加到实例命名空间中。

实现上下文管理器来解析 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,它与option_space_dict 的本质不同在于它类似于:

[
 { '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 模板执行的操作。然而,为了得到这个,我们添加到option_space_dictkey4 键在程序的其他地方引起了问题:

# 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])]

我收到错误 TypeError: unhashable type: 'list' 是因为 key4 的值包含一个嵌套列表结构,它是“不可散列的”。

所以我们遇到了障碍。有没有人对我们如何克服这个问题提出建议?能够以这种方式指定我们的 JSON 文件来执行我们对 Jinja2 的要求,同时仍然能够以相同的格式解析数据结构?

谢谢一百万!

【问题讨论】:

  • 定义一种将列表转换为字符串并用于比较/散列的方法?例如,''.join(sorted(my_list))?

标签: python json list dictionary jinja2


【解决方案1】:

您可以规范化您的 key 数据结构以在从 JSON 解析后使用可散列类型。

由于key4 是一个列表,您有两种选择:

  1. 将其转换为顺序很重要的元组。例如,

    key = tuple(key)
    
  2. 将其转换为frozenset,其中顺序无关紧要。例如,

    key = frozenset(key)
    

如果键可以包含字典,那么您将有两个额外的选择:

  1. 将其转换为已排序的元组或它的项元组的冻结集。例如,

    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() 进行了转换。

【讨论】:

  • 感谢您的建议,但是,当我尝试实现您在完整示例中显示的内容时,我仍然遇到了完全相同的错误,即存在 unhashable list 类型。你还有什么想法吗?
  • @AKKO 您能否编辑您的问题以包括错误发生在哪一行,以及来自self.options 的哪个键值对失败?
  • 它实际上的键值对,例如我已经包含在我的问题中的那个:"extra_scripts" : [["Altera/AlteraCommon.lua", "Altera/StratixIV/EP4SGX70HF35C2.lua"]],因为嵌套列表不可散列而失败。
  • 已经有一段时间了,我为迟到的回复道歉,但如果我没记错的话,它是在线的if set([v]) != set(self.option_space_dict[k]):。基本上,self.option_space_dict[k] 将得到值 [["Altera/AlteraCommon.lua", "Altera/StratixIV/EP4SGX70HF35C2.lua"]],它是一个嵌套列表,但 set() 不能直接在这个嵌套列表上工作,它会抛出 TypeError 错误。
  • 我们已经找到了解决方案,我会尽快发布答案。
【解决方案2】:

我们已经设法找出解决这个问题的办法。我们解决方案的主要要点在于我们实现了一个帮助函数来帮助我们将列表实际转换为元组。基本上,回到我的问题,记得我们有这个列表:[["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,并且解决了这个问题。这样做似乎很麻烦,但我们经历这一切的原因是为了有一种客观的方法来比较两个对象,方法是将它们“标准化”为相同的对象类型,一个集合,在以便稍后在我们的源代码中执行一些其他操作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-21
    • 1970-01-01
    • 1970-01-01
    • 2010-12-22
    • 2015-05-02
    • 2022-06-14
    • 2022-10-04
    相关资源
    最近更新 更多