为Python脚本指定布尔过滤表达式
我有一个CSV文件,这种文件是用逗号分隔的值,里面包含学生的信息。文件的列标题包括学生ID、学生名字、学生姓氏、学生邮政编码、学生身高、学生通勤方式等等,后面的每一行则是每个学生的信息。现在,我想写一个Python 2.5的脚本,这个脚本可以接受一个过滤条件作为命令行参数,然后返回符合这个条件的学生(行)。比如,过滤条件可以是下面这样的(用伪代码表示):
"StudentCommuteMethod = Bus AND StudentZipCode = 12345"
然后可以这样调用这个Python脚本:
MyPythonScript.py -filter "<above string>" -i input.csv
这个脚本应该返回所有住在邮政编码为12345的地区,并且是坐公交车通勤的学生的列表。这个过滤条件也可以非常复杂,可以包含任意数量的AND和OR操作符。
问题:
用户指定过滤条件的最佳格式是什么(作为命令行参数)。这个格式应该简单,适合简单的表达式,同时又要足够强大,可以表达所有类型的条件。
- 我想到的格式有(1)SQL,和(2)Python语言本身。但无论哪种情况,我都不知道如何让Python在运行时应用这些过滤条件。也就是说,我该如何处理在命令行输入的表达式,并将其应用到某一行上,以得到真或假?
我希望有一个用户界面,可以以可视化的方式表达过滤条件。也许可以让用户每行输入一个简单的两个操作数的条件,并以某种直观的方式结合这些条件,使用AND和OR。它应该能够生成上面提到的格式的过滤表达式。我是否可以重用某个开源项目来实现这个?
如果你认为有比传递命令行表达式加用户界面更好的解决方案,请随时提出来。最终,用户(一个对编程不太了解的电气工程师)应该能够轻松输入过滤表达式。
谢谢!
注意:我无法控制输入或输出格式(都是CSV文件)。
2 个回答
这是对Danilo建议的一个小改动。你可以通过给eval
传递一个本地字典,来避免对每一行使用exec
来绑定变量。而csv.DictReader返回的字典非常适合这个用途:
import csv, optparse
infile = open('datafile.csv')
reader = csv.DictReader(infile)
parser = optparse.OptionParser()
parser.add_option('--filter', type='string', dest='filter')
options, args = parser.parse_args()
for row in reader:
if eval(options.filter, row):
print row
这里假设输入文件的第一行是列标题,并且你想在表达式中使用的任何标题都必须是有效的Python标识符。
你可能是在尝试用Python重新实现SQL。我觉得直接使用关系型数据库,运行SQL查询会更好。
关于第一个问题,你可以让用户输入Python表达式,然后用eval()
在每一行数据上执行这些表达式。
这里有一个工作示例,它使用exec
将列值绑定到本地变量(我承认这有点黑科技)。为了简洁起见,这里省略了CVS解析的部分。
import optparse, sys
# Assume your CSV data is read into a list of dictionaries
sheet = [
{'StudentId': 1, 'StudentFirstName': 'John', 'StudentLastName': 'Doe', 'StudentZipCode': '12345', 'StudentCommuteMethod': 'Bus'},
{'StudentId': 2, 'StudentFirstName': 'Bob', 'StudentLastName': 'Chen', 'StudentZipCode': '12345', 'StudentCommuteMethod': 'Bus'},
{'StudentId': 3, 'StudentFirstName': 'Jane', 'StudentLastName': 'Smith', 'StudentZipCode': '12345', 'StudentCommuteMethod': 'Train'},
{'StudentId': 4, 'StudentFirstName': 'Dave', 'StudentLastName': 'Burns', 'StudentZipCode': '45467', 'StudentCommuteMethod': 'Bus'},
]
# Options parsing
parser = optparse.OptionParser()
parser.add_option('--filter', type='string', dest='filter')
options, args = parser.parse_args()
# Filter option is required
if options.filter is None:
print >> sys.stderr, 'error: no filter expression given'
sys.exit(1)
# Process rows and build result set
result = []
for row in sheet:
# Bind each column to a local variable (StudentId, StudentFirstName, etc.);
# this allows evaluating Python expressions on a row, for example:
# 'StudentCommuteMethod = "Bus" and StudentZipCode = "12345"'
for col, val in row.iteritems():
exec '%s = %s' % (col, repr(val))
# Apply filter to the row
if eval(options.filter):
result.append(row)
# Print out result set
for row in result:
print row
我用以下过滤表达式进行了测试:
./MyPythonScript.py --filter 'StudentCommuteMethod == "Bus" and StudentZipCode == "12345"'
./MyPythonScript.py --filter 'StudentCommuteMethod == "Bus" or StudentZipCode == "12345"'
(从命令行运行程序时,要注意shell的引号规则。)