Python数据结构/对象以模拟静态多维表格
我刚刚重新开始写代码,之前停了几年,现在我想设计一种多层次的静态表单,让我可以方便地获取和操作特定的表单层级或整个子树。
表单层级示例:
- 我的表单
- 问题 1
- 部分 1
- 问题 1.1
- 部分 2
- 问题 2.1
- 子部分 1
- 问题 2.1.1
- 问题 2.1.2
- 问题 2
每个问题都会有多个属性(比如问题的文本、是否是必填项等等),而且问题可以出现在层级的任何位置。
我希望能够做到类似这样的操作:
>>> MyForm.getQuestionObjects()
[Question1, Question1_1, Question2_1, Question2_1_1, Question2_1_2, Question2]
>>> MyForm.Part2.getQuestionObjects()
[Question2_1, Question2_1_1, Question2_1_2]
或者类似这样的操作:
>>> # Get questions (return class members)
>>> MyForm.SubPart1.getQuestions()
(('2.1.1 text', otherAttributes), ('2.1.2 text', otherAttributes))
>>> # Get questions -- but replace an attribute on 2.1.2
>>> MyForm.Part2.getQuestions(replace_attr('Question_2_1_2', 'text', 'New text'))
(('2.1.1 text', otherAttributes), ('New text', otherAttributes))
我一直在尝试用嵌套类来实现这个功能,但这让我很头疼,而且在Python中支持得也不太好。不过即使我能找到用嵌套类的解决方案,我还是在想有没有更好的方法来存储这些表单信息,以便让非程序员也能轻松编辑(可能是一个纯文本模板),然后在运行时加载数据,因为这些数据是静态的,我会经常需要它们在内存中。表单数据更新的频率不会超过每月一次。无论我怎么存储数据,我都想找到一个好的数据结构来表示、遍历和操作这些数据。
- 有没有办法创建一个这样的分层属性对象?
- 我能否使用多维命名元组来实现?
- 还有其他的想法吗?
谢谢大家的评论。
3 个回答
我想分享一下我在使用ElementTree时学到的一些东西,特别是lxml这个版本的ElementTree和lxml.objectify,还有一些XPath的用法。其实,XML也可以简化成只有<part>
和<question>
这两个标签,名字可以作为属性来存储。
questions.xml
<myform>
<question1>Question 1</question1>
<part1 name="Part 1">
<question1_1>Question 1.1</question1_1>
</part1>
<part2 name="Part 2">
<question2_1 attribute="stuff">Question 2.1</question2_1>
<subpart1 name="SubPart 1">
<question2_1_1>Question 2.1.1</question2_1_1>
<question2_1_2>Question 2.1.2</question2_1_2>
</subpart1>
</part2>
<question2>Question 2</question2>
</myform>
questions.py
from lxml import etree
from lxml import objectify
# Objectify adds some python object-like syntax and other features.
# Important note: find()/findall() in objectify uses ETXPath, which supports
# any XPath expression. The union operator, starts-with(), and local-name()
# expressions below don't work with etree.findall.
# Using etree features
tree = objectify.parse('questions.xml')
root = tree.getroot()
# Dump root to see nodes and attributes
print etree.dump(root)
# Pretty print XML
print etree.tostring(root, pretty_print=True)
# Get part2 & all of its children
part2_and_children = root.findall(".//part2 | //part2//*")
# Get all Part 2 children
part2_children = root.findall(".//*[@name='Part 2']//*[starts-with(local-name(), 'question')]")
# Get dictionary of attributes for Question 2.1
list_of_dict_of_attributes = root.find(".//question2_1")[0].items()
# Access nodes like python objects
# Get all part2 question children
part2_question_children = root.part2.findall(".//*[starts-with(local-name(), 'question')]")
# Get text of question 2.1
text2_1 = root.part2.question2_1.text
# Get dictionary of attributes for Question 2.1
q2_1_attrs = root.part2.question2_1[0].items()
在Python中,嵌套类并没有什么复杂或者不被支持的地方,只是它们本身并没有什么特别的功能。不要指望它们能像Java中的内部类那样自动链接到它们的拥有者实例:嵌套类其实就是普通的类,只不过它们的类对象被存储在另一个类的属性里。这样并不会给你带来什么帮助。
有没有办法创建一个分层属性的对象呢?
当然可以,但你可能更好地利用Python已有的序列类,这样可以享受到它们所有现有操作的好处。例如,一个表单的“部分”可以简单地是一个列表,同时还有一个标题:
class FormPart(list):
def __init__(self, title, *args):
list.__init__(self, *args)
self.title= title
def __repr__(self):
return 'FormPart(%r, %s)' % (self.title, list.__repr__(self))
现在你可以这样说 form= FormPart('我的表单', [问题, 表单部分...])
,并且可以使用普通的列表索引和切片来访问里面的问题和表单部分。
接下来,问题可以是一个不可变的东西,比如元组,但也许你希望里面的项有好看的属性名称。所以可以在 tuple
上添加这些属性:
class FormQuestion(tuple):
def __new__(cls, title, details= '', answers= ()):
return tuple.__new__(cls, (title, details, answers))
def __repr__(self):
return 'FormQuestion%s' % tuple.__repr__(self)
title= property(operator.itemgetter(0))
details= property(operator.itemgetter(1))
answers= property(operator.itemgetter(2))
现在你可以这样定义你的数据:
form= FormPart('MyForm', [
FormQuestion('Question 1', 'Why?', ('Because', 'Why not?')),
FormPart('Part 1', [
FormQuestion('Question 1.1', details= 'just guess'),
]),
FormPart('Part 2', [
FormQuestion('Question 2.1'),
FormPart('SubPart 1', [
FormQuestion('Question 2.1.1', answers= ('Yes')),
]),
]),
FormQuestion('Question 2'),
])
并且可以这样访问它:
>>> form[0]
FormQuestion('Question 1', 'Why?', ('Because', 'Why not?'))
>>> form[1].title
'Part 1'
>>> form[2][1]
FormPart('SubPart 1', [FormQuestion('Question 2.1.1', '', 'Yes')])
现在为了遍历你的层级结构,你可以在 FormPart
上定义:
def getQuestions(self):
for child in self:
for descendant in child.getQuestions():
yield descendant
以及在 FormQuestion
上:
def getQuestions(self):
yield self
现在你有了一个返回 FormQuestions 的后代生成器:
>>> list(form[1].getQuestions())
[FormQuestion('Question 1.1', 'just guess', ())]
>>> list(form.getQuestions())
[FormQuestion('Question 1', 'Why?', ('Because', 'Why not?')), FormQuestion('Question 1.1', 'just guess', ()), FormQuestion('Question 2.1', '', ()), FormQuestion('Question 2.1.1', '', 'Yes'), FormQuestion('Question 2', '', ())]
我会把这种层级数据存储在XML文件里。你可以使用xml.etree.ElementTree
这个标准模块,把XML文件加载到Python中,形成一个层级的数据结构。然后你可以对这个数据结构进行修改,最后再把它保存回文件里。这样你就不用担心实际的数据结构是怎样的,因为ElementTree会自动帮你构建。
可以在Python手册中查看xml.etree.ElementTree的相关内容。更多信息可以在这里找到:
(Python中还有其他成熟的解决方案,可以把XML文件加载到不同的数据结构中。你可以选择一个最简单的来完成你的任务。谷歌是你的好帮手。:-))