关于OOP:使Python脚本面向对象

Making a Python script Object-Oriented

我正在用python编写一个应用程序,它将有很多不同的函数,所以从逻辑上讲,我认为最好将脚本拆分为不同的模块。当前,我的脚本在一个文本文件中读取,该文件包含已转换为标记和拼写的代码。然后,脚本将代码重新构造为一个字符串,其中的注释将出现在原始代码中。

不过,我在编写面向对象的脚本时遇到了问题。无论我尝试什么,我似乎都不能让程序像只运行一个脚本文件那样运行。理想情况下,我想要两个脚本文件,一个包含类和函数,用于清理和重建文件。第二个脚本只需从另一个文件中的类调用函数,该文件作为命令行中的参数提供。这是我当前的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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 += '
'

        if diff == 1:
            cleanedInput += '

'

        else:
            cleanedInput += '
'
* diff

        prevLine = lineNo
        continue

    cleanedLine = line.split(':', 1)[1].strip()
    cleanedInput += cleanedLine + ' '

print cleanedInput

在遵循下面的alex martelli建议之后,我现在有了下面的代码,它提供了与原始代码相同的输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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('
'
)
            if diff == 1:
                cleanedInput.append('

'
)
            else:
                cleanedInput.append('
'
* diff)

            prevLine = lineNo
            continue

        cleanedLine = line.split(':', 1)[1].strip()
        cleanedInput.append(cleanedLine + ' ')

    print cleanedInput

if __name__ == '__main__':
    main()

不过,我还是想将代码拆分为多个模块。我的程序中的"已清理文件"将对其执行其他功能,因此,自然地,已清理文件本身应该是类?


为了有效地加速现有代码,在分配给tokenList之前添加def main():,在这4个空格之后缩进所有内容,最后放上惯用的习惯用法。

1
2
if __name__ == '__main__':
  main()

(保护实际上不是必需的,但是这是一个好习惯,因为对于具有可重用函数的脚本,它使它们可以从其他模块导入)。

这与"面向对象"没有什么关系:在Python中,将所有重要的代码保存在函数中,而不是作为顶级模块代码,这样做只会更快。

第二个加速,把cleanedInput改成一个列表,即它的第一个任务应该是= [],而现在有+=的地方,使用.append。最后,''.join(cleanedInput)得到最终得到的字符串。这使得您的代码需要线性时间作为输入大小的函数(O(N)是表示这个的正常方式),而目前它需要二次时间(O(N squared))。

那么,正确性:在continue之后的两个语句永远不会执行。你需要还是不需要?如果不需要,删除它们(和continue,如果实际需要这两个语句,则删除continue。如果不执行之前的if,那么从if diff开始的测试将大大失败,因为diff那时将不确定。您发布的代码是否可能有缩进错误,即,您发布的代码的缩进是否与实际代码的缩进不同?

考虑到这些重要的需要增强的功能,以及很难看出您在使这个小代码OO(和/或模块化)方面所追求的优势,我建议澄清缩进/正确性情况,应用我提出的增强功能,并将其保留在这个位置;-)。

编辑:由于OP现在已经应用了我的大部分建议,所以让我用一种合理的方法将大部分功能划分到单独模块中的一个类中。在新文件中,例如foobar.py,与原始脚本位于同一目录中(或在site-packages中,或在sys.path上的其他位置),放置此代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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('
'
* (diff if diff>1 else diff+1))
            prevLine = lineNo
        else:
            cleanedLine = token_of(line)
            cleanedInput.append(cleanedLine + ' ')

    return cleanedInput

然后,您的主脚本变为:

1
2
3
4
5
6
7
8
9
import sys
import foobar

def main():
    thefile = foobar.FileParser(sys.argv[1])
    print thefile.cleaned_input()

if __name__ == '__main__':
  main()


当我进行这种特殊的重构时,我通常从第一个文件中的初始转换开始。步骤1:将功能移到新类中的方法中。步骤2:添加下面的魔术调用使文件像脚本一样运行:

1
2
3
4
5
6
7
8
9
10
11
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])


如果呈现的代码都是代码,就不要添加任何类!!

你的代码太简单了!!OOP方法会增加不必要的复杂性。

但如果仍然不行。把所有代码都放到函数中。

1
2
3
4
5
def parse_tokenized_input(file):
    tokenList = open(file, 'r')
    cleanedInput = ''
    prevLine = 0
    #rest of code

在结束添加:

1
2
if __name__ == '__main__':
    parse_tokenized_input(sys.argv[1])

如果代码工作正常,请将函数的def放到新文件中(以及所有需要的导入!)MyMODYLY.PY

现在您的脚本将是:

1
2
3
4
from mymodule.py import parse_tokenized_input

if __name__ == '__main__':
        parse_tokenized_input(sys.argv[1])

哦,为您的函数和模块想出更好的名称(模块应该有通用名称)。


您可以通过创建一个函数并将您的所有逻辑都放在它里面来逃脱惩罚。但是,对于完全的"对象定向",您可以这样做:

ps-您发布的代码在continue行上有一个bug-它总是被执行,最后两行永远不会执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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))