Python:用lambda从列表中获取字典键为某值的项
可以用lambda来获取数据吗?我知道我们可以用sorted
函数配合lambda,这个用起来非常方便。
有没有一种简短的方法可以在一个列表中找到一个对象,这个对象的键'id'
等于,比如说20
?
当然,我们可以用循环来遍历整个列表。
x = [
{'Car': 'Honda', 'id': 12},
{'Car': 'Mazda', 'id': 45},
{'Car': 'Toyota', 'id': 20}
]
desired_val = None
for item in list:
if item['id'] == 20:
desired_val = item
break
那么,能不能用lambda
来实现同样的功能呢?我对lambda
不是很了解。
4 个回答
在Python 3中,filter
会返回一个迭代器,这意味着你可以用next
来获取它的第一个值:
val = next(filter(lambda x: x['id'] == 20, list))
如果你在用Python 2,就要使用itertools.ifilter
,因为Python 2自带的filter
会直接生成一个包含所有结果的列表:
from itertools import ifilter
val = next(ifilter(lambda x: x['id'] == 20, list))
你可以考虑给next
传一个默认值,这样在迭代器为空的时候就会返回这个默认值:
In [3]: next(filter(bool, [False]), 'default value here')
Out[3]: 'default value here'
你问的这个用法是用一个lambda表达式,配合生成器表达式,这种写法通常比用filter更容易读懂。而且,这种方式在Python 2和Python 3中都能正常工作。
lambda x: next(i for i in x if i['id'] == 20)
用法示例:
>>> foo = lambda x: next(i for i in x if i['id'] == 20)
>>> foo(x)
{'Car': 'Toyota', 'id': 20}
不过,这种用lambda的方式可能用处不大。我们可以很简单地定义一个函数:
def foo(x):
return next(i for i in x if i['id'] == 20)
而且我们可以给函数添加文档字符串,它知道自己的名字,还有一些匿名函数(我们之后给它命名)没有的有趣属性。
另外,我觉得你想表达的可能是表达式中的filter部分。
在
filter(lambda x: x[id]==20, x)
中,我们用生成器表达式的条件部分替代了这个功能。生成器表达式的功能部分(当用方括号表示时是列表推导式)也在类似地替代map
。
在这里使用 lambda
其实并不是必须的。Lambda 并不是一种神奇的东西,它只是写简单函数的一种简化方式。它的功能比起普通写法来说是更弱的,并不是更强大。(这并不是说它有时候不方便,只是说它没有什么超能力。)
不过,你可以用生成器表达式和默认参数来实现。注意这里我返回的是对象本身,而不是 20,因为这样对我来说更有意义。
>>> somelist = [{"id": 10, "x": 1}, {"id": 20, "y": 2}, {"id": 30, "z": 3}]
>>> desired_val = next((item for item in somelist if item['id'] == 20), None)
>>> print(desired_val)
{'y': 2, 'id': 20}
>>> desired_val = next((item for item in somelist if item['id'] == 21), None)
>>> print(desired_val)
None
我想告诉你,你自己的方法是找到列表中第一个符合条件的项目的最佳方式。
这个方法简单明了,一旦找到想要的目标就会立刻停止循环。
而且,这个方法也是最快的。下面我们来看看几种返回列表中第一个字典,条件是'id'==20
的方式:
from __future__ import print_function
def f1(LoD, idd=20):
# loop until first one is found then break and return the dict found
desired_dict = None
for di in LoD:
if di['id'] == idd:
desired_dict = di
break
return desired_dict
def f2(LoD, idd=20):
# The genexp goes through the entire list, then next() returns either the first or None
return next((di for di in LoD if di['id'] == idd), None)
def f3(LoD, idd=20):
# NOTE: the 'filter' here is ifilter if Python2
return next(filter(lambda di: di['id']==idd, LoD), None)
def f4(LoD, idd=20):
desired_dict=None
i=0
while True:
try:
if LoD[i]['id']==idd:
desired_dict=LoD[i]
break
else:
i+=1
except IndexError:
break
return desired_dict
def f5(LoD, idd=20):
try:
return [d for d in LoD if d['id']==idd][0]
except IndexError:
return None
if __name__ =='__main__':
import timeit
import sys
if sys.version_info.major==2:
from itertools import ifilter as filter
x = [
{'Car': 'Honda', 'id': 12},
{'Car': 'Mazda', 'id': 45},
{'Car': 'Toyota', 'id': 20}
] * 10 # the '* 10' makes a list of 30 dics...
result=[]
for f in (f1, f2, f3, f4, f5):
fn=f.__name__
fs="f(x, idd=20)"
ft=timeit.timeit(fs, setup="from __main__ import x, f", number=1000000)
r=eval(fs)
result.append((ft, fn, r, ))
result.sort(key=lambda t: t[0])
for i, t in enumerate(result):
ft, fn, r = t
if i==0:
fr='{}: {:.4f} secs is fastest\n\tf(x)={}\n========'.format(fn, ft, r)
else:
t1=result[0][0]
dp=(ft-t1)/t1
fr='{}: {:.4f} secs - {} is {:.2%} faster\n\tf(x)={}'.format(fn, ft, result[0][1], dp, r)
print(fr)
如果找到了'id'==20
,会打印:
f1: 0.4324 secs is fastest
f(x)={'Car': 'Toyota', 'id': 20}
========
f4: 0.6963 secs - f1 is 61.03% faster
f(x)={'Car': 'Toyota', 'id': 20}
f3: 0.9077 secs - f1 is 109.92% faster
f(x)={'Car': 'Toyota', 'id': 20}
f2: 0.9840 secs - f1 is 127.56% faster
f(x)={'Car': 'Toyota', 'id': 20}
f5: 2.6065 secs - f1 is 502.77% faster
f(x)={'Car': 'Toyota', 'id': 20}
如果没有找到,则会打印:
f1: 1.6084 secs is fastest
f(x)=None
========
f2: 2.0128 secs - f1 is 25.14% faster
f(x)=None
f5: 2.5494 secs - f1 is 58.50% faster
f(x)=None
f3: 4.4643 secs - f1 is 177.56% faster
f(x)=None
f4: 5.7889 secs - f1 is 259.91% faster
f(x)=None
当然,按照现在的写法,这些函数只会返回列表中第一个'id'==20
的字典。如果你想要所有符合条件的,可以使用列表推导式或者用lambda函数进行过滤。
你可以看到,虽然你最开始写的函数是这样,但如果改成返回一个列表,它的表现依然很不错:
def f1(LoD, idd):
desired_lst = []
for item in LoD:
if item['id'] == idd:
desired_lst.append(item)
return desired_lst
def f2(LoD, idd):
return [d for d in LoD if d['id']==idd]
def f3(LoD, idd):
return list(filter(lambda x: x['id']==idd, LoD) )
用同样的代码来计时,这些函数会打印:
f2: 2.3849 secs is fastest
f(x)=[{'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}]
========
f1: 3.0051 secs - f2 is 26.00% faster
f(x)=[{'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}]
f3: 5.2386 secs - f2 is 119.66% faster
f(x)=[{'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}, {'Car': 'Toyota', 'id': 20}]
在这种情况下,列表推导式的表现更好。