代码的挑战源于关于" Hashable"的问题。属性

时间:2014-12-04 10:23:13

标签: python json list dictionary jinja2

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

实现上下文管理器来解析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"是{ {1}}此处,虽然对于我们的计划来说,它要大得多:

'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'] } ] 可以很好地满足我们对jinja2模板的要求。但是,为了实现这一点,我们添加到option_space的{​​{1}}密钥在程序的其他位置引起了问题:

key4

我收到错误option_space_dict,因为# 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])] 的值包含嵌套列表结构,这是一种“不可用的”#。

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

万分感谢!

2 个答案:

答案 0 :(得分:0)

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

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

  1. 将其转换为订单重要的元组。如,

    key = tuple(key)
    
  2. 将其转换为订单无关紧要的frozenset。如,

    key = frozenset(key)
    
  3. 如果某个键可以包含字典,那么您还有两个选项:

    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)
      
    3. 根据您的密钥的简单程度或复杂程度,您可能必须递归地应用转换。

      由于您的密钥直接来自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()转换了密钥。

答案 1 :(得分: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函数会将该嵌套列表转换为元组对象,而不是迭代地执行它,它实际上构造了一个它返回自身的set对象。该函数的定义是:

# 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,并解决了问题。这样做似乎很麻烦,但我们经历这一切的原因是为了有一个客观的方法来比较两个对象的正常化和#34;它们是相同的对象类型,一组,以便稍后在我们的源代码中执行一些其他操作。