Python - 'if foo in dict' vs 'thử: dict [foo]'


24

Đây không phải là một câu hỏi về bản chất của việc gõ vịt và nhiều hơn về việc ở lại pythonic, tôi cho rằng.

Trước hết - khi xử lý các dicts, đặc biệt là khi cấu trúc của dict khá dễ đoán và một khóa nhất định thường không xuất hiện nhưng đôi khi, trước tiên, tôi nghĩ đến hai cách tiếp cận:

if myKey in dict:
    do_some_work(dict[myKey])
else:
    pass

Và dĩ nhiên, cách tiếp cận 'tha thứ và cho phép' của Ye Olde.

try:
    do_some_work(dict[myKey])
except KeyError:
    pass

Là một người hành trình Python, tôi cảm thấy như tôi thấy cái sau được ưa thích hơn rất nhiều, điều mà tôi chỉ cảm thấy kỳ lạ bởi vì trong các tài liệu Python try/exceptsdường như được ưa thích khi có một sai lầm thực sự, trái ngược với sự thiếu vắng thành công?

Nếu dict thỉnh thoảng không có khóa trong myDict và được biết rằng nó sẽ không luôn luôn có khóa đó, đó có phải là một thử / ngoại trừ sai lệch theo ngữ cảnh? Đây không phải là lỗi lập trình, đó chỉ là sự thật của dữ liệu - chính tả này không có khóa cụ thể đó.

Điều này có vẻ đặc biệt quan trọng khi bạn nhìn vào cú pháp try / trừ / khác, có vẻ như thực sự hữu ích khi đảm bảo rằng thử không bắt quá nhiều lỗi. Bạn có thể làm một cái gì đó như:

try:
    foo += bar
except TypeError:
    pass
else:
    return some_more_work(foo)

Không phải điều đó sẽ dẫn đến việc nuốt tất cả các loại lỗi lạ có lẽ là kết quả của một số mã xấu? Đoạn mã trên có thể khiến bạn không thể thấy rằng bạn đang cố thêm 2 + {}và bạn có thể không bao giờ nhận ra rằng một phần mã của bạn đã bị sai một cách khủng khiếp. Tôi không khuyên chúng ta nên kiểm tra tất cả các loại, đó là lý do tại sao đó là Python chứ không phải JavaScript - nhưng một lần nữa với bối cảnh thử / ngoại trừ, có vẻ như phải bắt chương trình làm điều gì đó không nên làm, thay vào đó cho phép nó tiếp tục.

Tôi nhận ra ví dụ trên là một cái gì đó của một cuộc tranh luận người rơm, và trên thực tế là cố ý xấu. Nhưng với tín ngưỡng pythonic của better to ask forgiveness than permissiontôi, tôi không thể không cảm thấy như nó đặt ra câu hỏi về việc dòng trên cát thực sự nằm ở đâu giữa ứng dụng chính xác của if / other vs try / trừ, đặc biệt là khi bạn biết những gì sẽ xảy ra dữ liệu bạn đang làm việc với.

Tôi thậm chí không nói về mối quan tâm về tốc độ hoặc thực tiễn tốt nhất ở đây, tôi chỉ bị bối rối một cách lặng lẽ bởi sơ đồ Venn nhận thức về các trường hợp có vẻ như nó có thể đi theo bất kỳ cách nào, nhưng mọi người đều thất bại trong việc thử / ngoại trừ bởi vì "ai đó ở đâu đó nói rằng đó là Pythonic". Tôi đã rút ra kết luận sai về việc áp dụng cú pháp này chưa?

python 

Tôi đồng ý rằng xu hướng thử - ngoại trừ có nguy cơ che giấu lỗi. Ý kiến ​​của tôi là bạn thường nên tránh thử - ngoại trừ các điều kiện bạn muốn thấy (tất nhiên một số trường hợp ngoại lệ áp dụng cho quy tắc này).
Gordon Bean

Câu trả lời:


26

Sử dụng phương thức get thay thế:

some_dict.get(the_key, default_value)

... default_valuegiá trị được trả về ở đâu nếu the_keykhông có some_dict. Nếu bạn bỏ qua default_value, Noneđược trả lại nếu thiếu khóa.

Nói chung, trong Python, mọi người có xu hướng thích thử / ngoại trừ kiểm tra thứ gì đó trước tiên - xem mục EAFP trong bảng chú giải . Lưu ý rằng nhiều chức năng "kiểm tra tư cách thành viên" sử dụng các ngoại lệ đằng sau hậu trường.


Đây không phải là loại vấn đề tương tự sao? Nếu chúng tôi đang cố gắng làm việc với dictvà thực hiện công việc dựa trên những gì được tìm thấy, thì cảm giác giống như if myDict.get(a_key) is not None: do_work(myDict[a_key])là một lời nói và một ví dụ khác về việc tìm kiếm ngầm trước khi nhảy - cộng với IMHO hơi khó đọc hơn một chút. Có lẽ tôi không hiểu dict.get(a_key, a_default)đúng ngữ cảnh, nhưng có vẻ như đó là tiền thân của việc viết 'tuyên bố không chuyển đổi nếu / elses' hoặc các giá trị ma thuật. Tôi làm như những gì phương pháp này làm, mặc dù vậy, tôi sẽ xem xét sâu hơn.

1
@Stick - bạn làm: data = myDict.get(a_key, default)do_worksẽ làm điều đúng khi được đưa ra default(ví dụ None:) hoặc bạn làm if data: do_work(data). Chỉ cần một tra cứu trong cả hai trường hợp. (Có thể if data is not None: ..., trong cả hai trường hợp, nó vẫn chỉ là một lần tra cứu và sau đó logic của riêng bạn có thể tiếp quản.)
gièm pha

1
Vì vậy, tôi đã quyết định rằng dict.get () đã tiết kiệm cho tôi rất nhiều nỗ lực trong dự án hiện tại của tôi. Nó đã làm giảm số lượng dòng của tôi, dọn sạch rất nhiều try/except/elserác trông khủng khiếp và vừa cải thiện IMHO về khả năng đọc mã của tôi. Tôi đã học được một bài học quý giá mà tôi đã nghe trước đây nhưng đã cố gắng bỏ qua việc ghi nhớ: "Nếu bạn cảm thấy như bạn đang viết quá nhiều mã, hãy dừng lại và tìm đến các tính năng ngôn ngữ." Cảm ơn!!

@Stick - rất vui khi nghe nó :) Tôi thích lời khuyên đó, tôi chưa bao giờ nghe nó trước đây.
gièm pha

1
Về mặt kỹ thuật, cái nào là nhanh nhất?
Olivier Pons

4

Thiếu chìa khóa là một quy tắc hay một ngoại lệ ?

Từ quan điểm thực dụng, ném / bắt chậm hơn nhiều sau đó kiểm tra xem chìa khóa có ở trong bàn không. Nếu thiếu khóa là một sự cố phổ biến - bạn nên sử dụng điều kiện.

Một điều nữa có lợi cho điều kiện là một mệnh đề khác - dễ hiểu hơn nhiều khi một trong hai khối nếu được thực thi, hãy xem xét rằng thậm chí cùng một lớp ngoại lệ có thể được ném từ một số câu lệnh trong khối thử.

Mã trong mệnh đề ngoại trừ không phải là một phần của lỗ hổng thông thường (ví dụ: nếu không có khóa, hãy thêm nó) - nó sẽ được xử lý lỗi.


4
"Từ quan điểm thực dụng, ném / bắt chậm hơn rất nhiều sau đó kiểm tra xem chìa khóa có ở trong bàn không" - không, không phải vậy. Không nhất thiết, dù sao đi nữa. Cuối cùng tôi đã kiểm tra, các triển khai Python phổ biến nhất thực hiện try/ catchphiên bản nhanh hơn.
gièm pha

2
Ném / bắt trong trăn không phải là một công việc lớn đối với hiệu suất thỏa thuận, đến mức nó thường được sử dụng để kiểm soát dòng chảy trong trăn. Ngoài ra, có khả năng trong một số tình huống mà lệnh đó có thể được sửa đổi giữa kiểm tra và truy cập.
whatsisname

@whatsisname - Tôi đã nghĩ đến việc thêm câu trả lời đó vào câu trả lời của mình, nhưng TBH nếu bạn gặp nguy hiểm về chủng tộc như vậy, bạn sẽ gặp vấn đề lớn hơn nhiều khi EAFP vs LBYL: P
gièm pha

1

Trong tinh thần là "pythonic" và đặc biệt, gõ vịt - thử / ngoại trừ trông gần như mong manh đối với tôi.

Tất cả những gì bạn thực sự muốn là một cái gì đó với quyền truy cập giống như dict, nhưng __getitem__có thể bị ghi đè để làm một cái gì đó không hoàn toàn như bạn mong đợi. Ví dụ: sử dụng defaultdicttừ thư viện chuẩn:

In [1]: from collections import defaultdict

In [2]: foo = defaultdict(int)

In [3]: foo['a'] = 1

In [4]: foo['b']  # Someone did access checking with a try/except exactly as you have in the question
Out[4]: 0

In [5]: 'a' in foo
Out[5]: True

In [6]: 'b' in foo  # We never set it, but now it exists!
Out[6]: True

In [7]: 'c' in foo
Out[7]: False

Bởi vì giá trị trả về mặc định là Falsy, kiểm tra truy cập như thế sẽ hoạt động tốt hầu hết thời gian. Nó dường như giữ cho mã đơn giản hơn, đó là lý do tại sao đồng nghiệp của tôi và tôi đã làm điều đó trong nhiều tháng.

Thật không may, một vài tháng sau đó, các khóa bổ sung này thực sự đã gây ra sự cố và các lỗi khó theo dõi, vì 0đã trở thành một giá trị hợp lệ. Và đôi khi chúng ta sẽ sử dụng inđể kiểm tra xem một khóa có tồn tại hay không, làm cho mã làm những việc khác nhau khi nó phải giống hệt nhau.

Lý do tôi cho rằng nó có vẻ bị hỏng là vì trong tinh thần gõ vịt của Python, tất cả những gì bạn muốn là một thứ gì đó giống như chính tả, và defaultdictphù hợp với yêu cầu đó một cách hoàn hảo, giống như bất kỳ đối tượng nào bạn có thể tạo ra từ đó dict. Bạn không thể đảm bảo rằng người gọi sẽ không thay đổi việc thực hiện sau này, vì vậy bạn nên thực hiện điều đó với ít tác dụng phụ nhất.

Sử dụng phiên bản đầu tiên , if myKey in mydict.


Ngoài ra, lưu ý rằng đây là cụ thể về ví dụ trong câu hỏi. Tín ngưỡng của con trăn "Thà xin tha thứ hơn là cho phép" phần lớn là để đảm bảo bạn thực sự hành động chính xác khi bạn không đạt được điều mình muốn. Ví dụ: kiểm tra xem một tập tin có tồn tại và sau đó cố gắng đọc từ nó hay không - ít nhất 3 điều tôi có thể nghĩ ra khỏi đỉnh đầu có thể sai:

  • Một điều kiện cuộc đua là tập tin có thể đã bị xóa / di chuyển / etc
  • Bạn không có quyền đọc trên tệp
  • Các tập tin đã bị hỏng

Người đầu tiên bạn có thể cố gắng "xin phép", nhưng theo bản chất của một điều kiện chủng tộc, nó không nhất thiết phải luôn hoạt động; cái thứ hai quá dễ quên để kiểm tra; và thứ ba không thể được kiểm tra mà không cố đọc tệp. Trong mọi trường hợp, những gì bạn làm sau khi thất bại gần như chắc chắn sẽ giống nhau, vì vậy trong ví dụ này , tốt hơn "yêu cầu sự tha thứ" bằng cách sử dụng thử / ngoại trừ.

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.