使用namedtuple进行嵌套

11 投票
4 回答
5254 浏览
提问于 2025-04-17 07:44

我在用Python处理数据时遇到了一些麻烦,想把数据整理成我想要的样子。

简单来说,我有一个程序可以读取二进制数据,并提供一些函数来绘图和分析这些数据。

我的数据有主要标题,然后是一些子标题,这些子标题的数据类型可能各不相同。

我希望能像这样访问我的数据:

>>> a = myDatafile.readit()
>>> a.elements.hydrogen.distributionfunction
(a big array)
>>> a.elements.hydrogen.mass
1
>>> a.elements.carbon.mass
12

但我在运行程序时才知道这些原子的名字。

我尝试过使用namedtuple,比如在读取完所有原子名字后:

self.elements = namedtuple('elements',elementlist)

这里的elementlist是一个字符串列表,比如说包含('hydrogen','carbon')。但问题是,我无法像这样嵌套这些数据:

for i in range(0,self.nelements):
    self.elements[i] = namedtuple('details',['ux','uy','uz','mass','distributionfunction'])

然后通过这样的方式访问值:

self.elements.electron.distributionfunction.

也许我完全搞错了。我对Python还不太熟悉。如果不需要动态命名变量,这个问题应该会简单很多。

希望我能清楚地表达我想要实现的目标!

4 个回答

2

这里有一种方法,可以通过递归的方式从嵌套的数据中创建命名元组。

from collections import Mapping, namedtuple


def namedtuplify(mapping, name='NT'):  # thank you https://gist.github.com/hangtwenty/5960435
    """ Convert mappings to namedtuples recursively. """
    if isinstance(mapping, Mapping):
        for key, value in list(mapping.items()):
            mapping[key] = namedtuplify(value)
        return namedtuple_wrapper(name, **mapping)
    elif isinstance(mapping, list):
        return [namedtuplify(item) for item in mapping]
    return mapping

def namedtuple_wrapper(name, **kwargs):
    wrap = namedtuple(name, kwargs)
    return wrap(**kwargs)


stuff = {'data': {'elements': {'hydrogen': {'distributionfunction': 'foo'}, 
  'nitrogen': {'xyzfunction': 'bar', 
    'distributionfunction': 'baz'}
  },
  'compound': {'water': {'distributionfunction': 'lorem'}, 
    'hcl': {'xyzfunction': 'ipsum'}}}
 }

example = namedtuplify(stuff)

example.data.elements.hydrogen.distributionfunction  # 'foo'
3

如果你的元素名称是动态的,也就是说它们是在程序运行时从数据中获取的,你可以把它们放到一个字典里,然后像这样访问它们。

elements['hydrogen'].mass

但是如果你想用点号来表示这些元素,你可以在运行时创建属性,比如这样。

from collections import namedtuple

class Elements(object):
    def add_element(self, elementname, element):
        setattr(self, elementname, element)

Element = namedtuple('Element', ['ux','uy','uz','mass','distributionfunction'])

elements = Elements()
for data in [('hydrogen',1,1,1,1,1), ('helium',2,2,2,2,2), ('carbon',3,3,3,3,3)]:
    elementname = data[0]
    element = Element._make(data[1:])
    elements.add_element(elementname, element)

print elements.hydrogen.mass
print elements.carbon.distributionfunction

这里我假设你有的数据是这样的,不过如果你的数据格式不同,也可以用类似的方法来处理。

5

在不了解你的数据的情况下,我们只能提供一个通用的解决方案。

考虑到前两行包含了标题和副标题,你可以通过某种方式确定它们的层级关系。你需要做的就是创建一个层级字典。

比如,扩展你的例子:

data.elements.hydrogen.distributionfunction
data.elements.nitrogen.xyzfunction
data.elements.nitrogen.distributionfunction
data.compound.water.distributionfunction
data.compound.hcl.xyzfunction

所以我们需要创建一个这样的字典:

{'data':{'elements':{'hydrogen':{'distributionfunction':<something>}
                     'nitrogen':{'xyzfunction':<something>,
                           'distributionfunction':<something>}
                }
       compound:{'water':{'distributionfunction':<something>}
                 'hcl':{'xyzfunction':<something>}
                }
       }
 }

如何填充这个字典取决于你的数据,这现在很难说。不过,字典的键应该从标题中提取,而你需要以某种方式将数据映射到字典中空白位置的相应值。

一旦这个映射完成,你就可以像这样访问它:

 yourDict['data']['compound']['hcl']['xyzfunction']

撰写回答