如何在Python中过滤一个字典? (...最Pythonic的方法)

2,526 阅读8分钟

问题:给定一个字典和一个过滤条件。如何通过......来过滤一个字典。

  • ...... ,以便只保留字典中那些(key, value) 对,其中 满足条件?
  • ...... ,以便只保留那些(key, value) 对,其中的 满足条件?

在本教程中,你将学习四种方法,以及它们如何相互比较。它松散地基于教程,但通过许多额外的例子和代码解释对其进行了扩展。

如果你太忙,没时间读本教程,这里有一个破坏者。

方法 4 - 字典理解{k:v for (k,v) in dict.items() if condition} 是在 Python 中过滤字典的最 Pythonic 和最快的方法。

然而,通过阅读这个短短的8分钟的教程,你将会学到很多关于编写Pythonic代码的细微差别。所以请继续阅读!

方法1:简单的迭代来过滤字典

你应该总是从最简单的方法开始解决一个问题(见奥卡姆剃刀),因为过早的优化是万恶之源!所以让我们来看看这个方法。

所以我们来看看过滤字典的直接循环迭代方法。

你从下面这个名字的字典开始。

names = {1: 'Alice',
         2: 'Bob',
         3: 'Carl',
         4: 'Ann',
         5: 'Liz'}

按键过滤 Python 字典 (简单循环)

你想保留那些(key, value) 对,其中 key 满足某个条件 (比如key%2 == 1)。

newDict = dict()

# Iterate over all (k,v) pairs in names
for key, value in names.items():

    # Is condition satisfied?
    if key%2 == 1:
        newDict[key] = value

让我们看一下输出结果。

print(newDict)
# {1: 'Alice', 3: 'Carl', 5: 'Liz'}

只有(key, value) 对,其中key 是一个奇数整数,保留在过滤后的字典newDict 中。

但是如果你想通过对值的条件来过滤字典呢?

通过值过滤 Python 字典 (简单循环)

要按值过滤,你只需要替换前面代码片断中的一行:不写 ifkey%2 == 1: ... ,而是用value 来决定是否添加某个(key, value) 对:if len(value)<5: ...

newDict = dict()

# Iterate over all (k,v) pairs in names
for key, value in names.items():

    # Is condition satisfied?
    if len(value)<5:
        newDict[key] = value

print(newDict)
# {2: 'Bob', 3: 'Carl', 4: 'Ann', 5: 'Liz'}

在过滤后的字典newDict ,只有名字字符串值的长度小于5个字符的字典(key, value) 对保留下来。

在我们的交互式Cod Shell中自己尝试一下(点击 "运行")。

现在,你知道了在 Python 中过滤字典的基本方法 (按键和按值)。

但是我们可以做得更好吗?如果你需要通过许多不同的过滤条件来过滤许多字典呢?我们必须一次又一次地重写同样的代码吗?

所有这些问题的答案都是否定的!

继续阅读,学习一种通用的方法,使过滤一个字典像调用一个函数一样容易,该函数传递字典和 [filter()](https://blog.finxter.com/python-filter/)函数。

方法2:用通用函数来过滤字典

如何在不同的字典上使用不同的过滤函数,而不重复写同样的代码呢?答案很简单:创建你自己的通用过滤函数!

你的目标是创建一个函数filter_dict(dictionary, filter_func) ,它接收一个要过滤的dictionary 和一个filter_func ,以确定每个(key, value) 对是否应该包括在过滤的字典中。

你从下面这个名字的字典开始。

names = {1: 'Alice',
         2: 'Bob',
         3: 'Carl',
         4: 'Ann',
         5: 'Liz'}

让我们创建一个通用的过滤函数吧!

def filter_dict(d, f):
    ''' Filters dictionary d by function f. '''
    newDict = dict()

    # Iterate over all (k,v) pairs in names
    for key, value in d.items():

        # Is condition satisfied?
        if f(key, value):
            newDict[key] = value

    return newDict

这个函数需要两个参数:要过滤的字典d 和决定一个元素是否应该包括在新字典中的函数f

你创建一个空的 dictionary newDict 并决定原始 dictionary d 的所有元素是否应该被包括在内。

为了达到这个目的,你遍历每个原始的(key, value) 对,并把它传递给函数f: key, value --> Boolean

该函数f 返回一个布尔值。如果它的值是True ,这个(key, value) 对就被添加到新的字典中。否则,它被跳过。返回值是新创建的 dictionarynewDict

这里是如何使用过滤函数按键过滤的。

使用通用函数按键过滤 Python 字典

如果你想完成和上面一样的事情--按键过滤,只包括奇数的键--你只需使用下面的单行调用。

print(filter_dict(names, lambda k,v: k%2 == 1))
# {1: 'Alice', 3: 'Carl', 5: 'Liz'}

这很容易!

你传递的lambda 函数返回k%2 == 1 ,这是与字典中每个原始元素相关的布尔过滤值names

同样,如果你想通过键来过滤,只包括偶数键,请做如下操作。

print(filter_dict(names, lambda k,v: k%2 == 0))
# {2: 'Bob', 4: 'Ann'}

事实上,使用这个完全相同的策略,按值过滤也同样方便。

使用通用函数按值过滤 Python 字典

下面是如何使用我们的函数filter_dict 来按值过滤。

print(filter_dict(names, lambda k,v: len(v)<5))
# {2: 'Bob', 3: 'Carl', 4: 'Ann', 5: 'Liz'}

在前面的代码片断中,你对字典进行过滤,以便只保留那些(key, value) 对,其中value 少于五个字符。

print(filter_dict(names, lambda k,v: v.startswith('A')))
# {1: 'Alice', 4: 'Ann'}

在这个代码片断中,你过滤了字典,只留下那些(key, value) ,其中value 以字符'A' 开始。

在我们的交互式Cod Shell中自己尝试一下(点击 "运行")。

但这是最Pythonic的方法吗?几乎不是!

方法3:字典上的 filter() 函数

filter(function, iterable) 函数将一个函数作为输入,该函数接受一个参数(一个可迭代的元素),并返回一个布尔值,该元素是否应该通过过滤器。所有通过过滤器的元素将作为一个新的iterable 对象(过滤器对象)返回。

你可以使用lambda 函数语句,在你传递参数的地方直接创建函数。

lambda函数的语法是lambda x: expression

这意味着你使用x 作为输入参数,并返回expression 作为结果(可以或不可以使用x 来决定返回值)。

使用 filter() + Lambda 函数按键过滤 Python 字典

这里是你如何只用一行代码就能按键过滤一个字典 (不用像方法 2 那样定义你的自定义过滤函数)。

# FILTER BY KEY
print(dict(filter(lambda x: x[0]%2 == 1, names.items())))
# {1: 'Alice', 3: 'Carl', 5: 'Liz'}

你可能认识到同样的过滤λ函数lambda x: x[0]%2 == 1 ,如果键是一个奇数的整数,则返回True

💡 注意:这个lambda函数只接受一个输入,因为 函数就是这样工作的。它要求你传递一个函数对象,该对象接受一个参数并将其映射为一个布尔值。filter()

你在 iterable 上操作 [names.items()](https://blog.finxter.com/python-dict-items-method/)上进行操作,它提供了所有(key, value) 对 (iterable的一个元素是一个(key, value) 元组)。

在过滤之后,你用初始化函数将过滤器对象转换回一个字典。 [dict(...)](https://blog.finxter.com/python-dict/)初始化函数。

另一个例子

print(dict(filter(lambda x: x[0]%2 == 0, names.items())))
# {2: 'Bob', 4: 'Ann'}

让我们看看这对按值过滤是如何工作的。

使用 filter() + Lambda 函数按值过滤 Python Dictionary

你可以使用同样的基本思想,使用filter() +lambda +dict() ,来按值过滤一个字典。

例如,如果你想过滤掉所有 (key, value) 对,其中值少于五个字符,使用下面的单行代码

print(dict(filter(lambda x: len(x[1])<5, names.items())))
# {2: 'Bob', 3: 'Carl', 4: 'Ann', 5: 'Liz'}

还有第二个例子

print(dict(filter(lambda x: x[1].startswith('A'), names.items())))
# {1: 'Alice', 4: 'Ann'}

在我们的交互式 Cod Shell 中自己试试(点击 "运行")。

所以,到目前为止还不错。但我们还能做得更好吗?我是说,单行代码就是单行代码,对吗?我们怎么可能在此基础上改进呢?

方法4:通过字典的理解力来过滤

在 Python 中过滤字典 的最好方法是使用字典理解的强大方法。

字典理解允许你将一个字典转化为另一个字典--按你喜欢的方式修改每一个(key, value) 对。

使用字典理解法按键过滤 Python 字典

词典理解的一般框架是{ expression context }

  • expression 定义你想如何改变每个 (key, value) 对。
  • context 定义 (key, value) 对,你想在新的字典中包括这些对。

这里有一个实际的例子,它过滤所有奇数键的 (key, value) 对。

print({k:v for (k,v) in names.items() if k%2 == 1})
# {1: 'Alice', 3: 'Carl', 5: 'Liz'}

而这里是一个过滤所有具有偶数键的 (key, value) 对的例子。

print({k:v for (k,v) in names.items() if k%2 == 0})
# {2: 'Bob', 4: 'Ann'}

让我们来看看按值过滤的 dictionary!它有什么不同吗?

使用字典的理解力按值过滤 Python 字典

不!它完全一样。

print({k:v for (k,v) in names.items() if len(v)<5})
# {2: 'Bob', 3: 'Carl', 4: 'Ann', 5: 'Liz'}

这个强大的框架不仅快速、易懂,而且简洁、一致。

过滤标准是表达式的最后一部分,这样你就可以快速掌握它的过滤方式。

print({k:v for (k,v) in names.items() if v.startswith('A')})
# {1: 'Alice', 4: 'Ann'}

在我们的交互式Cod Shell中自己试试(点击 "运行")。

总结 - 复制和粘贴的所有四种方法

这里是教程中所有四种简化复制和粘贴的方法。

names = {1: 'Alice',
         2: 'Bob',
         3: 'Carl',
         4: 'Ann',
         5: 'Liz'}

''' Method 1: Simple For Loop '''

# FILTER BY KEY
newDict = dict()

# Iterate over all (k,v) pairs in names
for key, value in names.items():

    # Is condition satisfied?
    if key%2 == 1:
        newDict[key] = value

print(newDict)
# {1: 'Alice', 3: 'Carl', 5: 'Liz'}


# FILTER BY VALUE
newDict = dict()

# Iterate over all (k,v) pairs in names
for key, value in names.items():

    # Is condition satisfied?
    if len(value)<5:
        newDict[key] = value

print(newDict)
# {2: 'Bob', 3: 'Carl', 4: 'Ann', 5: 'Liz'}


''' Method 2: Custom Function '''

def filter_dict(d, f):
    ''' Filters dictionary d by function f. '''
    newDict = dict()

    # Iterate over all (k,v) pairs in names
    for key, value in d.items():

        # Is condition satisfied?
        if f(key, value):
            newDict[key] = value

    return newDict


# FILTER BY KEY
print(filter_dict(names, lambda k,v: k%2 == 1))
print(filter_dict(names, lambda k,v: k%2 == 0))

# FILTER BY VALUE
print(filter_dict(names, lambda k,v: len(v)<5))
print(filter_dict(names, lambda k,v: v.startswith('A')))


''' Method 3: filter() '''

# FILTER BY KEY
print(dict(filter(lambda x: x[0]%2 == 1, names.items())))
print(dict(filter(lambda x: x[0]%2 == 0, names.items())))

# FITER BY VALUE
print(dict(filter(lambda x: len(x[1])<5, names.items())))
print(dict(filter(lambda x: x[1].startswith('A'), names.items())))


''' Method 4: Dict Comprehension '''

# FITER BY KEY
print({k:v for (k,v) in names.items() if k%2 == 1})
print({k:v for (k,v) in names.items() if k%2 == 0})

# FITER BY VALUE
print({k:v for (k,v) in names.items() if len(v)<5})
print({k:v for (k,v) in names.items() if v.startswith('A')})

但是,所有这些方法在算法复杂性和运行时间方面如何比较?让我们来看看。

算法分析

在对这些方法进行基准测试之前,让我们快速讨论一下计算复杂性。

所有这四种方法在原始字典中要过滤的元素数量上都有线性的运行时间复杂性--假设过滤条件本身具有恒定的运行时间复杂性(它与字典中的元素数量无关)。

方法复杂度
方法1:循环O(n)
方法2:自定义O(n)
方法3:过滤器()O(n)
方法4:字典理解O(n)

但这并不意味着所有的方法都同样有效。

对于这些类型的过滤操作,字典理解法通常是最快的,因为它不使用中间函数filter() 。而且它也是最容易理解的。

因此,你应该在你自己的代码中使用 dictionary comprehension 来按键或按值过滤一个 dictionary!