将Python脚本转为面向对象

27 投票
4 回答
24153 浏览
提问于 2025-04-15 16:28

我正在用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 个回答

0

你可以创建一个函数,把所有的逻辑都放在里面,这样就能简单处理问题。不过,如果想要完全符合“面向对象”的编程风格,你可以这样做:

顺便说一下,你发的代码在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))
1

当我进行这种特定的代码重构时,通常会从第一个文件的初始转换开始。第一步:把功能移动到一个新类的方法里。第二步:在下面添加这个神奇的调用,让文件再次像脚本一样运行:

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]) 
57

为了让你现有的代码运行得更快,可以在给 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()

撰写回答