为Python脚本指定布尔过滤表达式

2 投票
2 回答
720 浏览
提问于 2025-04-17 01:52

我有一个CSV文件,这种文件是用逗号分隔的值,里面包含学生的信息。文件的列标题包括学生ID、学生名字、学生姓氏、学生邮政编码、学生身高、学生通勤方式等等,后面的每一行则是每个学生的信息。现在,我想写一个Python 2.5的脚本,这个脚本可以接受一个过滤条件作为命令行参数,然后返回符合这个条件的学生(行)。比如,过滤条件可以是下面这样的(用伪代码表示):

"StudentCommuteMethod = Bus AND StudentZipCode = 12345" 

然后可以这样调用这个Python脚本:

MyPythonScript.py -filter "<above string>" -i input.csv

这个脚本应该返回所有住在邮政编码为12345的地区,并且是坐公交车通勤的学生的列表。这个过滤条件也可以非常复杂,可以包含任意数量的AND和OR操作符。

问题:

  1. 用户指定过滤条件的最佳格式是什么(作为命令行参数)。这个格式应该简单,适合简单的表达式,同时又要足够强大,可以表达所有类型的条件。

    • 我想到的格式有(1)SQL,和(2)Python语言本身。但无论哪种情况,我都不知道如何让Python在运行时应用这些过滤条件。也就是说,我该如何处理在命令行输入的表达式,并将其应用到某一行上,以得到真或假?
  2. 我希望有一个用户界面,可以以可视化的方式表达过滤条件。也许可以让用户每行输入一个简单的两个操作数的条件,并以某种直观的方式结合这些条件,使用AND和OR。它应该能够生成上面提到的格式的过滤表达式。我是否可以重用某个开源项目来实现这个?

  3. 如果你认为有比传递命令行表达式加用户界面更好的解决方案,请随时提出来。最终,用户(一个对编程不太了解的电气工程师)应该能够轻松输入过滤表达式。

谢谢!

注意:我无法控制输入或输出格式(都是CSV文件)。

2 个回答

1

这是对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标识符。

1

你可能是在尝试用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的引号规则。)

撰写回答