PyTables 教程(三) 执行撤消/重做功能,使用枚举类型,表中的嵌套结构

2023-05-17,,

翻译自http://www.pytables.org/usersguide/tutorials.html

执行撤消/重做功能

PyTables 支持撤销/重做功能,此功能可让您将标记放置在层次结构操作操作的特定位置,以便您可以将 HDF5 文件弹回(撤消)到特定标记(例如,用于检查层次结构在该点的外观)。您还可以前进到最近的标记(重做)。您甚至可以使用一条指令跳转到您想要的标记,我们很快就会看到。

您可以撤消/重做与对象树管理相关的所有操作,例如在给定对象树中创建、删除、移动或重命名节点(或完整的子层次结构)。您还可以撤消/重做永久节点属性的操作(即创建、删除或修改)。但是,当前无法撤消/重做包含数据集内部修改(包括 Table.append、Table.modify_rows 或 Table.remove_rows 等)的操作。

此功能在许多情况下都很有用,例如在使用多个分支进行模拟时。当您必须在这种情况下选择一条路径时,您可以在那里做一个标记,如果模拟不顺利,您可以返回该标记并开始另一条路径。另一个可能的应用是定义粗粒度操作,这些操作以类似事务的方式运行,即如果操作在运行时发现某种问题,则将数据库返回到其先前的状态。您可能可以设计许多其他场景,其中撤消/重做功能对您有用 3。

一个基本的例子

在本节中,我们将展示撤消/重做功能的基本行为。您可以在 examples/tutorial3-1.py 中找到本示例中使用的代码。下一节将解释一个稍微复杂一些的例子。

首先,创建一个文件

>>> import tables
>>> fileh = tables.open_file("tutorial3-1.h5", "w", title="Undo/Redo demo 1")

And now然后,使用File.enable_undo()方法激活撤销/重做功能:

>>> fileh.enable_undo()

从现在起,PyTables将会被记录我们的操作。现在,我们将创建一个节点(在本例中为 Array 对象):

>>> one = fileh.create_array('/', 'anarray', [3,4], "An array")

对这个点进行标记:

>>> fileh.mark()
1

我们已经标记了动作序列中的当前点。此外,mark() 方法返回了分配给这个新标记的标识符,即 1(标记 #0 是为动作日志开头的隐式标记保留的)。在下一节中,我们将看到您还可以为标记指定名称(有关 mark() 的更多信息,请参阅  File.mark())。现在,我们将创建另一个数组:

>>> another = fileh.create_array('/', 'anotherarray', [4,5], "Another array")

现在,我们可以开始做有趣的事情了。假设我们要退回上一个标记(其值为 1,您还记得吗?)。让我们介绍 undo() 方法(参见 File.undo()):

>>> fileh.undo()

好吧,你认为发生了什么?好吧,让我们看看对象树:

>>> print(fileh)
tutorial3-1.h5 (File) 'Undo/Redo demo 1'
Last modif.: 'Tue Mar 13 11:43:55 2007'
Object Tree:
/ (RootGroup) 'Undo/Redo demo 1'
/anarray (Array(2,)) 'An array'

我们刚刚创建的 /anotherarray 节点发生了什么?你已经猜到了,它已经消失了,因为它是在标记 1 之后创建的。如果你足够好奇,你可能会问——它去哪儿了?嗯,还没有完全删除;它刚刚被移到一个特殊的、隐藏的 PyTables 组中,使其不可见并等待重生的机会。

现在,再次回退,并查看对象树:

>>> fileh.undo()
>>> print(fileh)
tutorial3-1.h5 (File) 'Undo/Redo demo 1'
Last modif.: 'Tue Mar 13 11:43:55 2007'
Object Tree:
/ (RootGroup) 'Undo/Redo demo 1'

糟糕,/anarray 也消失了!别担心,我们很快就会再次访问它。所以,你现在可能有点迷茫;我们在哪个标记处?让我们询问文件处理程序中的  File.get_current_mark() 方法:

>>> print(fileh.get_current_mark())
0

所以我们在标记#0,记得吗?标记 #0 是一个隐式标记,在调用 File.enable_undo() 时启动操作日志时创建。很好,但是您丢失了一些的数组。我们能做些什么呢?使用File.redo()来恢复:

>>> fileh.redo()
>>> print(fileh)
tutorial3-1.h5 (File) 'Undo/Redo demo 1'
Last modif.: 'Tue Mar 13 11:43:55 2007'
Object Tree:
/ (RootGroup) 'Undo/Redo demo 1'
/anarray (Array(2,)) 'An array'

Great!  /anarray 数组再度恢复。检查它是否还活着:

>>> fileh.root.anarray.read()
[3, 4]
>>> fileh.root.anarray.title
'An array'

嗯,它看起来与过去的生活非常相似;更重要的是,它是完全相同的对象:

>>> fileh.root.anarray is one
True

刚刚移到隐藏组又回来了,仅此而已!这很有趣,所以我们将对 /anotherarray 做同样的事情:

>>> fileh.redo()
>>> print(fileh)
tutorial3-1.h5 (File) 'Undo/Redo demo 1'
Last modif.: 'Tue Mar 13 11:43:55 2007'
Object Tree:
/ (RootGroup) 'Undo/Redo demo 1'
/anarray (Array(2,)) 'An array'
/anotherarray (Array(2,)) 'Another array'

欢迎回来,/anotherarray!只需进行一些健全性检查:

>>> assert fileh.root.anotherarray.read() == [4,5]
>>> assert fileh.root.anotherarray.title == "Another array"
>>> fileh.root.anotherarray is another
True

很好,你已经设法让你的数据恢复生机。恭喜!但是等等,当您不再需要此功能时,请不要忘记关闭您的操作日志:

>>> fileh.disable_undo()

这将允许您继续处理您的数据,而实际上不需要 PyTables 来跟踪您的所有操作,更重要的是,在不需要追踪时可以不启用这个功能,从而节省处理时间和空间在您的数据库文件中。

一个更完整的例子

现在,是时候对撤消/重做功能进行更复杂的演示了。在其中,将在代码流的不同部分设置几个标记,我们将看到如何仅通过一个方法调用在这些标记之间跳转。您可以在 examples/tutorial3-2.py中找到此示例中使用的代码

我们进入代码第一部分:

import tables

# Create an HDF5 file
fileh = tables.open_file('tutorial3-2.h5', 'w', title='Undo/Redo demo 2') #'-**-**-**-**-**-**- enable undo/redo log -**-**-**-**-**-**-**-'
fileh.enable_undo() # Start undoable operations
fileh.create_array('/', 'otherarray1', [3,4], 'Another array 1')
fileh.create_group('/', 'agroup', 'Group 1') # Create a 'first' mark
fileh.mark('first')
fileh.create_array('/agroup', 'otherarray2', [4,5], 'Another array 2')
fileh.create_group('/agroup', 'agroup2', 'Group 2') # Create a 'second' mark
fileh.mark('second')
fileh.create_array('/agroup/agroup2', 'otherarray3', [5,6], 'Another array 3') # Create a 'third' mark
fileh.mark('third')
fileh.create_array('/', 'otherarray4', [6,7], 'Another array 4')
fileh.create_array('/agroup', 'otherarray5', [7,8], 'Another array 5')

你可以看到我们是如何在代码流中穿插设置几个标记代表数据库的不同状态的。另请注意,我们为这些标记指定了名称,即‘first’, ‘second’ 和‘third’。

现在,开始在数据库的状态中来回做一些跳转:

# Now go to mark 'first'
fileh.goto('first')
assert '/otherarray1' in fileh
assert '/agroup' in fileh
assert '/agroup/agroup2' not in fileh
assert '/agroup/otherarray2' not in fileh
assert '/agroup/agroup2/otherarray3' not in fileh
assert '/otherarray4' not in fileh
assert '/agroup/otherarray5' not in fileh # Go to mark 'third'
fileh.goto('third')
assert '/otherarray1' in fileh
assert '/agroup' in fileh
assert '/agroup/agroup2' in fileh
assert '/agroup/otherarray2' in fileh
assert '/agroup/agroup2/otherarray3' in fileh
assert '/otherarray4' not in fileh
assert '/agroup/otherarray5' not in fileh # Now go to mark 'second'
fileh.goto('second')
assert '/otherarray1' in fileh
assert '/agroup' in fileh
assert '/agroup/agroup2' in fileh
assert '/agroup/otherarray2' in fileh
assert '/agroup/agroup2/otherarray3' not in fileh
assert '/otherarray4' not in fileh
assert '/agroup/otherarray5' not in fileh

好吧,上面的代码显示了使用File.goto()方法跳转到数据库中的某个标记是多么容易。

还有一些隐式标记用于转到已保存状态的开头或结尾:0 和 -1。标记#0 表示转到已保存操作的开头,即调用方法fileh.enable_undo() 时。转到标记#-1 表示转到最后记录的动作,即代码流中的最后一个动作。

让我们看看当走到动作日志的末尾时会发生什么:

# Go to the end
fileh.goto(-1)
assert '/otherarray1' in fileh
assert '/agroup' in fileh
assert '/agroup/agroup2' in fileh
assert '/agroup/otherarray2' in fileh
assert '/agroup/agroup2/otherarray3' in fileh
assert '/otherarray4' in fileh
assert '/agroup/otherarray5' in fileh # Check that objects have come back to life in a sane state
assert fileh.root.otherarray1.read() == [3,4]
assert fileh.root.agroup.otherarray2.read() == [4,5]
assert fileh.root.agroup.agroup2.otherarray3.read() == [5,6]
assert fileh.root.otherarray4.read() == [6,7]
assert fileh.root.agroup.otherarray5.read() == [7,8]

尝试自己转到操作日志的开头(记住,标记 #0)并检查对象树的内容。

我们几乎完成了这个演示。与往常一样,不要忘记关闭操作日志和数据库:

#'-**-**-**-**-**-**- disable undo/redo log  -**-**-**-**-**-**-**-'
fileh.disable_undo()
# Close the file
fileh.close()

您可能需要查看examples/undo-redo.py中显示的有关撤消/重做功能的其他示例。

使用枚举类型

PyTables支持处理枚举类型enumerated types。这些类型是通过为该类型的变量提供可能的命名值的详尽集合列表来定义的。相同类型的枚举变量通常比较它们之间的相等性,有时是为了顺序,但通常不对其进行操作。

枚举值具有关联的名称具体值。每个名字都是独一无二的,具体的值也是如此。枚举变量始终采用具体值,而不是其名称。通常,具体值不会直接使用,而且经常是完全不相关的。出于同样的原因,枚举变量通常不会与其枚举类型之外的具体值进行比较。对于这种用途,标准变量和常量更合适。

PyTables提供 Enum(请参阅The Enum class)类来提供对枚举类型的支持。 Enum 的每个实例都是一个枚举类型(或枚举)。例如,让我们创建一个颜色枚举

所有这些示例都可以在examples/play-with-enums.py中找到:

>>> import tables
>>> colorList = ['red', 'green', 'blue', 'white', 'black']
>>> colors = tables.Enum(colorList)

这里我们使用了一个给出枚举值名称的简单列表,但我们将具体值的选择留给了 Enum 类。让我们看看枚举对来检查这些值:

>>> print("Colors:", [v for v in colors])
Colors: [('blue', 2), ('black', 4), ('white', 3), ('green', 1), ('red', 0)]

名称已被赋予自动整数具体值。我们可以迭代枚举中的值,但我们通常对访问单个值更感兴趣。我们可以通过将名称作为属性或项目访问来获取与名称关联的具体值(后者对于和不类似于 Python 标识符的名称很有用):

>>> print("Value of 'red' and 'white':", (colors.red, colors.white))
Value of 'red' and 'white': (0, 3)
>>> print("Value of 'yellow':", colors.yellow)
Value of 'yellow':
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File ".../tables/misc/enum.py", line 230, in __getattr__
raise AttributeError(\*ke.args)
AttributeError: no enumerated value with that name: 'yellow'
>>>
>>> print("Value of 'red' and 'white':", (colors['red'], colors['white']))
Value of 'red' and 'white': (0, 3)
>>> print("Value of 'yellow':", colors['yellow'])
Value of 'yellow':
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File ".../tables/misc/enum.py", line 189, in __getitem__
raise KeyError("no enumerated value with that name: %r" % (name,))
KeyError: "no enumerated value with that name: 'yellow'"

了解访问不在枚举中的值如何引发适当的异常。我们也可以做相反的事情,通过使用 Enum 的 __call__() 方法来获取与具体值匹配的名称:

>>> print(f"Name of value {colors.red}:", colors(colors.red))
Name of value 0: red
>>> print("Name of value 1234:", colors(1234))
Name of value 1234:
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File ".../tables/misc/enum.py", line 320, in __call__
raise ValueError(
ValueError: no enumerated value with that concrete value: 1234

您可以看到我们使用枚举类型将具体值转换为枚举中的名称。当然,枚举之外的值无法转换。

枚举列

枚举类型的列可以使用 EnumCol(参见 The Col class and its descendants)类来声明。为了了解它是如何工作的,让我们打开一个新的 PyTables 文件并创建一个表来收集概率实验的模拟结果。在里面,我们有一个装满彩球的袋子;我们取出一个球,并标注提取时间和球的颜色:

>>> h5f = tables.open_file('enum.h5', 'w')
>>> class BallExt(tables.IsDescription):
... ballTime = tables.Time32Col()
... ballColor = tables.EnumCol(colors, 'black', base='uint8')
>>> tbl = h5f.create_table('/', 'extractions', BallExt, title="Random ball extractions")
>>>

We 我们将 ballColor 列声明为枚举类型的颜色,默认值为黑色。我们还声明我们将具体值存储为无符号 8 位整数值4

用一些随机数来填充表格:

>>> import time
>>> import random
>>> now = time.time()
>>> row = tbl.row
>>> for i in range(10):
... row['ballTime'] = now + i
... row['ballColor'] = colors[random.choice(colorList)] # take note of this
... row.append()
>>>

请注意我们如何使用颜色的 __getitem__() 调用来获取要存储在 ballColor 中的具体值。这种将值附加到表的方式会自动检查枚举值的有效性。例如:

>>> row['ballTime'] = now + 42
>>> row['ballColor'] = 1234
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "tableextension.pyx", line 1086, in tableextension.Row.__setitem__
File ".../tables/misc/enum.py", line 320, in __call__
"no enumerated value with that concrete value: %r" % (value,))
ValueError: no enumerated value with that concrete value: 1234

请注意,此检查仅由 row.append() 执行,而不是在 tbl.append() 或 tbl.modify_rows() 等其他方法中执行。现在,在刷新表后,我们可以看到插入的结果:

>>> tbl.flush()
>>> for r in tbl:
... ballTime = r['ballTime']
... ballColor = colors(r['ballColor']) # notice this
... print("Ball extracted on %d is of color %s." % (ballTime, ballColor))
Ball extracted on 1173785568 is of color green.
Ball extracted on 1173785569 is of color black.
Ball extracted on 1173785570 is of color white.
Ball extracted on 1173785571 is of color black.
Ball extracted on 1173785572 is of color black.
Ball extracted on 1173785573 is of color red.
Ball extracted on 1173785574 is of color green.
Ball extracted on 1173785575 is of color red.
Ball extracted on 1173785576 is of color white.
Ball extracted on 1173785577 is of color white.

最后一点,您可能想知道在关闭并重新打开文件后如何访问与 ballColor 关联的枚举。您可以调用 tbl.get_enum(‘ballColor’)(请参阅Table.get_enum())来获取枚举。

枚举数组

EArray 和 VLArray 叶也可以通过 EnumAtom(请参阅The Atom class and its descendants)类来声明存储枚举值,该类的工作方式与表的 EnumCol 非常相似。此外,Array 叶可用于打开本机 HDF 枚举数组。

让我们创建一个示例 EArray,其中包含工作日范围作为二维值:

>>> workingDays = {'Mon': 1, 'Tue': 2, 'Wed': 3, 'Thu': 4, 'Fri': 5}
>>> dayRange = tables.EnumAtom(workingDays, 'Mon', base='uint16')
>>> earr = h5f.create_earray('/', 'days', dayRange, (0, 2), title="Working day ranges")
>>> earr.flavor = 'python'

没什么异常,除了两个细节。首先,我们使用字典而不是列表来显式设置枚举中的具体值。其次,没有创建显式的 Enum 实例!相反,字典作为第一个参数传递给 EnumAtom 的构造函数。如果构造函数接收到列表或字典而不是枚举,它会自动从中构建枚举。

现在让我们向数组提供一些数据:

>>> wdays = earr.get_enum()
>>> earr.append([(wdays.Mon, wdays.Fri), (wdays.Wed, wdays.Fri)])
>>> earr.append([(wdays.Mon, 1234)])

请注意,由于我们没有明确的 Enum 实例,我们被迫使用 get_enum()(请参阅EArray methods)从数组中获取它(我们也可以使用 dayRange.enum)。另请注意,我们能够附加一个无效值 (1234)。数组方法不检查枚举值的有效性。

最后,我们将打印数组的内容:

>>> for (d1, d2) in earr:
... print(f"From {wdays(d1) to {wdays(d2) ({d2 - d1 + 1} days).")
From Mon to Fri (5 days).
From Wed to Fri (3 days).
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File ".../tables/misc/enum.py", line 320, in __call__
"no enumerated value with that concrete value: %r" % (value,))
ValueError: no enumerated value with that concrete value: 1234

这是一个基于具体值操作的例子。它还显示了由于值不属于枚举,值到名称的转换如何失败。

现在我们将关闭文件,这个关于枚举类型的小教程就完成了:

>>> h5f.close()

表中的嵌套结构

PyTables支持处理表对象中的嵌套结构(或者换句话说,嵌套数据类型),允许您定义任意深度的嵌套列。

这为什么有用?假设您的数据在列级别上具有某种结构,您希望在数据模型中表示这种结构。您可以通过创建IsDescription的嵌套子类来实现。好处是能够更轻松地分组和检索数据。下面的例子可能有点傻,但它将作为概念的说明:

import tables as tb

class Info(tb.IsDescription):
"""A sub-structure of NestedDescr"""
_v_pos = 2 # The position in the whole structure
name = tb.StringCol(10)
value = tb.Float64Col(pos=0) colors = tb.Enum(['red', 'green', 'blue']) class NestedDescr(tb.IsDescription):
"""A description that has several nested columns"""
color = tb.EnumCol(colors, 'red', base='uint32')
info1 = tb.Info() class info2(tb.IsDescription):
_v_pos = 1
name = tb.StringCol(10)
value = tb.Float64Col(pos=0) class info3(tb.IsDescription):
x = tb.Float64Col(dflt=1)
y = tb.UInt8Col(dflt=1)

NestedDescr 是根类,其中包含两个子结构:info1 和 info2。注意 info1 是在 NestedDescr 之前定义的类 Info 的实例。 info2 在 NestedDescr 中声明。此外,还有第三个子结构 info3,它依次在子结构 info2 中声明。您可以通过声明特殊的类属性 _v_pos 来定义包含对象中子结构的位置。

创建嵌套表

现在我们已经定义了嵌套结构,让我们创建一个嵌套表,即包含具有子列的列的表:

>>> fileh = tb.open_file("nested-tut.h5", "w")
>>> table = fileh.create_table(fileh.root, 'table', NestedDescr)

完成!现在,要用值填充表,请为每个字段指定一个值。引用嵌套字段可以通过提供完整路径来完成。遵循前面定义的、类似于访问Unix文件系统上的子目录的、使用“/”结构访问的每个子级的规范:

>>> row = table.row
>>> for i in range(10):
... row['color'] = colors[['red', 'green', 'blue'][i % 3]]
... row['info1/name'] = f"name1-{i}"
... row['info2/name'] = f"name2-{i}"
... row['info2/info3/y'] = i
... # Remaining fields will be filled with defaults
... row.append()
>>> table.flush()
>>> table.nrows
10

如上所示,可以通过指定表层次结构中定义的完整路径来访问子结构的字段。

读取嵌套表

现在,如果我们想阅读表格,会发生什么?我们会得到什么样的数据容器?让我们来了解一下:

>>> nra = table[::4]
>>> nra
array([(((1.0, 0), 'name2-0', 0.0), ('name1-0', 0.0), 0L),
(((1.0, 4), 'name2-4', 0.0), ('name1-4', 0.0), 1L),
(((1.0, 8), 'name2-8', 0.0), ('name1-8', 0.0), 2L)],
dtype=[('info2', [('info3', [('x', '>f8'), ('y', '\|u1')]),
('name', '\|S10'), ('value', '>f8')]),
('info1', [('name', '\|S10'), ('value', '>f8')]),
('color', '>u4')])

我们得到的是一个带有复合、嵌套数据类型的 NumPy 数组,即它的 dtype 是名称-数据类型元组的列表。对于表中的每第四行(note [::4]),我们得到一个结果行,总共三个。

您可以以多种不同的方式使用上述对象。例如,您可以使用它将新数据附加到现有表对象:

>>> table.append(nra)
>>> table.nrows
13

或者创建新表:

>>> table2 = fileh.create_table(fileh.root, 'table2', nra)
>>> table2[:]
array([(((1.0, 0), 'name2-0', 0.0), ('name1-0', 0.0), 0L),
(((1.0, 4), 'name2-4', 0.0), ('name1-4', 0.0), 1L),
(((1.0, 8), 'name2-8', 0.0), ('name1-8', 0.0), 2L)],
dtype=[('info2', [('info3', [('x', '<f8'), ('y', '\|u1')]),
('name', '\|S10'), ('value', '<f8')]),
('info1', [('name', '\|S10'), ('value', '<f8')]),
('color', '<u4')])

最后,您可以选择满足任意条件的嵌套值:

>>> names = [ x['info2/name'] for x in table if x['color'] == colors.red ]
>>> names
['name2-0', 'name2-3', 'name2-6', 'name2-9', 'name2-0']

请注意,行访问器不提供自然命名功能,因此您必须指定所需列的绝对路径才能访问它。

使用 Cols 访问器

我们可以使用表的cols属性对象 (参见 The Cols class)从而方便地访问存储在子结构中的数据:

>>> table.cols.info2[1:5]
array([((1.0, 1), 'name2-1', 0.0), ((1.0, 2), 'name2-2', 0.0),
((1.0, 3), 'name2-3', 0.0), ((1.0, 4), 'name2-4', 0.0)],
dtype=[('info3', [('x', '<f8'), ('y', '\|u1')]), ('name', '\|S10'),
('value', '<f8')])

在这里,我们使用cols访问器访问info2子结构,并使用切片操作检索我们感兴趣的数据子集;您可能也认识到这里使用的自然命名方法。我们可以继续并在info3子结构中请求数据:

>>> table.cols.info2.info3[1:5]
array([(1.0, 1), (1.0, 2), (1.0, 3), (1.0, 4)],
dtype=[('x', '<f8'), ('y', '\|u1')])

你还可以使用 _f_col方法去获得这一列的句柄:

>>> table.cols._f_col('info2')
/table.cols.info2 (Cols), 3 columns
info3 (Cols(), Description)
name (Column(), \|S10)
value (Column(), float64)

在这里,您有另一个 Cols 对象句柄,因为 info2 是一个嵌套列。如果您选择非嵌套列,您将获得一个常规的 Column 实例:

>>> table.cols._f_col('info2/info3/y')
/table.cols.info2.info3.y (Column(), uint8, idx=None)

总而言之,cols 访问器是一个非常方便和强大的工具,用于访问嵌套表中的数据。不要犹豫使用它,尤其是在进行交互式工作时。

访问嵌套表的元信息

表有一个描述属性,它返回一个带有表元数据的描述类(参见The Description class)的实例。它有助于理解表结构,包括嵌套列:

>>> table.description
{
"info2": {
"info3": {
"x": Float64Col(shape=(), dflt=1.0, pos=0),
"y": UInt8Col(shape=(), dflt=1, pos=1)},
"name": StringCol(itemsize=10, shape=(), dflt='', pos=1),
"value": Float64Col(shape=(), dflt=0.0, pos=2)},
"info1": {
"name": StringCol(itemsize=10, shape=(), dflt='', pos=0),
"value": Float64Col(shape=(), dflt=0.0, pos=1)},
"color": EnumCol(enum=Enum({'blue': 2, 'green': 1, 'red': 0}), dflt='red',
base=UInt32Atom(shape=(), dflt=0), shape=(), pos=2)}

如您所见,它提供了有关表格中列的格式和结构的信息。

您可以使用带有 description 属性的自然命名方法来访问子列的元数据:

>>> table.description.info1
{"name": StringCol(itemsize=10, shape=(), dflt='', pos=0),
"value": Float64Col(shape=(), dflt=0.0, pos=1)}
>>> table.description.info2.info3
{"x": Float64Col(shape=(), dflt=1.0, pos=0),
"y": UInt8Col(shape=(), dflt=1, pos=1)}

_v_nested_names 属性提供列的名称以及嵌入其中的结构:

>>> table.description._v_nested_names
[('info2', [('info3', ['x', 'y']), 'name', 'value']),
('info1', ['name', 'value']), 'color']
>>> table.description.info1._v_nested_names
['name', 'value']

无论访问结构的哪个级别,_v_nested_names 的输出都包含相同类型的信息。这是因为即使对于嵌套结构,也会返回一个 Description 对象。

可以使用 _v_nested_descr 属性创建模仿嵌套表状结构的数组:

>>> import numpy
>>> table.description._v_nested_descr
[('info2', [('info3', [('x', '()f8'), ('y', '()u1')]), ('name', '()S10'),
('value', '()f8')]), ('info1', [('name', '()S10'), ('value', '()f8')]),
('color', '()u4')]
>>> numpy.rec.array(None, shape=0,
dtype=table.description._v_nested_descr)
recarray([],
dtype=[('info2', [('info3', [('x', '>f8'), ('y', '|u1')]),
('name', '|S10'), ('value', '>f8')]),
('info1', [('name', '|S10'), ('value', '>f8')]),
('color', '>u4')])
>>> numpy.rec.array(None, shape=0,
dtype=table.description.info2._v_nested_descr)
recarray([],
dtype=[('info3', [('x', '>f8'), ('y', '|u1')]), ('name', '|S10'),
('value', '>f8')])

最后但并非最不重要的是,Description 类有一个特殊的迭代器:_f_walk,它返回表的不同列:

>>> for coldescr in table.description._f_walk():
... print(f"column--> {coldescr}")
column--> Description([('info2', [('info3', [('x', '()f8'), ('y', '()u1')]),
('name', '()S10'), ('value', '()f8')]),
('info1', [('name', '()S10'), ('value', '()f8')]),
('color', '()u4')])
column--> EnumCol(enum=Enum({'blue': 2, 'green': 1, 'red': 0}), dflt='red',
base=UInt32Atom(shape=(), dflt=0), shape=(), pos=2)
column--> Description([('info3', [('x', '()f8'), ('y', '()u1')]), ('name', '()S10'),
('value', '()f8')])
column--> StringCol(itemsize=10, shape=(), dflt='', pos=1)
column--> Float64Col(shape=(), dflt=0.0, pos=2)
column--> Description([('name', '()S10'), ('value', '()f8')])
column--> StringCol(itemsize=10, shape=(), dflt='', pos=0)
column--> Float64Col(shape=(), dflt=0.0, pos=1)
column--> Description([('x', '()f8'), ('y', '()u1')])
column--> Float64Col(shape=(), dflt=1.0, pos=0)
column--> UInt8Col(shape=(), dflt=1, pos=1)

有关 Description 对象的属性和方法的完整列表,请参阅 The Description class类。

好了,这是本教程的结尾。与往常一样,请记住关闭您的文件:

>>> fileh.close()

最后,您可能需要查看生成的数据文件。

$ ptdump -d nested-tut.h5
/ (RootGroup) ''
/table (Table(13,)) ''
Data dump:
[0] (((1.0, 0), 'name2-0', 0.0), ('name1-0', 0.0), 0L)
[1] (((1.0, 1), 'name2-1', 0.0), ('name1-1', 0.0), 1L)
[2] (((1.0, 2), 'name2-2', 0.0), ('name1-2', 0.0), 2L)
[3] (((1.0, 3), 'name2-3', 0.0), ('name1-3', 0.0), 0L)
[4] (((1.0, 4), 'name2-4', 0.0), ('name1-4', 0.0), 1L)
[5] (((1.0, 5), 'name2-5', 0.0), ('name1-5', 0.0), 2L)
[6] (((1.0, 6), 'name2-6', 0.0), ('name1-6', 0.0), 0L)
[7] (((1.0, 7), 'name2-7', 0.0), ('name1-7', 0.0), 1L)
[8] (((1.0, 8), 'name2-8', 0.0), ('name1-8', 0.0), 2L)
[9] (((1.0, 9), 'name2-9', 0.0), ('name1-9', 0.0), 0L)
[10] (((1.0, 0), 'name2-0', 0.0), ('name1-0', 0.0), 0L)
[11] (((1.0, 4), 'name2-4', 0.0), ('name1-4', 0.0), 1L)
[12] (((1.0, 8), 'name2-8', 0.0), ('name1-8', 0.0), 2L)
/table2 (Table(3,)) ''
Data dump:
[0] (((1.0, 0), 'name2-0', 0.0), ('name1-0', 0.0), 0L)
[1] (((1.0, 4), 'name2-4', 0.0), ('name1-4', 0.0), 1L)
[2] (((1.0, 8), 'name2-8', 0.0), ('name1-8', 0.0), 2L)

本节中的大部分代码也可以在examples/nested-tut.py中找到。

PyTables 提供了一套全面的工具来处理嵌套结构并满足您的分类需求。尽量避免将数据嵌套得太深,因为这可能会导致非常长且令人费解的一系列列表、元组和描述对象,这些内容可能难以阅读和理解。

PyTables发行版中的其他示例

看看目录examples/中的其余示例,并尝试理解它们。我们编写了几个实用的示例脚本,让您了解 PyTables 的功能、处理 HDF5 对象的方式以及如何在现实世界中使用它。


1       也支持将数据附加到数组,但您需要创建称为 EArray 的特殊对象(有关详细信息,请参阅 The EArray class 类)。
2       请注意,您不仅可以将标量值附加到表中,还可以将完全多维数组对象附加到表中。
3       您甚至可以暂时隐藏节点。你能想出办法吗?
4       实际上,目前仅支持整型数值,但将来可能会发生变化。
Copyright 2011–2020, PyTables maintainers

PyTables 教程(三) 执行撤消/重做功能,使用枚举类型,表中的嵌套结构的相关教程结束。

《PyTables 教程(三) 执行撤消/重做功能,使用枚举类型,表中的嵌套结构.doc》

下载本文的Word格式文档,以方便收藏与打印。