【问题标题】:Making a Python script Object-Oriented使 Python 脚本面向对象
【发布时间】:2009-11-28 17:19:36
【问题描述】:

我正在用 Python 编写一个应用程序,它将具有许多不同的功能,所以从逻辑上讲,我认为最好将我的脚本分成不同的模块。目前,我的脚本读入一个文本文件,其中包含已转换为标记和拼写的代码。然后,该脚本将代码重构为一个字符串,在原始代码中 cmets 的位置出现空白行。

我在使脚本面向对象时遇到了问题。无论我尝试什么,我似乎都无法让程序以与它只是一个脚本文件相同的方式运行。理想情况下,我想要两个脚本文件,一个包含清理和重建文件的类和函数。第二个脚本将简单地从另一个文件中的类调用该函数,该文件作为命令行参数给出。这是我当前的脚本:

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()

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

【问题讨论】:

  • 假设你有你想要的对象。你会如何使用它?换句话说,你想要什么语法?
  • 在这种情况下,我会认为输入文件是对象。清洁过程是我希望能够对所述对象执行的功能。考虑到这一点,我很想在我的输入文件类中使用 for 循环并使其成为清理功能。

标签: python oop


【解决方案1】:

要显着加快现有代码的速度,请将 def main(): 添加到 tokenList 之前,缩进这 4 个空格之后的所有内容,最后放入常用的成语

if __name__ == '__main__':
  main()

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

这与“面向对象”几乎没有任何关系:在 Python 中,将所有重要代码保存在函数中,作为顶级模块代码会更快。

第二次加速,将cleanedInput 更改为一个列表,即它的第一个分配应该是= [],而无论您现在有+=,请改用.append。最后,''.join(cleanedInput) 得到最终的结果字符串。这使您的代码需要线性时间作为输入大小的函数(O(N) 是表达这一点的正常方式),而当前需要二次时间(O(N squared))。

那么,正确性:continue 之后的两条语句永远不会执行。你是否需要它们?如果不需要,请删除它们(和continue),如果确实需要这两个语句,请删除continue。并且以if diff 开头的测试将显着失败,除非之前的if 被执行,因为diff 将是未定义的。您发布的代码是否可能存在缩进错误,即您发布的内容的缩进与实际代码的缩进是否不同?

考虑到这些重要的必要增强功能,以及很难看出您在制作这种小型代码 OO(和/或模块化)方面所追求的优势这一事实,我建议澄清缩进/正确性情况,应用我已经做过的增强功能提出来,然后就这样了;-)。

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

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()

【讨论】:

  • 稍微扩展一点 Alex 的观点:在 Python 中,字符串是不可变的。 s += 'abc' 制作了 s 的全新副本,并附加了 abc。因此,如果您正在构建一个带有连接的长字符串,那么您就是在一遍又一遍地复制一个不断增长的字符串。将项目附加到列表(或使用 StringIO 模块)然后在最后执行单个 join 可以避免这种情况。
  • 感谢那些 cmets 亚历克斯。是的,当我粘贴它时出现缩进错误。我现在已经修复了它以反映我在 IDE 中看到的内容。我希望这段代码模块化的原因是因为程序最终会成为一个递归下降解析器,所以为了简单起见,我想把它们分开。
  • @greenie,感谢您修复缩进,但我指出的问题(代码不在函数中,字符串 +=)仍然存在,还有其他问题,例如复杂拆分的完全重复- and-strip 表达式,其优先级 更高,以提高此代码的质量,而不是将几行代码归为方法而不是函数。首先要做的事!-)
  • 我已经编辑了我的原始问题,以反映您建议亚历克斯的更改。希望现在应该可以从中创建一个类,这样我就可以将“清理过的文件”表示为一个对象,以后可以执行其他功能。
  • 代码不应该在某处调用FileParser.tokenList.close()吗?
【解决方案2】:

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

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

【讨论】:

  • 我调整了你的缩进。但是,clean()arg 仍未定义。
  • 抱歉,在我准备好之前点击了提交按钮。另外,我只是想出了代码示例widgit。 叹息
  • 哈,两个强迫症想同时清理同一个帖子。这就像一个疯狂的 O. Henry 故事。
  • “贤士的礼物”,也许吧。 literaturecollection.com/a/o_henry/25
【解决方案3】:

您可以创建一个函数并将所有逻辑放入其中。但是,对于完整的“面向对象”,您可以执行以下操作:

ps - 您发布的代码在 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))

【讨论】:

  • 我的错误。粘贴时我忘记缩进 continue 和上面的所有内容以将其保留在第一个 if 语句中。
【解决方案4】:

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

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

但如果仍然不会。 将所有代码放入函数中,例如。

def parse_tokenized_input(file):
    tokenList = open(file, 'r')
    cleanedInput = ''
    prevLine = 0
    #rest of code

最后添加:

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

如果代码正确,则将函数的 def 放入新文件(以及所有需要的导入!) 例如。 mymodyle.py

您的脚本现在将是:

from mymodule.py import parse_tokenized_input

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

哦,为你的函数和模块想一个更好的名字(模块应该有通用名称)。

【讨论】:

    猜你喜欢
    • 2016-06-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-12
    • 2011-08-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多