Tôi nên sử dụng 'has_key ()' hoặc 'in' trên Python dicts?


911

Tôi tự hỏi những gì tốt hơn để làm:

d = {'a': 1, 'b': 2}
'a' in d
True

hoặc là:

d = {'a': 1, 'b': 2}
d.has_key('a')
True

Câu trả lời:


1287

in chắc chắn là nhiều pythonic.

Trong thực tế has_key()đã được gỡ bỏ bằng Python 3.x .


3
Ngoài ra, trong Python 3, để kiểm tra sự tồn tại của các giá trị, thay vì các khóa, hãy thử >>> 1 trong d.values ​​()
riza

217
Mặc dù vậy, một điều cần tránh là phải đảm bảo rằng bạn thực hiện: "key in some_dict" chứ không phải "key in some_dict.keys ()". Cả hai đều tương đương về mặt ngữ nghĩa, nhưng hiệu suất-khôn ngoan thì chậm hơn nhiều (O (n) so với O (1)). Tôi đã thấy mọi người thực hiện "in dict.keys ()" nghĩ rằng nó rõ ràng hơn và do đó tốt hơn.
Adam Parkin

2
@AdamParkin Tôi đã chứng minh nhận xét của bạn trong câu trả lời stackoverflow.com/a/41390975/117471
Bruno Bronosky

8
@AdamParkin Trong Python 3, keys()chỉ là một chế độ xem giống như tập hợp vào từ điển chứ không phải là bản sao, x in d.keys()O (1) cũng vậy. Tuy nhiên, x in dlà nhiều Pythonic.
Arthur Tacca

2
@AdamParkin Thật thú vị, tôi đã không thấy điều đó. Tôi cho rằng đó là vì x in d.keys()phải xây dựng và phá hủy một đối tượng tạm thời, hoàn thành việc cấp phát bộ nhớ, trong đó x in d.keys()chỉ là thực hiện một phép toán số học (tính toán hàm băm) và thực hiện tra cứu. Lưu ý rằng d.keys()chỉ dài khoảng 10 lần, điều này vẫn không thực sự dài. Tôi chưa kiểm tra nhưng tôi vẫn khá chắc chắn đó chỉ là O (1).
Arthur Tacca

253

in chiến thắng thực sự, không chỉ ở sự thanh lịch (và không bị phản đối ;-) mà còn về hiệu suất, ví dụ:

$ python -mtimeit -s'd=dict.fromkeys(range(99))' '12 in d'
10000000 loops, best of 3: 0.0983 usec per loop
$ python -mtimeit -s'd=dict.fromkeys(range(99))' 'd.has_key(12)'
1000000 loops, best of 3: 0.21 usec per loop

Mặc dù quan sát sau đây không phải lúc nào cũng đúng, nhưng bạn sẽ nhận thấy rằng thông thường , trong Python, giải pháp nhanh hơn là thanh lịch và Pythonic hơn; đó là lý do tại sao -mtimeitSO hữu ích - nó không chỉ là tiết kiệm một trăm nano giây ở đây và đó! -)


4
Cảm ơn vì điều này, đã xác minh rằng "trong some_dict" trên thực tế là O (1) dễ dàng hơn nhiều (hãy thử tăng 99 để nói 1999, và bạn sẽ thấy thời gian chạy là như nhau).
Adam Parkin

2
has_keydường như là O (1).
dan-gph


42

Sử dụng dict.has_key()if (và chỉ khi) mã của bạn được yêu cầu để có thể chạy được bởi các phiên bản Python sớm hơn 2.3 (khi key in dictđược giới thiệu).


1
Bản cập nhật WebSphere năm 2013 sử dụng Jython 2.1 làm ngôn ngữ kịch bản chính của nó. Vì vậy, điều này không may vẫn là một điều hữu ích cần lưu ý, năm năm sau khi bạn ghi nhận nó.
ArtOfWarfare

23

Có một ví dụ inthực sự giết chết hiệu suất của bạn.

Nếu bạn sử dụng intrên một thùng chứa O (1) chỉ thực hiện __getitem__has_key()không thực hiện , __contains__bạn sẽ biến tìm kiếm O (1) thành tìm kiếm O (N) (khi inquay lại tìm kiếm tuyến tính thông qua __getitem__).

Sửa lỗi rõ ràng là tầm thường:

def __contains__(self, x):
    return self.has_key(x)

6
Câu trả lời này được áp dụng khi nó được đăng, nhưng 99,95% độc giả có thể bỏ qua nó một cách an toàn. Trong hầu hết các trường hợp, nếu bạn đang làm việc với thứ gì đó tối nghĩa này, bạn sẽ biết nó.
wizzwizz4 27/07/18

2
Đây thực sự không phải là một vấn đề. has_key()đặc trưng cho từ điển Python 2 . in/ __contains__là API chính xác để sử dụng; đối với những vùng chứa không thể tránh được việc quét toàn bộ thì không có has_key()phương pháp nào và nếu có cách tiếp cận O (1) thì đó sẽ là trường hợp sử dụng cụ thể và tùy thuộc vào nhà phát triển để chọn loại dữ liệu phù hợp cho vấn đề.
Martijn Pieters

15

has_keylà một phương thức từ điển, nhưng insẽ hoạt động trên bất kỳ bộ sưu tập nào và ngay cả khi __contains__bị thiếu, insẽ sử dụng bất kỳ phương pháp nào khác để lặp lại bộ sưu tập để tìm hiểu.


1
Và cũng hoạt động trên các trình vòng lặp "x in xrange (90, 200) <=> 90 <= x <200"
u0b34a0f6ae

1
Tập: Đây có vẻ là một ý tưởng rất tồi: 50 hoạt động thay vì 2.
Clément

1
@ Clément Trong Python 3, thực sự khá hiệu quả để thực hiện incác thử nghiệm trên rangecác đối tượng. xrangeMặc dù vậy, tôi không chắc về hiệu quả của nó trên Python 2 . ;)
PM 2Ring

@ Clément không có trong Python 3; __contains__có thể tính toán tầm thường nếu một giá trị nằm trong phạm vi hay không.
Martijn Pieters

1
@AlexandreHuat Thời gian của bạn bao gồm chi phí tạo một rangethể hiện mới mỗi lần. Sử dụng một ví dụ duy nhất có sẵn , bài kiểm tra "số nguyên trong phạm vi" nhanh hơn khoảng 40% trong thời gian của tôi.
MisterMiyagi

14

Giải pháp cho dict.has_key () không được dùng nữa, sử dụng 'in' - trình soạn thảo văn bản cao siêu 3

Ở đây tôi đã lấy một ví dụ về từ điển có tên là 'lứa tuổi' -

ages = {}

# Add a couple of names to the dictionary
ages['Sue'] = 23

ages['Peter'] = 19

ages['Andrew'] = 78

ages['Karren'] = 45

# use of 'in' in if condition instead of function_name.has_key(key-name).
if 'Sue' in ages:

    print "Sue is in the dictionary. She is", ages['Sue'], "years old"

else:

    print "Sue is not in the dictionary"

6
Đúng, nhưng nó đã được trả lời, chào mừng bạn đến với Stackoveflow, cảm ơn vì ví dụ, luôn luôn kiểm tra các câu trả lời!
igorgue

@igorgue tôi không chắc chắn về các downvote cho cô ấy. Câu trả lời của cô ấy có thể giống với câu trả lời đã được trả lời, nhưng cô ấy cung cấp một ví dụ. Điều đó có đủ xứng đáng để trở thành một câu trả lời của SO không?
Akshat Agarwal

14

Mở rộng bài kiểm tra hiệu suất của Alex Martelli với nhận xét của Adam Parkin ...

$ python3.5 -mtimeit -s'd=dict.fromkeys(range( 99))' 'd.has_key(12)'
Traceback (most recent call last):
  File "/usr/local/Cellar/python3/3.5.2_3/Frameworks/Python.framework/Versions/3.5/lib/python3.5/timeit.py", line 301, in main
    x = t.timeit(number)
  File "/usr/local/Cellar/python3/3.5.2_3/Frameworks/Python.framework/Versions/3.5/lib/python3.5/timeit.py", line 178, in timeit
    timing = self.inner(it, self.timer)
  File "<timeit-src>", line 6, in inner
    d.has_key(12)
AttributeError: 'dict' object has no attribute 'has_key'

$ python2.7 -mtimeit -s'd=dict.fromkeys(range(  99))' 'd.has_key(12)'
10000000 loops, best of 3: 0.0872 usec per loop

$ python2.7 -mtimeit -s'd=dict.fromkeys(range(1999))' 'd.has_key(12)'
10000000 loops, best of 3: 0.0858 usec per loop

$ python3.5 -mtimeit -s'd=dict.fromkeys(range(  99))' '12 in d'
10000000 loops, best of 3: 0.031 usec per loop

$ python3.5 -mtimeit -s'd=dict.fromkeys(range(1999))' '12 in d'
10000000 loops, best of 3: 0.033 usec per loop

$ python3.5 -mtimeit -s'd=dict.fromkeys(range(  99))' '12 in d.keys()'
10000000 loops, best of 3: 0.115 usec per loop

$ python3.5 -mtimeit -s'd=dict.fromkeys(range(1999))' '12 in d.keys()'
10000000 loops, best of 3: 0.117 usec per loop

Số liệu thống kê tuyệt vời, đôi khi ẩn có thể tốt hơn rõ ràng (ít nhất là về hiệu quả) ...
varun

Cảm ơn bạn, @varun. Tôi đã quên mất câu trả lời này. Tôi cần phải làm loại thử nghiệm này thường xuyên hơn. Tôi thường xuyên đọc các chủ đề dài nơi mọi người tranh luận về Cách tốt nhất ™ để làm việc. Nhưng tôi hiếm khi nhớ làm thế nào dễ dàng để có được bằng chứng .
Bruno Bronosky 30/03/18

0

Nếu bạn có một cái gì đó như thế này:

t.has_key(ew)

thay đổi nó thành bên dưới để chạy trên Python 3.X trở lên:

key = ew
if key not in t

6
Không, bạn đã đảo ngược bài kiểm tra. t.has_key(ew)trả về Truenếu ewtham chiếu giá trị cũng là một khóa trong từ điển. key not in ttrả về Truenếu giá trị không có trong từ điển. Hơn nữa, key = ewbí danh là rất, rất dư thừa. Viết đúng chính tả là if ew in t. Đó là những gì câu trả lời được chấp nhận từ 8 năm trước đã nói với bạn.
Martijn Pieters
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.