Chapter 04 Table结构

无论是运维监控,还是数据分析,我们都是要向用户展示一个聚合后的最终结果,这个最终结果的形式,通常会是二维表格。然而,要映射成二维表格的数据形式却是多种多样的,可能是一个二维列表,也可能是一个对象元组,你往往需要频繁的花大量时间去将这些数据与表格的行和列小心翼翼的对应起来。Girlfriend内置了Table对象来解决将这些不同格式的数据映射为二维表的问题。

Table的代码位于girlfriend.data.table模块中。一个Table对象由表格名称、标题行、数据行这三个要素构成。构建一个简单的Table:

>>> from girlfriend.data.table import ListTable, Title
>>> table = ListTable("users",
    (Title("id", u"编号"), Title("username", u"用户名"), Title("score", u"积分")),
    [(1, "Sam", 10), (2, "Jack", 9), (3, "James", 6)])
>>> table
<girlfriend.data.table.ListTable object at 0x10afcf9d0>
>>> print table
+------+--------+------+
| 编号 | 用户名 | 积分   |
+------+--------+------+
|  1   |  Sam   |  10  |
|  2   |  Jack  |  9   |
|  3   | James  |  6   |
+------+--------+------+
>>> table[0].id
1
>>> table[0].username
'Sam'
>>> table[0][0]
1
>>> table[0][2]
10
>>> table[0]["username"]
'Sam'
>>> for row in table:
...     print row.id, row.username, row.score
... 
1 Sam 10
2 Jack 9
3 James 6

我们看到,通过使用Table对元组列表进行包装后,我们可以使用数字索引访问表格的每一行,更有趣的是,我们既可以使用数字索引也可以像访问对象属性或者字典元素一样访问某一行的某一列。Title构造方法的第一个参数定义了该列的属性名称,第二个参数定义了要展示的列名。尽量使用属性名称访问而不是使用数字索引可以提高代码的可读性。

girlfriend内置了ListTable、DictTable和ObjectTable三个实现,当你的每一行是可以通过数字索引访问的序列时(比如list、tuple)那么你可以使用ListTable来对其进行包装、当你的每一行都是由kv结构对象组成时,你可以使用DictTable,当你的每一行是自定义对象时,你可以使用ObjectTable。虽然包装的数据类型差异很大,但这三种Table对象在行为上是一致的:

DictTable

>>> from girlfriend.data.table import Title, DictTable
>>> table = DictTable("users", (Title("id", u"编号"), Title("username", u"用户名"), Title("score", u"积分")), [{"id":1, "username": "Sam", "score": 10}, {"id":2, "username": "Jack", "score": 20}])
>>> table[0].id
1
>>> table[0][0]
1
>>> table[0][1]
'Sam'
>>> table[0]["username"]
'Sam'

ObjectTable

>>> class User(object):
...     def __init__(self, id_, username, score):
...         self.id = id_
...         self.username = username
...         self.score = score
... 
>>> table = ObjectTable("users", (Title("id", u"编号"), Title("username", u"用户名"), Title("score", u"积分")), [User(1, "Sam", 10), User(2, "Jack", 20)])
>>> table[0][0]
1
>>> table[0][1]
'Sam'
>>> table[0].id
1
>>> table[0]["username"]
'Sam'

很多时候我们不知道要去映射的对象是什么格式的,那么我们可以使用girlfriend.data.table.TableWrapper去自动映射,TableWrapper可以自动感知行对象的类型,从而使用不同类型的Table进行包装:

>>> TableWrapper(
...     name="users",
...     titles=(Title("id", u"编号"), Title("username", u"用户名")))([(1, "Sam", 1), (2, "Jack", 2)])
...

TableWrapper会使用第一行的类型来探测应该使用哪一种Table,如果你的列表中包含各种各样的元素,那么使用TableWrapper就会出现错误,此时应该统一数据格式后再进行Table包装。

关于Table的性能

通常情况下无需关心Table的性能,Table不会对被包装的对象进行任何复制操作,你不必担心使用Table会造成内存消耗翻倍以及过长时间的初始化,Table只是对行对象进行简单的包装,而且包装只发生在当你访问这一行的时候。另外需要注意的是,无论使用数字索引还是字典key或是对象属性来访问具体的单元格,Table都不会执行遍历查找,因为在一开始的构造方法中,Table就自动根据对象类型生成了不同的mapping,比如对于ListTable,会生成属性名称到数字索引的mapping,当你使用属性名称访问的时候,会先通过mapping找到真实的索引值,然后再通过数字索引进行访问,这样即使你的表再宽,访问效率也是O(1)的,唯一的牺牲是在构造方法中对所有Title执行了一次遍历。

results matching ""

    No results matching ""