【问题标题】:How does collections.defaultdict work?collections.defaultdict 是如何工作的?
【发布时间】:2011-08-19 12:35:56
【问题描述】:

我已经阅读了 python 文档中的示例,但仍然无法弄清楚这种方法的含义。有人可以帮忙吗?以下是 python 文档中的两个示例

>>> from collections import defaultdict

>>> s = 'mississippi'
>>> d = defaultdict(int)
>>> for k in s:
...     d[k] += 1
...
>>> d.items()
[('i', 4), ('p', 2), ('s', 4), ('m', 1)]

>>> s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
>>> d = defaultdict(list)
>>> for k, v in s:
...     d[k].append(v)
...
>>> d.items()
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]

参数intlist 是干什么用的?

【问题讨论】:

标签: python dictionary default-value defaultdict


【解决方案1】:

通常,如果您尝试使用当前不在字典中的键获取项目,Python 字典会抛出 KeyError。相比之下,defaultdict 只会创建您尝试访问的任何项目(当然前提是它们尚不存在)。要创建这样一个“默认”项,它会调用您传递给构造函数的函数对象(更准确地说,它是一个任意的“可调用”对象,包括函数和类型对象)。对于第一个示例,使用int() 创建默认项,这将返回整数对象0。对于第二个示例,默认项是使用 list() 创建的,它返回一个新的空列表对象。

【讨论】:

  • 它在功能上是否与使用 d.get(key, default_val) 不同?
  • @Ambareesh d.get(key, default) 永远不会修改您的字典——它只会返回默认值并保持字典不变。另一方面,defaultdict 会将一个键插入 到字典中(如果它还没有)。这是一个很大的区别;请参阅问题中的示例以了解原因。
  • 我们如何知道每种类型的默认值是多少? int() 的 0 和 list() 的 [] 是直观的,但也可以有更复杂或自定义的类型。
  • @Sean defaultdict 调用你传入的任何构造函数。如果你传入一个类型T,值将使用T() 构造。并非所有类型都可以在不传递任何参数的情况下构造。如果你想构造这样的类型,你需要一个包装函数,或者类似functools.partial(T, arg1, arg2)
  • 或者更简单:一个 lambda。 defaultdict(lambda : T(arg1, arg2)).
【解决方案2】:

defaultdict 表示如果在字典中找不到键,则不会抛出KeyError,而是创建一个新条目。这个新条目的类型由 defaultdict 的参数给出。

例如:

somedict = {}
print(somedict[3]) # KeyError

someddict = defaultdict(int)
print(someddict[3]) # print int(), thus 0

【讨论】:

  • "这个新对的类型由 defaultdict 的参数给出。"请注意,参数可以是 any 可调用对象 - 而不仅仅是类型函数。例如,如果 foo 是一个返回“bar”的函数,则 foo 可以用作默认 dict 的参数,如果访问了不存在的键,则其值将设置为“bar”。
  • 或者如果你只想返回 "bar":somedict = defaultdict(lambda:"bar")
  • 第四行返回0整数,如果是someddict = defaultdict(list)则返回[ ]。 0 是默认整数吗?还是 [ ] 默认列表?
  • 两者都不是。 0 是不可变的 - 在 CPython 中,从 -5256 的所有值都是缓存的单例,但这是特定于实现的行为 - 在这两种情况下,每次使用 int()list() “创建”一个新实例。这样,d[k].append(v) 可以在不使用对同一列表的引用填充字典的情况下工作,这将使defaultdict 几乎无用。如果这是行为,defaultdict 将采用一个值而不是 lambda 作为参数。 (对不起,糟糕的解释!)
【解决方案3】:

默认字典

“标准字典包含 setdefault() 方法,用于检索值并在该值不存在时建立默认值。相比之下,defaultdict 让调用者在容器已初始化。”

Doug HellmannPython 标准库示例中定义

如何使用默认字典

导入默认字典

>>> from collections import defaultdict

初始化默认字典

通过传递初始化它

callable 作为它的第一个参数(强制)

>>> d_int = defaultdict(int)
>>> d_list = defaultdict(list)
>>> def foo():
...     return 'default value'
... 
>>> d_foo = defaultdict(foo)
>>> d_int
defaultdict(<type 'int'>, {})
>>> d_list
defaultdict(<type 'list'>, {})
>>> d_foo
defaultdict(<function foo at 0x7f34a0a69578>, {})

**kwargs 作为它的第二个参数(可选)

>>> d_int = defaultdict(int, a=10, b=12, c=13)
>>> d_int
defaultdict(<type 'int'>, {'a': 10, 'c': 13, 'b': 12})

>>> kwargs = {'a':10,'b':12,'c':13}
>>> d_int = defaultdict(int, **kwargs)
>>> d_int
defaultdict(<type 'int'>, {'a': 10, 'c': 13, 'b': 12})

它是如何工作的

作为标准字典的子类,它可以执行所有相同的功能。

但是在传递未知键的情况下,它会返回默认值而不是错误。例如:

>>> d_int['a']
10
>>> d_int['d']
0
>>> d_int
defaultdict(<type 'int'>, {'a': 10, 'c': 13, 'b': 12, 'd': 0})

如果你想改变默认值覆盖default_factory:

>>> d_int.default_factory = lambda: 1
>>> d_int['e']
1
>>> d_int
defaultdict(<function <lambda> at 0x7f34a0a91578>, {'a': 10, 'c': 13, 'b': 12, 'e': 1, 'd': 0})

>>> def foo():
...     return 2
>>> d_int.default_factory = foo
>>> d_int['f']
2
>>> d_int
defaultdict(<function foo at 0x7f34a0a0a140>, {'a': 10, 'c': 13, 'b': 12, 'e': 1, 'd': 0, 'f': 2})

问题中的示例

示例 1

由于 int 已作为 default_factory 传递,因此任何未知键默认返回 0。

现在,当字符串在循环中传递时,它将增加 d 中这些字母的计数。

>>> s = 'mississippi'
>>> d = defaultdict(int)
>>> d.default_factory
<type 'int'>
>>> for k in s:
...     d[k] += 1
>>> d.items()
[('i', 4), ('p', 2), ('s', 4), ('m', 1)]
>>> d
defaultdict(<type 'int'>, {'i': 4, 'p': 2, 's': 4, 'm': 1})

示例 2

由于列表已作为 default_factory 传递,任何未知(不存在)的键将默认返回 [ ](即列表)。

现在,当元组列表在循环中传递时,它会将值附加到 d[color]

>>> s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
>>> d = defaultdict(list)
>>> d.default_factory
<type 'list'>
>>> for k, v in s:
...     d[k].append(v)
>>> d.items()
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]
>>> d
defaultdict(<type 'list'>, {'blue': [2, 4], 'red': [1], 'yellow': [1, 3]})

【讨论】:

  • 感谢您的回答。你知道如何让常数总是不同吗?我解释一下:defaultdict(lambda: 'string', **kwargs) 不会按预期工作,因为所有新键都将共享相同的“字符串”实例。我怎样才能每次都提供一份副本?请注意,defaultdict(lambda: copy.copy('string'), **kwargs) 不起作用,因为 copy 只计算一次。
【解决方案4】:

字典是存储数据以供以后按名称(键)检索的便捷方式。键必须是唯一的、不可变的对象,并且通常是字符串。字典中的值可以是任何值。对于许多应用程序,值是简单类型,例如整数和字符串。

当字典中的值是集合(列表、字典等)时会更有趣。在这种情况下,必须在第一次使用给定键时初始化值(空列表或字典)。虽然这相对容易手动完成,但 defaultdict 类型可以自动化并简化这些操作。 defaultdict 的工作方式与普通 dict 完全相同,但它使用不带参数并为不存在的键提供默认值的函数(“默认工厂”)进行初始化。

defaultdict 永远不会引发 KeyError。任何不存在的键都会得到默认工厂返回的值。

from collections import defaultdict
ice_cream = defaultdict(lambda: 'Vanilla')

ice_cream['Sarah'] = 'Chunky Monkey'
ice_cream['Abdul'] = 'Butter Pecan'

print(ice_cream['Sarah'])
>>>Chunky Monkey

print(ice_cream['Joe'])
>>>Vanilla

这是另一个关于如何使用 defaultdict,我们可以降低复杂性的示例

from collections import defaultdict
# Time complexity O(n^2)
def delete_nth_naive(array, n):
    ans = []
    for num in array:
        if ans.count(num) < n:
            ans.append(num)
    return ans

# Time Complexity O(n), using hash tables.
def delete_nth(array,n):
    result = []
    counts = defaultdict(int)

    for i in array:
        if counts[i] < n:
            result.append(i)
            counts[i] += 1
    return result


x = [1,2,3,1,2,1,2,3]
print(delete_nth(x, n=2))
print(delete_nth_naive(x, n=2))

总之,当你需要一个字典,并且每个元素的值都应该以一个默认值开头时,使用一个 defaultdict。

【讨论】:

  • 最后,一个清晰、简单、pythonic 的例子。谢谢。
【解决方案5】:

这里对 defaultdicts 有很好的解释:http://ludovf.net/blog/python-collections-defaultdict/

基本上,参数 intlist 是您传递的函数。请记住,Python 接受函数名作为参数。 int 默认返回 0,list 在使用括号调用时返回一个空列表。

在普通字典中,如果在您的示例中我尝试调用d[a],我会收到错误(KeyError),因为只有键 m、s、i 和 p 存在并且键 a 尚未初始化。但是在一个defaultdict中,它以一个函数名作为参数,当你尝试使用一个尚未初始化的键时,它只是简单地调用你传入的函数并将其返回值分配为新键的值。

【讨论】:

    【解决方案6】:

    defaultdict 的行为可以很容易地在每次调用中使用dict.setdefault 而不是d[key] 来模仿。

    换句话说,代码:

    from collections import defaultdict
    
    d = defaultdict(list)
    
    print(d['key'])                        # empty list []
    d['key'].append(1)                     # adding constant 1 to the list
    print(d['key'])                        # list containing the constant [1]
    

    相当于:

    d = dict()
    
    print(d.setdefault('key', list()))     # empty list []
    d.setdefault('key', list()).append(1)  # adding constant 1 to the list
    print(d.setdefault('key', list()))     # list containing the constant [1]
    

    唯一的区别是,使用defaultdict,列表构造函数只被调用一次,而使用dict.setdefault,列表构造函数被更频繁地调用(但如果确实需要,可以重写代码以避免这种情况)。

    有些人可能会争辩说存在性能方面的考虑,但这个话题是一个雷区。 This post shows there isn't a big performance gain in using defaultdict, for example.

    IMO,defaultdict 是一个集合,它给代码带来的混乱多于好处。对我没用,但其他人可能会有不同的想法。

    【讨论】:

      【解决方案7】:

      由于问题是关于“它是如何工作的”,一些读者可能希望看到更多具体细节。具体来说,有问题的方法是__missing__(key) 方法。见:https://docs.python.org/2/library/collections.html#defaultdict-objects

      更具体地说,这个答案显示了如何以实际方式使用__missing__(key)https://stackoverflow.com/a/17956989/1593924

      为了阐明“可调用”的含义,这是一个交互式会话(从 2.7.6 开始,但也应该在 v3 中工作):

      >>> x = int
      >>> x
      <type 'int'>
      >>> y = int(5)
      >>> y
      5
      >>> z = x(5)
      >>> z
      5
      
      >>> from collections import defaultdict
      >>> dd = defaultdict(int)
      >>> dd
      defaultdict(<type 'int'>, {})
      >>> dd = defaultdict(x)
      >>> dd
      defaultdict(<type 'int'>, {})
      >>> dd['a']
      0
      >>> dd
      defaultdict(<type 'int'>, {'a': 0})
      

      这是 defaultdict 最典型的用法(除了 x 变量的无意义使用)。您可以使用 0 作为显式默认值做同样的事情,但不能使用简单的值:

      >>> dd2 = defaultdict(0)
      
      Traceback (most recent call last):
        File "<pyshell#7>", line 1, in <module>
          dd2 = defaultdict(0)
      TypeError: first argument must be callable
      

      相反,因为它传入了一个简单的函数(它动态创建了一个不带参数且始终返回 0 的无名函数),所以下面的代码有效:

      >>> dd2 = defaultdict(lambda: 0)
      >>> dd2
      defaultdict(<function <lambda> at 0x02C4C130>, {})
      >>> dd2['a']
      0
      >>> dd2
      defaultdict(<function <lambda> at 0x02C4C130>, {'a': 0})
      >>> 
      

      并且使用不同的默认值:

      >>> dd3 = defaultdict(lambda: 1)
      >>> dd3
      defaultdict(<function <lambda> at 0x02C4C170>, {})
      >>> dd3['a']
      1
      >>> dd3
      defaultdict(<function <lambda> at 0x02C4C170>, {'a': 1})
      >>> 
      

      【讨论】:

        【解决方案8】:

        我自己的 2¢:你也可以继承 defaultdict:

        class MyDict(defaultdict):
            def __missing__(self, key):
                value = [None, None]
                self[key] = value
                return value
        

        这对于非常复杂的情况会派上用场。

        【讨论】:

          【解决方案9】:

          defaultdict 工具是 Python 集合类中的一个容器。它类似于通常的字典(dict)容器,但有一个区别:值字段的数据类型是在初始化时指定的。

          例如:

          from collections import defaultdict
          
          d = defaultdict(list)
          
          d['python'].append("awesome")
          
          d['something-else'].append("not relevant")
          
          d['python'].append("language")
          
          for i in d.items():
          
              print i
          

          打印出来:

          ('python', ['awesome', 'language'])
          ('something-else', ['not relevant'])
          

          【讨论】:

          • “值字段的数据类型是在初始化时指定的”:这是不正确的。提供了元素工厂功能。这里list 是调用来填充缺失值的函数,而不是要创建的对象的类型。例如,要拥有1 的默认值,您可以使用lambda:1,这显然不是一个类型。
          【解决方案10】:

          那么,defaultdict 在以下情况下也可以引发 keyerror:

          from collections import defaultdict
          d = defaultdict()
          print(d[3]) #raises keyerror
          

          永远记得给 defaultdict 像这样的参数提供参数

          d = defaultdict(int)
          

          【讨论】:

            【解决方案11】:

            没有defaultdict,您可能可以为看不见的键分配新值,但不能修改它。例如:

            import collections
            d = collections.defaultdict(int)
            for i in range(10):
              d[i] += i
            print(d)
            # Output: defaultdict(<class 'int'>, {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9})
            
            import collections
            d = {}
            for i in range(10):
              d[i] += i
            print(d)
            # Output: Traceback (most recent call last): File "python", line 4, in <module> KeyError: 0
            

            【讨论】:

              【解决方案12】:

              简而言之:

              defaultdict(int) - 参数 int 表示值将是 int 类型。

              defaultdict(list) - 参数列表表示值将是列表类型。

              【讨论】:

                【解决方案13】:

                我认为最好用它来代替 switch case 语句。想象一下,如果我们有如下 switch case 语句:

                option = 1
                
                switch(option) {
                    case 1: print '1st option'
                    case 2: print '2nd option'
                    case 3: print '3rd option'
                    default: return 'No such option'
                }
                

                python 中没有可用的switch case 语句。我们可以使用defaultdict 来达到同样的效果。

                from collections import defaultdict
                
                def default_value(): return "Default Value"
                dd = defaultdict(default_value)
                
                dd[1] = '1st option'
                dd[2] = '2nd option'
                dd[3] = '3rd option'
                
                print(dd[4])    
                print(dd[5])    
                print(dd[3])
                

                打印出来:

                Default Value
                Default Value
                3rd option
                

                在上面的 sn-p dd 中没有键 4 或 5,因此它打印出我们在辅助函数中配置的默认值。这比原始字典要好得多,如果键不存在则抛出KeyError。由此可见,defaultdict 更像是一个 switch case 语句,我们可以避免复杂的if-elif-elif-else 块。

                this site 给我留下深刻印象的另一个好例子是:

                >>> from collections import defaultdict
                >>> food_list = 'spam spam spam spam spam spam eggs spam'.split()
                >>> food_count = defaultdict(int) # default value of int is 0
                >>> for food in food_list:
                ...     food_count[food] += 1 # increment element's value by 1
                ...
                defaultdict(<type 'int'>, {'eggs': 1, 'spam': 7})
                >>>
                

                如果我们尝试访问除 eggsspam 以外的任何项目,我们将得到 0。

                【讨论】:

                  【解决方案14】:

                  标准字典包括方法 setdefault() 用于检索值并在该值不存在时建立默认值。相比之下,defaultdict 让调用者在容器初始化时预先指定默认值。

                  import collections
                  
                  def default_factory():
                      return 'default value'
                  
                  d = collections.defaultdict(default_factory, foo='bar')
                  print 'd:', d
                  print 'foo =>', d['foo']
                  print 'bar =>', d['bar']
                  

                  只要所有键都具有相同的默认值,这种方法就可以很好地工作。如果默认值是用于聚合或累积值的类型,例如列表、集合甚至 int,则它可能特别有用。标准库文档包含几个以这种方式使用 defaultdict 的示例。

                  $ python collections_defaultdict.py
                  
                  d: defaultdict(<function default_factory at 0x100468c80>, {'foo': 'bar'})
                  foo => bar
                  bar => default value
                  

                  【讨论】:

                    【解决方案15】:
                    #dictinary and defaultdict
                    
                    normaldictionary=dict()
                    print(type(normaldictionary))
                    #print(normaldictionary["keynotexisit"])
                    #Above normal dictionary give an error as key not present
                    
                    from collections import defaultdict
                    defaultdict1=defaultdict()
                    print(type(defaultdict1))
                    #print(defaultdict1['keynotexisit'])
                    ######################################
                    
                    from collections import defaultdict
                    default2=defaultdict(int)
                    print(default2['keynotexist'])
                    

                    https://msatutorpy.medium.com/different-between-dictionary-and-defaultdictionary-cb215f682971

                    【讨论】:

                      【解决方案16】:

                      文档和解释几乎是不言自明的:

                      http://docs.python.org/library/collections.html#collections.defaultdict

                      作为参数传递的类型函数(int/str 等)用于为字典中不存在该键的任何给定键初始化默认值。

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2016-02-11
                        • 1970-01-01
                        • 1970-01-01
                        • 2017-07-24
                        • 2016-11-13
                        • 2017-10-11
                        相关资源
                        最近更新 更多