Các từ điển được đặt hàng trong Python 3.6+?


469

Từ điển được đặt hàng trong Python 3.6 (ít nhất là theo triển khai CPython) không giống như các phiên bản trước. Đây có vẻ là một thay đổi đáng kể, nhưng nó chỉ là một đoạn ngắn trong tài liệu . Nó được mô tả như một chi tiết triển khai CPython chứ không phải là một tính năng ngôn ngữ, nhưng cũng ngụ ý điều này có thể trở thành tiêu chuẩn trong tương lai.

Làm thế nào để thực hiện từ điển mới thực hiện tốt hơn so với cái cũ trong khi duy trì trật tự phần tử?

Đây là văn bản từ tài liệu:

dict()bây giờ sử dụng một đại diện nhỏ gọn của người Viking được PyPy tiên phong . Việc sử dụng bộ nhớ của dict mới () nhỏ hơn từ 20% đến 25% so với Python 3.5. PEP 468 (Giữ nguyên thứ tự ** kwargs trong một hàm.) Được thực hiện bằng cách này. Khía cạnh giữ trật tự của triển khai mới này được coi là một chi tiết triển khai và không nên dựa vào (điều này có thể thay đổi trong tương lai, nhưng mong muốn có triển khai chính tả mới này trong ngôn ngữ cho một vài bản phát hành trước khi thay đổi thông số ngôn ngữ để bắt buộc ngữ nghĩa duy trì trật tự cho tất cả các triển khai Python hiện tại và tương lai, điều này cũng giúp duy trì khả năng tương thích ngược với các phiên bản cũ hơn của ngôn ngữ trong đó thứ tự lặp ngẫu nhiên vẫn còn hiệu lực, ví dụ Python 3.5). (Được đóng góp bởi INADA Naoki trongphát hành 27350 . Ý tưởng ban đầu được đề xuất bởi Raymond Hettinger .)

Cập nhật tháng 12 năm 2017: dictthứ tự chèn giữ lại được đảm bảo cho Python 3.7


2
Xem chủ đề này trên danh sách gửi thư của Python-Dev: mail.python.org/pipermail/python-dev/2016-September/146327.html nếu bạn chưa thấy nó; về cơ bản nó là một cuộc thảo luận xung quanh những chủ đề này.
mgc

1
Nếu các kwarg bây giờ được cho là đã được đặt hàng (đó là ý tưởng hay) và kwargs là dict, không phải OrderedDict, thì tôi đoán người ta có thể cho rằng các khóa dict sẽ được đặt hàng trong phiên bản tương lai của Python, mặc dù tài liệu nói khác.
Dmitriy Sintsov

4
@DmitriySintsov Không, đừng đưa ra giả định đó. Đây là một vấn đề được đưa ra trong quá trình viết PEP xác định tính năng duy trì trật tự **kwargsvà vì vậy từ ngữ được sử dụng là ngoại giao: **kwargstrong một chữ ký chức năng hiện được đảm bảo là ánh xạ bảo toàn trật tự chèn . Họ đã sử dụng ánh xạ thuật ngữ để không buộc bất kỳ triển khai nào khác phải ra lệnh chính tả (và sử dụng OrderedDictnội bộ) và như một cách để báo hiệu rằng điều này không phải phụ thuộc vào thực tế dictlà không được ra lệnh.
Dimitris Fasarakis Hilliard

7
Một lời giải thích video hay từ Raymond Hettinger
Alex

1
@wazoox, thứ tự và độ phức tạp của hàm băm không thay đổi. Sự thay đổi làm cho hashmap nhỏ hơn bằng cách lãng phí ít không gian hơn và không gian lưu được (thường là?) Nhiều hơn mảng phụ trợ. Nhanh hơn, nhỏ hơn, đã ra lệnh - bạn có thể chọn tất cả 3.
John La Rooy

Câu trả lời:


512

Các từ điển được đặt hàng trong Python 3.6+?

Chúng được chèn theo thứ tự [1] . Kể từ Python 3.6, đối với việc triển khai CPython của Python, từ điển nhớ thứ tự các mục được chèn . Đây được coi là một chi tiết triển khai trong Python 3.6 ; bạn cần sử dụng OrderedDictnếu bạn muốn thứ tự chèn được đảm bảo trong các triển khai Python khác (và hành vi được đặt hàng khác [1] ).

Kể từ Python 3.7 , đây không còn là chi tiết triển khai và thay vào đó trở thành một tính năng ngôn ngữ. Từ một tin nhắn python-dev của GvR :

Làm cho nó như vậy. "Dict giữ trật tự chèn" là phán quyết. Cảm ơn!

Điều này đơn giản có nghĩa là bạn có thể phụ thuộc vào nó . Các triển khai khác của Python cũng phải cung cấp một từ điển được đặt hàng chèn nếu chúng muốn là một triển khai phù hợp của Python 3.7.


Làm thế nào để 3.6thực hiện từ điển Python thực hiện tốt hơn [2] so với cái cũ hơn trong khi duy trì trật tự phần tử?

Về cơ bản, bằng cách giữ hai mảng .

  • Mảng đầu tiên dk_entries, giữ các mục ( thuộc loạiPyDictKeyEntry ) cho từ điển theo thứ tự chúng được chèn vào. Thứ tự bảo quản đạt được bằng cách này là một mảng chỉ nối thêm trong đó các mục mới luôn được chèn vào cuối (thứ tự chèn).

  • Thứ hai, dk_indicesgiữ các chỉ số cho dk_entriesmảng (nghĩa là các giá trị chỉ ra vị trí của mục tương ứng trong dk_entries). Mảng này hoạt động như bảng băm. Khi một khóa được băm, nó dẫn đến một trong các chỉ mục được lưu trữ dk_indicesvà mục tương ứng được tìm nạp bằng cách lập chỉ mục dk_entries. Vì chỉ giữ các chỉ mục, nên loại của mảng này phụ thuộc vào kích thước tổng thể của từ điển (từ loại int8_t( 1byte) đến int32_t/ int64_t( 4/ 8byte) trên các bản dựng 32/ 64bit)

Trong lần thực hiện trước, một mảng thưa về loại PyDictKeyEntryvà kích thước dk_sizephải được phân bổ; thật không may, nó cũng dẫn đến rất nhiều không gian trống vì mảng đó không được phép quá 2/3 * dk_sizeđầy vì lý do hiệu suất . (và không gian trống vẫnPyDictKeyEntrykích thước!).

Đây không phải là trường hợp bây giờ vì chỉ có các mục yêu cầu được lưu trữ (những mục đã được chèn) và một mảng loại thưa intX_t( Xtùy thuộc vào kích thước chính tả) 2/3 * dk_sizeđược giữ nguyên. Các không gian trống thay đổi từ loại PyDictKeyEntrysang intX_t.

Vì vậy, rõ ràng, việc tạo ra một mảng kiểu PyDictKeyEntrythưa thớt đòi hỏi nhiều bộ nhớ hơn là một mảng thưa thớt để lưu trữ ints.

Bạn có thể xem toàn bộ cuộc hội thoại trên Python-Dev liên quan đến tính năng này nếu quan tâm, đây là một bài đọc tốt.


Trong đề xuất ban đầu được thực hiện bởi Raymond Hettinger , có thể thấy một hình ảnh trực quan của các cấu trúc dữ liệu được sử dụng để nắm bắt ý chính của ý tưởng.

Ví dụ: từ điển:

d = {'timmy': 'red', 'barry': 'green', 'guido': 'blue'}

hiện được lưu trữ dưới dạng [keyhash, key, value]:

entries = [['--', '--', '--'],
           [-8522787127447073495, 'barry', 'green'],
           ['--', '--', '--'],
           ['--', '--', '--'],
           ['--', '--', '--'],
           [-9092791511155847987, 'timmy', 'red'],
           ['--', '--', '--'],
           [-6480567542315338377, 'guido', 'blue']]

Thay vào đó, dữ liệu nên được tổ chức như sau:

indices =  [None, 1, None, None, None, 0, None, 2]
entries =  [[-9092791511155847987, 'timmy', 'red'],
            [-8522787127447073495, 'barry', 'green'],
            [-6480567542315338377, 'guido', 'blue']]

Như bây giờ bạn có thể thấy, trong đề xuất ban đầu, rất nhiều không gian về cơ bản là trống để giảm va chạm và giúp việc tra cứu nhanh hơn. Với phương pháp mới, bạn giảm bộ nhớ cần thiết bằng cách di chuyển độ thưa thớt ở nơi thực sự cần thiết, trong các chỉ số.


[1]: Tôi nói "chèn theo thứ tự" và không "ra lệnh" vì, với sự tồn tại của OrderedDict, "đã ra lệnh" gợi ý thêm hành vi mà dictđối tượng không cung cấp . OrderedDicts có thể đảo ngược, cung cấp các phương thức nhạy cảm với thứ tự và, chủ yếu, cung cấp một bài kiểm tra tính công bằng theo thứ tự ( ==, !=). dicts hiện không cung cấp bất kỳ hành vi / phương pháp nào.


[2]: Việc triển khai từ điển mới thực hiện bộ nhớ tốt hơn bằng cách được thiết kế gọn hơn; đó là lợi ích chính ở đây. Tốc độ khôn ngoan, sự khác biệt không quá lớn, có những nơi mà chính quyền mới có thể đưa ra các hồi quy nhẹ ( ví dụ như tra cứu khóa ) trong khi ở những người khác (lặp đi lặp lại và thay đổi kích thước đến tâm trí) nên tăng cường hiệu suất.

Nhìn chung, hiệu suất của từ điển, đặc biệt là trong các tình huống thực tế, được cải thiện nhờ sự nhỏ gọn được giới thiệu.


15
Vì vậy, những gì xảy ra khi một mục bị loại bỏ? là entriesdanh sách thay đổi kích cỡ? hoặc là một không gian trống được giữ? hoặc nó được nén theo thời gian?
njzk2

18
@ njzk2 Khi một mục bị xóa, chỉ mục tương ứng được thay thế bằng DKIX_DUMMYmột giá trị -2và mục nhập trong entrymảng được thay thế bằngNULL , khi việc chèn được thực hiện, các giá trị mới được thêm vào mảng mục, chưa thể nhận ra, nhưng khá chắc chắn khi các chỉ số lấp đầy vượt quá 2/3ngưỡng thay đổi kích thước được thực hiện. Điều này có thể dẫn đến thu hẹp thay vì phát triển nếu có nhiều DUMMYmục tồn tại.
Dimitris Fasarakis Hilliard

3
@Chris_Rands Không, hồi quy thực tế duy nhất tôi thấy là trên trình theo dõi trong một tin nhắn của Victor . Ngoài microbenchmark đó, tôi không thấy vấn đề / thông báo nào khác cho thấy sự khác biệt nghiêm trọng về tốc độ trong tải công việc thực tế. Có những nơi mà chính tả mới có thể đưa ra các hồi quy nhẹ (ví dụ như tra cứu khóa) trong khi ở những nơi khác (lặp đi lặp lại và thay đổi kích thước đến tâm trí) sẽ tăng hiệu suất.
Dimitris Fasarakis Hilliard

3
Sửa lỗi về phần thay đổi kích thước : Từ điển không thay đổi kích thước khi bạn xóa các mục, chúng sẽ tính lại khi bạn chèn lại. Vì vậy, nếu một lệnh được tạo với d = {i:i for i in range(100)}.poptất cả các mục bạn không chèn, kích thước sẽ không thay đổi. Khi bạn thêm vào nó một lần nữa, d[1] = 1kích thước phù hợp sẽ được tính và dict thay đổi kích thước.
Dimitris Fasarakis Hilliard

6
@Chris_Rands Tôi khá chắc chắn rằng nó đang ở. Vấn đề là, và lý do tại sao tôi thay đổi câu trả lời của mình để xóa các tuyên bố về việc ' dictđược ra lệnh', dictkhông được ra lệnh theo nghĩa OrderedDict. Vấn đề đáng chú ý là sự bình đẳng. dicts có thứ tự vô cảm ==, OrderedDicts có thứ tự nhạy cảm. Việc bán phá giá OrderedDictvà thay đổi dictsđến bây giờ có các so sánh nhạy cảm theo thứ tự có thể dẫn đến nhiều sự phá vỡ trong mã cũ. Tôi đoán điều duy nhất có thể thay đổi về OrderedDicts là việc thực hiện nó.
Dimitris Fasarakis Hilliard

67

Dưới đây là trả lời câu hỏi đầu tiên ban đầu:

Tôi nên sử dụng dicthoặc OrderedDicttrong Python 3.6?

Tôi nghĩ rằng câu này từ tài liệu thực sự đủ để trả lời câu hỏi của bạn

Khía cạnh giữ trật tự của việc thực hiện mới này được coi là một chi tiết thực hiện và không nên dựa vào

dictkhông rõ ràng có nghĩa là một bộ sưu tập theo thứ tự, vì vậy nếu bạn muốn duy trì sự nhất quán và không dựa vào tác dụng phụ của việc triển khai mới, bạn nên tuân thủ OrderedDict.

Làm cho mã của bạn bằng chứng trong tương lai :)

Có một cuộc tranh luận về điều đó ở đây .

EDIT: Python 3.7 sẽ giữ điều này như một tính năng xem


1
Có vẻ như nếu họ không có nghĩa đó là một tính năng thực sự mà chỉ là một chi tiết triển khai thì họ thậm chí không nên đưa nó vào tài liệu sau đó.
xji

3
Tôi không chắc chắn về cảnh báo chỉnh sửa của bạn; vì bảo đảm chỉ áp dụng cho Python 3.7, tôi cho rằng lời khuyên dành cho Python 3.6 là không thay đổi, tức là các lệnh được đặt hàng trong CPython nhưng không được tính vào nó
Chris_Rands

25

Cập nhật: Guido van Rossum đã thông báo trên danh sách gửi thư rằng kể từ Python 3.7 dicttrong tất cả các triển khai Python phải duy trì thứ tự chèn.


2
Bây giờ, thứ tự chính là tiêu chuẩn chính thức, mục đích của OrderedDict là gì? Hoặc, bây giờ nó là dư thừa?
Jonny Bánh quế

2
Tôi đoán OrderedDict sẽ không dư thừa vì nó có move_to_endphương thức và tính công bằng của nó là thứ tự nhạy cảm: docs.python.org/3/l Library / . Xem ghi chú về câu trả lời của Jim Fasarakis Hilliard.
fjsj

@JonnyWaffles xem câu trả lời của Jim và câu hỏi này về stackoverflow.com/questions/50872498/
dọa

3
Nếu bạn muốn mã của mình chạy giống nhau trên 2.7 và 3.6 / 3.7 +, bạn cần sử dụng OrderedDict
khiển thuyền

3
Có khả năng sẽ sớm có "UnorderedDict" cho những người thích gây rắc rối cho các lý do bảo mật của họ vì lý do bảo mật; p
ZF007

9

Tôi muốn thêm vào cuộc thảo luận ở trên nhưng không có tiếng để bình luận.

Python 3.8 chưa được phát hành hoàn toàn, nhưng nó thậm chí sẽ bao gồm reversed()chức năng trên từ điển (loại bỏ sự khác biệt khác OrderedDict.

Dict và dictview bây giờ có thể lặp lại theo thứ tự chèn đảo ngược bằng cách sử dụng đảo ngược (). (Được đóng góp bởi Rémi Lapeyre trong bpo-33462.) Xem những gì mới trong python 3.8

Tôi không thấy bất kỳ đề cập nào về toán tử đẳng thức hoặc các tính năng khác OrderedDictđể chúng vẫn không hoàn toàn giống nhau.

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.