请原谅这个回复的长度。
如果我对您的理解正确,您希望对括号进行简单的语法检查以确保它们正确平衡。在您的问题中,您指定了一个基于简单计数的测试,但正如其他人指出的那样,这并没有捕捉到像“([)]”这样的东西。
我还想对您代码的其他方面提出一些建设性的批评。
首先,最好从命令行获取文件名,而不是提示输入。这样您就可以在开发程序时轻松地重复运行程序,而无需一直输入文件名。这是你的方式:
$ python foo.py
给出文件名:data
[一些输出]
$ python foo.py
给出一个文件名:data
[一些输出]
$ python foo.py
给出一个文件名:data
[一些输出]
每次都需要输入文件名。您无需多次输入命令即可运行该程序。第一次之后,您可以使用箭头键从 shell 的命令历史记录中获取它。如果你从命令行获取文件名,你可以这样做:
$ python foo.py testfile
[一些输出]
$ python foo.py testfile
[一些输出]
$ python foo.py testfile
[一些输出]
这样,当您第二次测试时,您不需要输入超过向上箭头和 Enter 键的内容。这是一个小小的便利,但很重要:当您开发软件时,即使是很小的事情也会开始烦人。就像你在长途跋涉时脚下的一大粒沙子:前几公里你甚至不会注意到它,但再走几公里后,你就会流血。
在 Python 中,要访问命令行参数,您需要
sys.argv
列表。对您的程序的相关更改:
import sys
filename = sys.argv[1]
如果你确实想提示,你应该使用内置input
函数以外的东西。它将用户键入的任何内容解释为 Python 表达式,这会导致各种问题。您可以使用sys.stdin.readline
.
无论如何,我们现在已经将文件名安全地存储在filename
变量中。是时候用它做点什么了。你的parentheses
函数几乎可以做所有事情,经验表明这通常不是最好的做事方式。相反,每个函数都应该只做一件事,但要做好。
我建议您应该将实际打开和关闭文件的部分与实际计数分开。这将简化计数的逻辑,因为它不需要担心其余部分。在代码中:
import sys
def check_parentheses(f):
pass # we'll come to this later
def main():
filename = sys.argv[1]
try:
f = file(filename)
except IOError:
sys.stderr.write('Error: Cannot open file %s' % filename)
sys.exit(1)
check_parentheses(f)
f.close()
main()
除了重新安排事情之外,我还改变了其他一些事情。首先,我将错误消息写入标准错误输出。这是执行此操作的正确方法,并且意味着 shell 用户重定向输出的意外更少。(如果这对您没有任何意义,请不要担心,暂时接受它。)
其次,如果出现错误,我会使用sys.exit(1)
. 这告诉启动程序的人它失败了。在 Unix shell 中,这使您可以执行以下操作:
if python foo.py inputfile
then
echo "inputfile is OK!"
else
echo "inputfile is BAD!"
fi
当然,shell 脚本可能会做一些比仅仅报告成功或失败更有趣的事情。例如,它可能会删除所有损坏的文件,或者给写这些文件的人发送电子邮件,要求他们修复它们。美妙之处在于编写检查程序的您不需要关心。您只需正确设置程序退出代码,并让编写 shell 脚本的人操心其余的事情。
下一步是实际读取文件的内容。这可以通过多种方式完成。最简单的方法是逐行执行,如下所示:
for line in f:
# do something with the line
然后我们需要查看该行中的每个字符:
for line in f:
for c in line:
# do something with the character
我们现在已经准备好开始检查括号了。正如其他人所建议的那样,堆栈是合适的数据结构。堆栈基本上是一个列表(或数组),您可以在其中将项目添加到一端,然后以相反的顺序取出它们。把它想象成一堆硬币:你可以在顶部添加一枚硬币,你可以取出最上面的硬币,但你不能从中间或底部取出一个。
(嗯,你可以,如果你这样做,这是一个巧妙的把戏,但计算机是简单的野兽,会被魔术弄得心烦意乱。)
我们将使用 Python 列表作为堆栈。要添加一个项目,我们使用列表的append
方法,要删除我们使用的pop
方法。一个例子:
stack = list()
stack.append('(')
stack.append('[')
stack.pop() # this will return '['
stack.pop() # this will return '('
要查看堆栈中最顶层的项目,我们使用stack[-1]
(换句话说,列表中的最后一个项目)。
我们使用堆栈如下:当我们找到一个左括号('(')、方括号('[')或大括号('{')时,我们将它放入堆栈。当我们找到一个右括号时,我们检查栈顶的元素,并确保它与关闭的元素匹配。如果不是,我们打印一个错误。像这样:
def check_parentheses(f):
stack = list()
for line in f:
for c in line:
if c == '(' or c == '[' or c == '{':
stack.append(c)
elif c == ')':
if stack[-1] != '(':
print 'Error: unmatched )'
else:
stack.pop()
elif c == ']':
if stack[-1] != '[':
print 'Error: unmatched ]'
else:
stack.pop()
elif c == '}':
if stack[-1] != '{':
print 'Error: unmatched }'
else:
stack.pop()
现在确实可以找到各种不匹配的括号。我们可以通过报告我们发现问题的行和列来稍微改进它。我们需要一个行号和列号计数器。
def error(c, line_number, column_number):
print 'Error: unmatched', c, 'line', line_number, 'column', column_number
def check_parentheses(f):
stack = list()
line_number = 0
for line in f:
line_number = line_number + 1
column_number = 0
for c in line:
column_number = column_number + 1
if c == '(' or c == '[' or c == '{':
stack.append(c)
elif c == ')':
if stack[-1] != '(':
error(')', line_number, column_number)
else:
stack.pop()
elif c == ']':
if stack[-1] != '[':
error(']', line_number, column_number)
else:
stack.pop()
elif c == '}':
if stack[-1] != '{':
error('}', line_number, column_number)
else:
stack.pop()
还要注意我是如何添加一个辅助函数error
来实际打印错误消息的。如果您想更改错误消息,您现在只需要在一个地方进行。
需要注意的另一件事是处理结束符号的情况都非常相似。我们也可以把它变成一个函数。
def check(stack, wanted, c, line_number, column_number):
if stack[-1] != wanted:
error(c, line_number, column_number)
else:
stack.pop()
def check_parentheses(f):
stack = list()
line_number = 0
for line in f:
line_number = line_number + 1
column_number = 0
for c in line:
column_number = column_number + 1
if c == '(' or c == '[' or c == '{':
stack.append(c)
elif c == ')':
check(stack, '(', ')', line_number, column_number)
elif c == ']':
check(stack, '[', ']', line_number, column_number)
elif c == '}':
check(stack, '{', '}', line_number, column_number)
该程序可以进一步完善,但现在应该足够了。我将在最后包含整个代码。
请注意,该程序只关心各种括号。如果您真的想检查整个 Python 程序的语法正确性,则需要解析 Python 的所有语法,这非常复杂,对于一个 Stack Overflow 答案来说太多了。如果那是您真正想要的,请提出后续问题。
整个程序:
import sys
def error(c, line_number, column_number):
print 'Error: unmatched', c, 'line', line_number, 'column', column_number
def check(stack, wanted, c, line_number, column_number):
if stack[-1] != wanted:
error(c, line_number, column_number)
else:
stack.pop()
def check_parentheses(f):
stack = list()
line_number = 0
for line in f:
line_number = line_number + 1
column_number = 0
for c in line:
column_number = column_number + 1
if c == '(' or c == '[' or c == '{':
stack.append(c)
elif c == ')':
check(stack, '(', ')', line_number, column_number)
elif c == ']':
check(stack, '[', ']', line_number, column_number)
elif c == '}':
check(stack, '{', '}', line_number, column_number)
def main():
filename = sys.argv[1]
try:
f = file(filename)
except IOError:
sys.stderr.write('Error: Cannot open file %s' % filename)
sys.exit(1)
check_parentheses(f)
f.close()
main()