PyYAML 解析成任意对象

5 投票
1 回答
1908 浏览
提问于 2025-04-15 20:25

我有一个Python 2.6的程序和一个YAML定义(使用PyYAML):

import yaml

x = yaml.load(
    """
        product:
           name     : 'Product X'
           sku      : 123
           features :
             - size    :  '10x30cm'
               weight  :  '10kg'

         """
    )

print type(x)
print x


运行后会得到以下输出:
<type 'dict'>
{'product': {'sku': 123, 'name': 'Product X', 'features': [{'weight': '10kg', 'size': '10x30cm'}]}}

我可以用x中的字段创建一个对象吗?

我希望能做到以下几点:

print x.features[0].size

我知道可以从已有的类创建一个实例,但这不是我在这个特定场景下想要的。

编辑:

  • 更新了关于“强类型对象”的混淆部分。
  • 根据Alex Martelli的建议,将对features的访问改为索引器。

1 个回答

8

你有一个字典,里面的键是字符串,值可以是数字、嵌套的字典或者列表。你想把这个字典包装成一个实例,这样就可以用属性访问的方式来代替字典的索引,用“用索引调用”的方式来代替列表的索引。至于“强类型”跟这个有什么关系,或者为什么你觉得 .features(0).features[0] 更好(用列表索引的方式更自然啊!),我不太明白,但这确实是可行的。比如,可以用一个简单的方法:

def wrap(datum):
  # don't wrap strings
  if isinstance(datum, basestring):
    return datum
  # don't wrap numbers, either
  try: return datum + 0
  except TypeError: pass
  return Fourie(datum)

class Fourie(object):
  def __init__(self, data):
    self._data = data
  def __getattr__(self, n):
    return wrap(self._data[n])
  def __call__(self, n):
    return wrap(self._data[n])

所以 x = wrap(x['product']) 应该能满足你的需求(我不知道你为什么想跳过那一层,因为你的整体逻辑显然需要 x.product.features(0).size,但显然这种跳过在调用时更合适,而不是在我刚刚展示的包装类或包装工厂函数中硬编码)。

编辑: 正如提问者所说,他确实想要 features[0] 而不是 features(0),只需将最后两行改为:

  def __getitem__(self, n):
    return wrap(self._data[n])

也就是说,定义 __getitem__(这个魔法方法用于索引)而不是 __call__(这个魔法方法用于实例调用)。

如果不使用“现有类”(这里是 Fourie),可以根据被包装的字典动态创建一个新类——这也是可行的,但这就有点复杂了,甚至可以说是“黑魔法”,而且我想不出有什么实际的好处。

如果提问者能明确说明他为什么想要动态创建类,他认为这样能得到什么好处等等,我会展示怎么做(而且,可能我还会告诉他,想要的好处其实并不存在;-)。不过,简单性在任何编程工作中都是很重要的,使用“深奥的魔法”而不是像上面那样简单明了的代码,通常不是个好主意!-)

撰写回答