将Python脚本转为面向对象
我正在用Python写一个应用程序,这个程序会有很多不同的功能,所以我觉得把我的代码分成不同的模块会比较好。目前,我的脚本会读取一个文本文件,这个文件里包含了一些被转换成标记和拼写的代码。然后,脚本会把这些代码重构成一个字符串,原本代码中的注释位置会留空行。
不过,我在把这个脚本做成面向对象的时候遇到了一些问题。无论我怎么尝试,程序都无法像一个单独的脚本文件那样运行。理想情况下,我想要有两个脚本文件,一个文件里包含一个类和一个函数,用来清理和重构文件。第二个脚本则是从命令行接收一个文件名,然后调用第一个文件中的类里的函数。
import sys
tokenList = open(sys.argv[1], 'r')
cleanedInput = ''
prevLine = 0
for line in tokenList:
if line.startswith('LINE:'):
lineNo = int(line.split(':', 1)[1].strip())
diff = lineNo - prevLine - 1
if diff == 0:
cleanedInput += '\n'
if diff == 1:
cleanedInput += '\n\n'
else:
cleanedInput += '\n' * diff
prevLine = lineNo
continue
cleanedLine = line.split(':', 1)[1].strip()
cleanedInput += cleanedLine + ' '
print cleanedInput
在按照Alex Martelli的建议修改后,我现在的代码输出和我最初的代码是一样的。
def main():
tokenList = open(sys.argv[1], 'r')
cleanedInput = []
prevLine = 0
for line in tokenList:
if line.startswith('LINE:'):
lineNo = int(line.split(':', 1)[1].strip())
diff = lineNo - prevLine - 1
if diff == 0:
cleanedInput.append('\n')
if diff == 1:
cleanedInput.append('\n\n')
else:
cleanedInput.append('\n' * diff)
prevLine = lineNo
continue
cleanedLine = line.split(':', 1)[1].strip()
cleanedInput.append(cleanedLine + ' ')
print cleanedInput
if __name__ == '__main__':
main()
不过,我还是想把我的代码分成多个模块。在我的程序中,"清理后的文件"会进行其他功能的处理,所以自然这个清理后的文件应该是一个独立的类,对吧?
4 个回答
你可以创建一个函数,把所有的逻辑都放在里面,这样就能简单处理问题。不过,如果想要完全符合“面向对象”的编程风格,你可以这样做:
顺便说一下,你发的代码在continue
那一行有个错误——它总是会被执行,导致后面的两行代码永远不会执行。
class Cleaner:
def __init__(...):
...init logic...
def Clean(self):
for line in open(self.tokenList):
...cleaning logic...
return cleanedInput
def main(argv):
cleaner = Cleaner(argv[1])
print cleaner.Clean()
return 0
if '__main__' == __name__:
sys.exit(main(sys.argv))
当我进行这种特定的代码重构时,通常会从第一个文件的初始转换开始。第一步:把功能移动到一个新类的方法里。第二步:在下面添加这个神奇的调用,让文件再次像脚本一样运行:
class LineCleaner:
def cleanFile(filename):
cleanInput = ""
prevLine = 0
for line in open(filename,'r'):
<... as in original script ..>
if __name__ == '__main__':
cleaner = LineCleaner()
cleaner.cleanFile(sys.argv[1])
为了让你现有的代码运行得更快,可以在给 tokenList
赋值之前加上 def main():
,然后把后面的内容缩进4个空格,最后加上常用的结束语。
if __name__ == '__main__':
main()
虽然这个保护措施并不是必须的,但养成这个习惯还是不错的,尤其是对于那些有可重用函数的脚本,这样可以让它们从其他模块中导入。
这和“面向对象”没有太大关系:在Python中,把主要的代码放在函数里,而不是放在顶层模块中,运行会更快。
第二个加速方法是把 cleanedInput
改成一个列表,也就是说,它的第一次赋值应该是 = []
,然后把所有的 +=
替换成 .append
。最后,用 ''.join(cleanedInput)
来得到最终的字符串。这样你的代码处理输入的时间就会是线性的(用 O(N)
来表示),而现在是平方级别的时间(用 O(N squared)
表示)。
接下来是正确性的问题:在 continue
后面的两个语句是永远不会执行的。你需要它们吗?如果不需要,就把它们(和 continue
)删掉;如果需要这两个语句,就删掉 continue
。而且,开始于 if diff
的测试会失败,除非前面的 if
被执行过,因为那时 diff
是未定义的。你发布的代码是否有缩进错误?也就是说,你发布的代码缩进和你实际代码的缩进是否不同?
考虑到这些重要的改进建议,以及很难看出你为什么要把这段小代码做成面向对象(或模块化),我建议你先理清缩进和正确性的问题,应用我提到的改进,然后就这样吧;-)。
编辑:由于提问者现在已经应用了我大部分的建议,我想跟进一个合理的方法,把大部分功能放到一个单独模块的类中。在一个新文件中,比如 foobar.py
,放在和原始脚本同一个目录下(或者在 site-packages
,或者在 sys.path
的其他地方),放入以下代码:
def token_of(line):
return line.partition(':')[-1].strip()
class FileParser(object):
def __init__(self, filename):
self.tokenList = open(filename, 'r')
def cleaned_input(self):
cleanedInput = []
prevLine = 0
for line in self.tokenList:
if line.startswith('LINE:'):
lineNo = int(token_of(line))
diff = lineNo - prevLine - 1
cleanedInput.append('\n' * (diff if diff>1 else diff+1))
prevLine = lineNo
else:
cleanedLine = token_of(line)
cleanedInput.append(cleanedLine + ' ')
return cleanedInput
然后你的主脚本就变成了:
import sys
import foobar
def main():
thefile = foobar.FileParser(sys.argv[1])
print thefile.cleaned_input()
if __name__ == '__main__':
main()