有没有更好的、pythonic的做法?

10 投票
8 回答
698 浏览
提问于 2025-04-15 15:13

这是我写的第一个Python程序 -

需求:读取一个文件,每一行包含{adId UserId}。对于每个adId,打印出不同的userId数量。

这是我的代码,是我从Python文档中整理出来的。你能给我一些建议,让我可以用更符合Python风格的方式来写吗?

代码:

import csv

adDict = {}
reader = csv.reader(open("some.csv"), delimiter=' ')
for row in reader:
    adId = row[0]
    userId = row[1]
    if ( adId in adDict ):
        adDict[adId].add(userId)
    else:
        adDict[adId] = set(userId)

for key, value in adDict.items():
    print (key, ',' , len(value))

谢谢。

8 个回答

7

你可以把这个for循环简化成这样:

for row in reader:
  adDict.setdefault(row[0], set()).add(row[1])
18

恭喜你,你的代码写得很好!不过有一些小技巧可以让它变得更简洁。

有一个很方便的对象类型叫做 defaultdict,它是 collections 模块提供的。使用这个 defaultdict,你就不需要每次都检查 adDict 里有没有 adId 这个键了。它的工作方式跟普通的字典差不多,但如果你查找的键不存在,它会自动给你一个空的集合 set()。所以你可以把

if ( adId in adDict ):
    adDict[adId].add(userId)
else:
    adDict[adId] = set(userId)

改成简单的

adDict[adId].add(userId)

另外,

for row in reader:
    adId = row[0]
    userId = row[1]

你可以把它简化为

for adId,userId in reader:

编辑:正如 Parker 在评论中友好地指出的,

for key, value in adDict.iteritems():

是遍历字典时最有效的方法,特别是当你需要在循环中同时使用键和值的时候。在 Python3 中,你可以使用

for key, value in adDict.items():

因为 items() 会返回一个迭代器。

#!/usr/bin/env python
import csv
from collections import defaultdict

adDict = defaultdict(set)
reader = csv.reader(open("some.csv"), delimiter=' ')
for adId,userId in reader:
    adDict[adId].add(userId)
for key,value in adDict.iteritems():
    print (key, ',' , len(value))
10

这段代码:

adDict[adId] = set(userId)

可能不会达到你想要的效果——它会把字符串 userId 当作一串字母来处理。举个例子,如果 userIdaleax,那么你会得到一个包含四个元素的集合,就像 set(['a', 'l', 'e', 'x'])。之后,如果你再用 userId 作为 aleax 调用 .add(userId),它会再添加一个第五个元素,也就是字符串 'aleax'。这是因为 .add 方法和集合的初始化方式不同,它只接受一个单独的元素。

如果你想创建一个只包含一个元素的集合,应该用 set([userId])

这个错误其实挺常见的,所以我想清楚地解释一下。说到这里,其他回答中提到的 defaultdict 显然是个正确的选择(尽量避免使用 setdefault,这个设计从来就不好,而且性能也不佳,使用起来也比较模糊)。

我还建议不要使用有点复杂的 csv,而是简单地用一个循环,对每一行使用 .split.strip

撰写回答