通常我们在列表、字典或集合等容器类型中进行条件筛选时,都是使用遍历+判断的方式来实现。这种实现方式的实现逻辑非常简单,但实现的效率却比较低,代码写起来也比较麻烦。我们来看下面这个例子,筛选出列表d中小于0的数:
d = [-1, 10, -2, 3, 4, 7, -9]
result = []
for num in d:
if num < 0:
result.append(num)
print(result)
# 结果:[-1, -2, -9]
这里,我们首先用了一个for循环,然后又用了一个if判断对每一个数进行判断,最终才得到符合条件的结果。Python一向追求代码简洁、优美、高效,那有没有更加简单的方法可以实现这个需求呢?答案其实有的,我们可以利用更加Pythonic的方式来解决这个问题。
在Python中,我们有一个叫列表推导式的东西,相信很多了解Python的人应该都知道。除了列表推导式,当然还有字典推导式、元组推导式、集合推导式等。推导式的表示法非常简洁,并且Python在其内部已经对算法进行了大量的优化,所以推导式运行起来比单纯的循环要快很多,并且语法也更加优雅。那么,接下来我们可以尝试使用列表推导式来求实现上面的那个效果。代码如下:
d = [-1, 10, -2, 3, 4, 7, -9]
result = [x for x in d if x < 0]
print(result)
# 结果:[-1, -2, -9]
核心的代码缩短到了只有一行就搞定了,效率非常高。至于运行效率,为了看到效果,我们可以使用一个稍微大一点的列表做个试验,比如利用random生成1000000个-10到10之间的随机数,代码如下:
import random
import time
d = [random.randint(-10, 10) for _ in range(1000000)]
result = []
start = time.time()
for num in d:
if num < 0:
result.append(num)
print('运行时间为:', time.time()-start)
# 运行时间约为0.15秒左右
如果用列表推导式来实现,代码如下:
import random
import time
d = [random.randint(-10, 10) for _ in range(1000000)]
start = time.time()
result = [x for x in d if x < 0]
print('运行时间为:', time.time()-start)
# 运行时间约为0.05秒左右
可见,列表推导式无论从编写代码的效率还是运行效率上,都更胜一筹。
除了列表推导式,还有字典推导式也是同样的道理。我们也来看看如何通过字典推导式来实现字典中特定元素的过滤。假如我们现在有一个学生成绩的字典,里面有20个学生的成绩信息,要求用字典推导式筛选出所有分数在90分以上的学生,我们的代码就可以这样写:
import random
d = {'student{}'.format(i): random.randint(60, 100) for i in range(1, 21)}
result = {k: v for k, v in d.items() if v > 90}
print(result)
除了推导式之外,Python内置的filter函数也是专门用来筛选容器类型的数据结构里的元素的。通常我们在使用filter函数的时候,需要传两个参数,一个用于筛选元素的函数指针,一般我们会定义一个lambda匿名函数。另一个参数是被筛选的容器对象,比如一个字典或一个列表。另外还要注意一点就是,在Python3中,filter函数返回的是一个生成器对象,如果想要得到一个列表或者是字典,必须做一次强制转换。
接下来,我们以列表为例,用filter来实现第一个列表推导式筛选元素的例子,代码就应该这样来写:
d = [random.randint(-10, 10) for _ in range(20)]
result = list(filter(lambda x: x < 0, d))
print(result)
上面的代码运行结果跟前面使用列表推导式是一模一样的,但filter的运行效率没有列表推导式高。另外,再次强调一下,filter中提供的第一个参数是一个函数指针,我们一般会直接定义一个lambda表达式。对于这个lambda表达式有两点必须注意,一是它必须返回一个布尔值作为结果,凡是结果为True的,就是要留下的,结果为False的就是要被筛掉的。二是这里的lambda表达式只接受一个参数(lambda本身是可以接收多个参数的,但这里只能接收一个)。那么这个参数是怎么来的呢,是谁提供的呢?答案是这个参数将由filter函数的第二个参数,也就是那个容器对象自动提供,所以每次比较的值就是容器对象内的一个元素,以实现依次比较的目的。
明白了这些问题之后,要实现利用filter函数来实现字典元素的筛选,也是很容易的一件事了,代码如下:
import random
d = {'student{}'.format(i): random.randint(60, 100) for i in range(1, 21)}
result = dict(filter(lambda item: item[1] > 90, d.items()))
print(result)
上面的代码中,lambda里面的item为什么要取item[1]呢?因为前面我们说过,lambda只接收一个参数,所以字典中的元素的键和值是以一个整体的形式来传入的。而我们要比较的是值,所以就要通过item[1]取出字典元素的值来进行运算。
综上,在进行列表、字典或集合等容器类型数据结构的简单元素筛选操作时,我们完全不需要呆板地利用循环遍历+判断的方式来写代码实现,毕竟那一点都不Pythonic。Python一向是优雅、高效、简洁的代名词,所以多了解一些更简洁的Pythonic式的用法是很有必要的。写Python是不担心脱发问题的,但前提得是你的代码要足够优雅和简洁,否则,老是想着用Java的方式来解决Python的问题,那神仙都帮不了你了。
欢迎关注我的公众号,学习更多优质内容!