我怎样才能简化这个 Python 代码(书中的作业)?

How can I simplify this Python code (assignment from a book)?

我正在学习 "Python for Everybody" Charles R. Severance 写的书,我对第 7 章的练习 2 有疑问。

任务是检查 mbox-short.txt 文件和 "When you encounter a line that starts with “X-DSPAM-Confidence:” pull apart the line to extract the floating-point number on the line. Count these lines and then compute the total of the spam confidence values from these lines. When you reach the end of the file, print out the average spam confidence."

这是我完成这项任务的方法:

fname = input('Enter the file name: ') 
try:
    fhand = open(fname) 
except:
    print('File cannot be opened:', fname)
    exit()
count = 0
values = list()
for line in fhand:
    if line.startswith('X-DSPAM-Confidence:'): 
        string = line
        count = count + 1
        colpos = string.find(":")
        portion = string[colpos+1:]
        portion = float(portion)
        values.append(portion)   
print('Average spam confidence:', sum(values)/count)

我知道这段代码有效,因为我得到了与书中相同的结果,但是,我认为这段代码可以更简单。我这么认为的原因是因为我在这段代码中使用了一个列表(声明它然后在其中存储值)。然而,"Lists" 是本书的下一个主题,在解决这个任务时,我对列表一无所知,不得不 google 它们。我以这种方式解决了这个任务,因为这是我在 R 语言(我已经非常熟悉)中所做的,我会创建一个向量,我将在其中存储迭代中的值。

所以我的问题是:这段代码可以简化吗?我可以在不使用列表的情况下完成相同的任务吗?如果可以,我该怎么做?

列表理解通常可以替代添加到列表中的 for 循环:

fname = input('Enter the file name: ') 
try:
    fhand = open(fname) 
except:
    print('File cannot be opened:', fname)
    exit()

values = [float(l[l.find(":")+1:]) for l in fhand if l.startswith('X-DSPAM-Confidence:')]

print('Average spam confidence:', sum(values)/len(values))

内部只是您的代码组合,因此可读性可能较差。

编辑:不使用列表,可以用“reduce”来完成:

from functools import reduce
fname = input('Enter the file name: ') 
try:
    fhand = open(fname) 
except:
    print('File cannot be opened:', fname)
    exit()

sum, count = reduce(lambda acc, l: (acc[0] + float(l[l.find(":")+1:]), acc[1]+1) if l.startswith('X-DSPAM-Confidence:') else acc, fhand, (0,0))

print('Average spam confidence:', sum / count)

Reduce 在其他语言中通常被称为“折叠”,它基本上允许您使用“累加器”迭代集合。在这里,我使用累加器迭代集合,累加器是 (sum, count) 的元组。对于每个项目,我们添加总和并增加计数。参见 Reduce documentation

综上所述,“简化”并不一定意味着代码越少越好,所以如果您对这些 shorthand 表示法不满意,我会坚持使用您自己的代码。

您可以在循环之前过滤文件的行,然后您可以将其他变量合并为一个,并使用列表理解获取值。由此,您可以从该列表的长度中得到计数。

interesting_lines = (line.startswith('X-DSPAM-Confidence:') for line in fhand)
values = [float(line[(line.find(":")+1):]) for line in interesting_lines]
count = len(values)

Can I do the same task without using list?

如果输出需要一个平均值,是的,你可以将总和和计数累加为自己的变量,而不需要列表来调用sum(values)反对

请注意,无论如何,open(fname) 都会为您提供一个可迭代的集合,并且您要遍历文件中的 "list of lines"。

我可以将 "values" 对象更改为浮动类型。问题中并不真正需要列表的开销。

values = 0.0

然后在循环中使用

values += portion 

否则,真的没有更简单的方法,因为这个问题有任务,你必须满足所有任务才能解决它。

  1. 打开文件
  2. 检查错误
  3. 遍历行
  4. 找到某些行
  5. 所述行总数
  6. 打印平均值

如果您可以在 3 行代码中完成它,那就太好了,但这并不一定会使后台发生的事情变得更简单。它也可能看起来很难看。