当初能快速上手Python最重要的一点是其内置的数据结构非常强大,关于数据类型的差别(例如可修改对象与不可修改对象)作为基础的知识点在其他教程中已经有非常详细的介绍,所以这里就不再赘述了。这次主要分享一些(也许算是换一种思路的)能提升开发效率的使用心得。
从列表、字典、集合筛选数据
我们都知道列表生成式是一种很Pythonic的用法:
[x for x in data if x>=0]
仅一行代码就能从可迭代对象data中筛选出满足大于等于0条件的元素。
它的迭代器版本为:
(x for x in data if x>=0)
区别在于返回的是一个满足筛选条件的迭代器,当遍历时元素在当前循环中才产生所以能大量节省内存,但定义后只能使用一次。
其实字典和集合也可以使用推导式:
{k: v for k, v in data.iteritems() if v>=0 } # iteritems是items的迭代器版本
{x for x in s if x>=0}
返回的是经过筛选的字典/集合,比使用for循环要简单直接得多。
- 尽可能使用生成式,效率比较高;
- 复杂的场景生成式可能会影响可读性,不建议使用。
为元组元素命名
有C/C++开发经验的朋友常常会用到:
// 宏定义
#define NAME 0
#define AGE 1
// 枚举类型
enum Student{NAME, AGE}
我想说的是这里用到一种为元素命名的方法,直接通过名称能在顺序结构中以更快的速度访问元素(而不需要遍历整个对象)。
在Python中也可以通过名称(而不是下标)的方法访问列表/元组元素:
例如可以自己定义“枚举类型”:
NAME, AGE, SEX, EMAIL = xrange(4)
student = ("Jim", 16, "male", "[email protected]")
print(student[NAME], student[AGE], student[SEX], student[EMAIL],)
另外也可以使用collections模块的namedtuple:
from collections import namedtuple
# 先定义一个名为Student的namedtuple
Student = namedtuple("Student", ["name", "age", "sex", "email"])
# 然后就可以使用自定义的Student类型创建namedtuple对象啦
s1 = Student(
name="Jim",
age=16,
sex="male",
email="[email protected]"
)
print(s1.name, s1.age, s1.sex, s1.email)
以上两种方法都可以实现通过名称访问元组/列表的元素哦~
统计序列中元素的出现频度
有时我们需要统计列表/元组中某些元素出现的次数。
其中一种方法是使用字典来统计:
# 生成范围在1 ~9的元素共100个,并存放在字典中
li = [random.randrange(1, 10) for i in range(100)]
di = dict.fromkeys(li, 0)
for i in li:
di[i] += 1
这个时候更方便的方法是使用collections的Counter类型:
from collections import Counter
c = Counter(li)
这时候返回的是一个计数器对象,可以使用dict直接转换为以元素为key、次数为value的字典。
而且还可以直接取出出现次数排名前三的元素,并组成新的字典:
c.most_common(3)
把字典根据值的大小排序
先复习一下字典的特性,字典与列表的对比:
字典 | 列表 |
---|---|
查找和插入速度快,不会随着键的增加而变慢 | 查找和插入的时间随着元素增加而增加 |
占用大量内存,浪费较多 | 占用内存少,浪费较多 |
通过键(哈希)查询,键不可改变(所以不能用列表等可变类型作为键) | 通过下标查询 |
默认无序(可以人工排序) | 有序 |
实际上字典也是可以排序、达到按序查找的效果:
对于一个随机的字典:
from random import randint
d = {x: randint(60, 100) for x in 'abcxyz'}
排序的方法是转化为一个元组列表,再对元组的项排序:
keys = d.iterkeys()
values = d.itervalues()
# 分别取字典的键和值组成元组(Python3中不支持,只能用keys()、values())
tu = zip(keys, values)
new_di = dict(sorted(tu))
更好的方法是利用sorted函数的第二个参数指定参与排序的项:
dict(sorted(d.items(), key=lambda x: x[1]))
找到多个字典中的公共键
假设我们有多个字典,需要找到这些字典中都存在的key:
di_a = {"a": 1, "b": 2, "c": 3}
di_b = {"a": 5, "d": 2, "e": 3}
di_c = {"a": -5, "f": 5, "g": 0}
可见公共键就是"a",但在比较大的字典中要迅速找到这个公共键,常见的方法是构造循环遍历di_a,并在循环中判断di_a的键是否同时存在于di_b和di_c......
有一种更简便的方法:
di_a.viewkeys() & di_b.viewkeys() & di_c.viewkeys()
di_a.viewkeys()返回的是一个view对象,可以直接转化为存放字典di_a的列表,最重要的优点是view对象是支持集合运算的,上面这个语句返回的是同时存在于di_a、di_b、di_c中的元素(交集)的集合,即三个字典的公共键集合:
set(['a'])
让字典保持有序
我们使用字典有时会碰上一种情景,要求让字典一直保持有序(例如比赛中统计名次和成绩),这时候使用collections的OrderDict类型就非常方便了,以下是一个模拟比赛的程序:
from random import randint
from time import time
from collections import OrderedDict
d = OrderedDict() # 存放选手成绩的有序字典
players = list('ABCDEFG') # 模拟七个选手
start = time()
for i in xrange(8):
input() # 阻塞:每次输入回车随机弹出一个选手表示该选手完成比赛
p = players.pop(randint(0, 7-i))
end = time()
print(i+1, p, end-start) # 输出名次、选手名、用时
d[p] = (i+1, end-start) # 把选手成绩计入有序字典
print()
for k in d:
print(k, d[k]) # 根据进入字典的顺序打印元素
实现用户的历史记录功能(最多n条)
很多时候我们需要让程序实现自动记录用户的历史数据和操作,达到缓存的效果(例如浏览器的搜索记录等)。
Python的collections模块为我们提供了双向队列,可以很方便的实现这个功能,以下是一个猜数游戏(用户每次输入数字,返回答案的取值范围,随着输入次数增多范围缩小,当用户输入与答案相同的数字时游戏结束):
from random import randint
N = randint(0, 100) # 返回0~100范围内的数字
def guess(k):
if k == N:
print("Right!")
return True
elif k < N:
print("{} < N...".format(k))
else:
print("{} > N...".format(k))
while 1:
num = input("Please input a number:")
if num:
k = int(num)
if guess(k):
break
但很多时候随着输入的次数增多,就很容易忘记之前已经输入过的数字,这时候可以引入一个存放历史记录的队列:
from collections import deque
from pickle import dump, load # 需要时可用pickle写入文件
history = deque([], 5) # 空队列,长度为5
# ...
while 1:
num = input("Please input a number:")
if num:
k = int(num)
history.append(k)
# 当用户输入?或history时返回历史输入提示
if num in ['history', '?']:
print(list(history))
if guess(k):
break