Làm thế nào để bộ sưu tập.defaultdict hoạt động?


532

Tôi đã đọc các ví dụ trong tài liệu python, nhưng vẫn không thể hiểu phương thức này có nghĩa gì. Ai đó có thể giúp gì không? Đây là hai ví dụ từ các tài liệu 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])]

Các thông số intlistđể làm gì?


15
BTW, tùy thuộc vào trường hợp sử dụng của bạn, đừng quên đóng băng defaultdict để chỉ sử dụng đọc bằng cách đặt nó default_factory = Nonesau khi bạn hoàn thành việc điền vào defaultdict. Xem câu hỏi này .
Acumenus

Câu trả lời:


598

Thông thường, một từ điển Python sẽ ném KeyErrornếu bạn cố lấy một mục có khóa hiện không có trong từ điển. Các defaultdicttrái ngược đơn giản sẽ tạo ra bất kỳ mục mà bạn cố gắng truy cập (với điều kiện tất nhiên họ không tồn tại chưa). Để tạo một mục "mặc định" như vậy, nó gọi đối tượng hàm mà bạn chuyển đến hàm tạo (chính xác hơn, đó là một đối tượng "có thể gọi được" tùy ý, bao gồm các đối tượng hàm và kiểu). Đối với ví dụ đầu tiên, các mục mặc định được tạo bằng cách sử dụng int(), sẽ trả về đối tượng số nguyên 0. Đối với ví dụ thứ hai, các mục mặc định được tạo bằng cách sử dụng list(), trả về một đối tượng danh sách trống mới.


4
Có chức năng khác với sử dụng d.get (key, default_val) không?
Ambareesh

29
@Ambareesh d.get(key, default)sẽ không bao giờ sửa đổi từ điển của bạn - nó sẽ chỉ trả về mặc định và giữ nguyên từ điển. defaultdictmặt khác, sẽ chèn một khóa vào từ điển nếu nó chưa có. Đây là một sự khác biệt lớn; xem các ví dụ trong câu hỏi để hiểu tại sao
Sven Marnach

Làm thế nào để chúng ta biết giá trị mặc định cho mỗi loại là gì? 0 cho int () và [] cho list () là trực quan, nhưng cũng có thể có các loại phức tạp hơn hoặc tự xác định.
Sean

1
@Sean defaultdictgọi bất kỳ hàm tạo nào bạn truyền vào. Nếu bạn chuyển qua một loại T, các giá trị sẽ được xây dựng bằng cách sử dụng T(). Không phải tất cả các loại có thể được xây dựng mà không thông qua bất kỳ tham số. Nếu bạn muốn xây dựng một kiểu như vậy, bạn cần một hàm bao bọc, hoặc đại loại như thế functools.partial(T, arg1, arg2).
Sven Marnach

224

defaultdictcó nghĩa là nếu không tìm thấy khóa trong từ điển, thì thay vì KeyErrorbị ném, một mục mới được tạo. Loại của mục mới này được đưa ra bởi đối số của defaultdict.

Ví dụ:

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

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

10
"Loại của cặp mới này được đưa ra bởi đối số của defaultdict." Lưu ý rằng đối số có thể là bất kỳ đối tượng có thể gọi nào - không chỉ là các hàm kiểu. Ví dụ: nếu foo là một hàm trả về "bar", foo có thể được sử dụng làm đối số cho dict mặc định và nếu khóa không có mặt được truy cập, giá trị của nó sẽ được đặt thành "bar".
lf215

13
Hoặc nếu bạn chỉ muốn trả lại "bar": somedict = defaultdict (lambda: "bar")
Michael Scott Cuthbert

Dòng thứ tư trả về 0số nguyên, nếu someddict = defaultdict(list)nó trả về [ ]. Là 0 số nguyên mặc định? Hoặc [] danh sách mặc định?
Gathide

Cũng không. 0là bất biến - trong CPython, tất cả các giá trị từ -5đến 256là các singletons được lưu trong bộ nhớ cache nhưng đây là hành vi dành riêng cho việc triển khai - trong cả hai trường hợp, một trường hợp mới được "tạo" mỗi lần với int()hoặc list(). Bằng cách đó, d[k].append(v)có thể hoạt động mà không cần điền từ điển với các tham chiếu vào cùng một danh sách, điều này sẽ khiến defaultdictgần như vô dụng. Nếu đây là hành vi, defaultdictsẽ lấy một giá trị, không phải là lambda, làm tham số. (Xin lỗi vì lời giải thích khủng khiếp!)
wizzwizz4

93

mặc định

"Từ điển tiêu chuẩn bao gồm phương thức setdefault () để truy xuất giá trị và thiết lập mặc định nếu giá trị không tồn tại. Ngược lại, hãy defaultdictđể người gọi chỉ định mặc định (giá trị được trả về) ở phía trước khi container được khởi tạo."

như được định nghĩa bởi Doug Hellmann trong Thư viện tiêu chuẩn Python theo ví dụ

Cách sử dụng defaultdict

Nhập defaultdict

>>> from collections import defaultdict

Khởi tạo defaultdict

Khởi tạo nó bằng cách vượt qua

có thể gọi là đối số đầu tiên của nó (bắt buộc)

>>> 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 là đối số thứ hai của nó (tùy chọn)

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

hoặc là

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

Nó làm việc như thế nào

Là một lớp con của từ điển chuẩn, nó có thể thực hiện tất cả các chức năng tương tự.

Nhưng trong trường hợp chuyển một khóa không xác định, nó sẽ trả về giá trị mặc định thay vì lỗi. Ví dụ:

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

Trong trường hợp bạn muốn thay đổi giá trị mặc định, ghi đè 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})

hoặc là

>>> 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})

Ví dụ trong câu hỏi

ví dụ 1

Vì int đã được truyền dưới dạng default_factory, bất kỳ khóa không xác định nào sẽ trả về 0 theo mặc định.

Bây giờ khi chuỗi được truyền trong vòng lặp, nó sẽ tăng số lượng các bảng chữ cái trong 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})

Ví dụ 2

Vì một danh sách đã được thông qua dưới dạng default_factory, mọi khóa không xác định (không tồn tại) sẽ trả về [] (tức là danh sách) theo mặc định.

Bây giờ khi danh sách các bộ dữ liệu được truyền trong vòng lặp, nó sẽ nối thêm giá trị trong 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]})

20

Từ điển là một cách thuận tiện để lưu trữ dữ liệu để truy xuất sau này theo tên (khóa). Các khóa phải là các đối tượng duy nhất, không thay đổi và thường là các chuỗi. Các giá trị trong một từ điển có thể là bất cứ điều gì. Đối với nhiều ứng dụng, các giá trị là các loại đơn giản như số nguyên và chuỗi.

Sẽ thú vị hơn khi các giá trị trong từ điển là các bộ sưu tập (danh sách, ký hiệu, v.v.) Trong trường hợp này, giá trị (danh sách trống hoặc lệnh chính tả) phải được khởi tạo lần đầu tiên khi sử dụng khóa đã cho. Mặc dù điều này tương đối dễ thực hiện thủ công, nhưng kiểu defaultdict sẽ tự động hóa và đơn giản hóa các loại hoạt động này. Một defaultdict hoạt động chính xác như một dict bình thường, nhưng nó được khởi tạo với một hàm (Nhà máy mặc định của nhà máy) không có đối số và cung cấp giá trị mặc định cho khóa không tồn tại.

Một defaultdict sẽ không bao giờ nâng KeyError. Bất kỳ khóa nào không tồn tại đều nhận được giá trị được trả về bởi nhà máy mặc định.

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

Đây là một ví dụ khác về cách sử dụng defaultdict, chúng ta có thể giảm độ phức tạp

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))

Tóm lại, bất cứ khi nào bạn cần một từ điển và mỗi giá trị của phần tử sẽ bắt đầu bằng một giá trị mặc định, hãy sử dụng một defaultdict.


18

Có một lời giải thích tuyệt vời về defaultdicts ở đây: http://ludovf.net/blog/python-collections-defaultdict/

Về cơ bản, các tham số intlist là các hàm mà bạn vượt qua. Hãy nhớ rằng Python chấp nhận tên hàm làm đối số. int trả về 0 theo mặc định và danh sách trả về một danh sách trống khi được gọi bằng dấu ngoặc đơn.

Trong từ điển thông thường, nếu trong ví dụ của bạn, tôi thử gọi d[a], tôi sẽ gặp lỗi (KeyError), vì chỉ tồn tại các khóa m, s, i và p và khóa a chưa được khởi tạo. Nhưng trong một defaultdict, nó lấy một tên hàm làm đối số, khi bạn cố gắng sử dụng một khóa chưa được khởi tạo, nó chỉ đơn giản gọi hàm bạn truyền vào và gán giá trị trả về của nó làm giá trị của khóa mới.


7

Vì câu hỏi là về "cách thức hoạt động", một số độc giả có thể muốn xem thêm các loại hạt và bu lông. Cụ thể, phương pháp trong câu hỏi là __missing__(key)phương pháp. Xem: https://docs.python.org/2/l Library / collections.html # defaultdict-objects .

Cụ thể hơn, câu trả lời này cho thấy cách sử dụng __missing__(key)một cách thiết thực: https://stackoverflow.com/a/17956989/1593924

Để làm rõ ý nghĩa của 'có thể gọi được', đây là một phiên tương tác (từ 2.7.6 nhưng cũng hoạt động trong phiên bản 3):

>>> 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})

Đó là cách sử dụng defaultdict điển hình nhất (ngoại trừ việc sử dụng biến x vô nghĩa). Bạn có thể làm điều tương tự với 0 là giá trị mặc định rõ ràng, nhưng không phải với giá trị đơn giản:

>>> dd2 = defaultdict(0)

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

Thay vào đó, các hoạt động sau đây vì nó chuyển qua một hàm đơn giản (nó tạo ra một hàm không tên không có đối số và luôn trả về 0):

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

Và với một giá trị mặc định khác:

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

7

Riêng tôi 2: bạn cũng có thể phân lớp defaultdict:

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

Điều này có thể có ích cho các trường hợp rất phức tạp.


4

Hành vi của defaultdictcó thể dễ dàng bắt chước bằng cách sử dụng dict.setdefaultthay vì d[key]trong mọi cuộc gọi.

Nói cách khác, mã:

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]

tương đương với:

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]

Sự khác biệt duy nhất là, bằng cách sử dụng defaultdict, hàm tạo danh sách chỉ được gọi một lần và sử dụng dict.setdefaulthàm tạo danh sách được gọi thường xuyên hơn (nhưng mã có thể được viết lại để tránh điều này, nếu thực sự cần thiết).

Một số người có thể lập luận rằng có một sự xem xét hiệu suất, nhưng chủ đề này là một bãi mìn. Bài đăng này cho thấy không có hiệu suất lớn trong việc sử dụng defaultdict, ví dụ.

IMO, defaultdict là một bộ sưu tập thêm nhiều nhầm lẫn hơn lợi ích cho mã. Vô dụng đối với tôi, nhưng những người khác có thể nghĩ khác.


3

Công cụ defaultdict là một thùng chứa trong lớp bộ sưu tập của Python. Nó tương tự như bộ chứa từ điển (dict) thông thường, nhưng nó có một điểm khác biệt: Kiểu dữ liệu của trường giá trị được chỉ định khi khởi tạo.

Ví dụ:

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

Bản in này:

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

"Kiểu dữ liệu của trường giá trị được chỉ định khi khởi tạo": điều này không chính xác. Một chức năng nhà máy phần tử được cung cấp. Đây listlà chức năng gọi để điền vào một giá trị còn thiếu, không phải loại đối tượng cần tạo. Ví dụ: để có giá trị mặc định 1, bạn sẽ sử dụng lambda:1rõ ràng không phải là một loại.
asac

2

Tôi nghĩ rằng nó được sử dụng tốt nhất thay cho một tuyên bố trường hợp chuyển đổi. Hãy tưởng tượng nếu chúng ta có một tuyên bố trường hợp chuyển đổi như dưới đây:

option = 1

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

Không có switchbáo cáo trường hợp có sẵn trong python. Chúng ta có thể đạt được điều tương tự bằng cách sử dụng 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])

Nó in:

Default Value
Default Value
3rd option

Trong đoạn mã trên ddkhông có khóa 4 hoặc 5 và do đó nó in ra một giá trị mặc định mà chúng ta đã cấu hình trong hàm trợ giúp. Điều này khá đẹp hơn so với một từ điển thô, nơi a KeyErrorbị ném nếu không có khóa. Từ điều này, rõ ràng defaultdictgiống như một tuyên bố trường hợp chuyển đổi trong đó chúng ta có thể tránh được if-elif-elif-elsecác khối phức tạp .

Một ví dụ nữa khiến tôi ấn tượng rất nhiều từ trang web này là:

>>> 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})
>>>

Nếu chúng tôi cố gắng truy cập bất kỳ mục nào khác ngoài eggsspamchúng tôi sẽ nhận được số 0.


2

Nếu không defaultdict, có lẽ bạn có thể gán các giá trị mới cho các khóa không nhìn thấy nhưng bạn không thể sửa đổi nó. Ví dụ:

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

2

Chà, defaultdict cũng có thể nâng keyerror trong trường hợp sau:

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

Luôn nhớ đưa ra đối số cho defaultdict như defaultdict (int).


0

Từ điển chuẩn bao gồm phương thức setdefault () để truy xuất giá trị và thiết lập mặc định nếu giá trị không tồn tại. Ngược lại, defaultdict cho phép người gọi chỉ định phía trước mặc định khi container được khởi tạo.

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']

Điều này hoạt động tốt miễn là nó phù hợp cho tất cả các khóa có cùng mặc định. Nó có thể đặc biệt hữu ích nếu mặc định là loại được sử dụng để tổng hợp hoặc tích lũy các giá trị, chẳng hạn như danh sách, bộ hoặc thậm chí int. Tài liệu thư viện chuẩn bao gồm một số ví dụ về việc sử dụng defaultdict theo cách này.

$ python collections_defaultdict.py

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

0

Nói ngắn gọn:

defaultdict(int) - đối số int chỉ ra rằng các giá trị sẽ là kiểu int.

defaultdict(list) - danh sách đối số chỉ ra rằng các giá trị sẽ là loại danh sách.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.