`if key in dict` so với` try / exception` - thành ngữ nào dễ đọc hơn?


93

Tôi có một câu hỏi về thành ngữ và khả năng đọc, và dường như có một cuộc đụng độ giữa các triết lý Python cho trường hợp cụ thể này:

Tôi muốn xây dựng từ điển A từ từ điển B. Nếu một khóa cụ thể không tồn tại trong B, thì không cần làm gì và tiếp tục.

Cách nào tốt hơn?

try:
    A["blah"] = B["blah"]
except KeyError:
    pass

hoặc là

if "blah" in B:
    A["blah"] = B["blah"]

"Làm và cầu xin sự tha thứ" so với "sự đơn giản và rõ ràng".

Đó là tốt hơn và tại sao?


1
Ví dụ thứ hai có thể được viết tốt hơn là if "blah" in B.keys(), hoặc if B.has_key("blah").
girasquid

2
không A.update(B)không làm việc cho bạn?
SilentGhost

21
@Luke: has_keyđã không còn được ủng hộ invà việc kiểm tra B.keys()thay đổi phép toán O (1) thành phép toán O (n).
kindall

4
@Luke: không phải đâu. .has_keykhông được dùng nữa và keystạo danh sách không cần thiết trong py2k và dư thừa trong py3k
SilentGhost

2
'xây dựng' A, như trong, A trống để bắt đầu với? Và chúng tôi chỉ muốn một số chìa khóa? Sử dụng một sự hiểu biết: A = dict((k, v) for (k, v) in B if we_want_to_include(k)).
Karl Knechtel

Câu trả lời:


76

Ngoại lệ không phải là điều kiện.

Phiên bản có điều kiện rõ ràng hơn. Đó là điều tự nhiên: đây là điều khiển luồng đơn giản, đó là điều kiện được thiết kế cho, không phải ngoại lệ.

Phiên bản ngoại lệ chủ yếu được sử dụng để tối ưu hóa khi thực hiện các tra cứu này trong một vòng lặp: đối với một số thuật toán, nó cho phép loại bỏ các thử nghiệm khỏi các vòng lặp bên trong. Nó không có lợi ích đó ở đây. Nó có một ưu điểm nhỏ là tránh phải nói "blah"hai lần, nhưng nếu bạn đang làm nhiều điều này, bạn có thể nên có một move_keychức năng trợ giúp .

Nói chung, tôi thực sự khuyên bạn nên sử dụng phiên bản có điều kiện theo mặc định trừ khi bạn có lý do cụ thể để không làm như vậy. Điều kiện là cách rõ ràng để làm điều này, thường là một khuyến nghị mạnh mẽ để thích giải pháp này hơn giải pháp khác.


3
Tôi không đồng ý. Nếu bạn nói "làm X, và nếu điều đó không hiệu quả, hãy làm Y". Lý do chính chống lại giải pháp có điều kiện ở đây, bạn phải viết "blah"thường xuyên hơn, dẫn đến tình trạng dễ xảy ra lỗi hơn.
glglgl

6
Và, đặc biệt trong Python, EAFP được sử dụng rất rộng rãi.
glglgl

8
Câu trả lời này sẽ đúng cho bất kỳ ngôn ngữ nào tôi biết ngoại trừ Python.
Tomáš Zato - Phục hồi Monica

3
Nếu bạn đang sử dụng các ngoại lệ như thể chúng là điều kiện trong Python, tôi hy vọng không ai khác phải đọc nó.
Glenn Maynard

Vậy, phán quyết cuối cùng là gì? :)
floatingpurr

60

Ngoài ra còn có một cách thứ ba để tránh cả hai trường hợp ngoại lệ và tra cứu hai lần, có thể quan trọng nếu việc tra cứu tốn kém:

value = B.get("blah", None)
if value is not None: 
    A["blah"] = value

Trong trường hợp bạn mong đợi từ điển để chứa Nonegiá trị, bạn có thể sử dụng một số hằng số bí ẩn khác như NotImplemented, Ellipsishoặc làm một cái mới:

MyConst = object()
def update_key(A, B, key):
    value = B.get(key, MyConst)
    if value is not MyConst: 
        A[key] = value

Dù sao, sử dụng update()là tùy chọn dễ đọc nhất đối với tôi:

a.update((k, b[k]) for k in ("foo", "bar", "blah") if k in b)

14

Theo những gì tôi hiểu, bạn muốn cập nhật dict A với các cặp khóa, giá trị từ dict B

update là một lựa chọn tốt hơn.

A.update(B)

Thí dụ:

>>> A = {'a':1, 'b': 2, 'c':3}
>>> B = {'d': 2, 'b':5, 'c': 4}
>>> A.update(B)
>>> A
{'a': 1, 'c': 4, 'b': 5, 'd': 2}
>>> 

"Nếu một khóa cụ thể không tồn tại trong B" Xin lỗi, lẽ ra phải rõ ràng hơn, nhưng tôi chỉ muốn sao chép các giá trị nếu các khóa cụ thể trong B tồn tại. Không phải tất cả ở B.
LeeMobile

1
@LeeMobile -A.update({k: v for k, v in B.iteritems() if k in specificset})
Omnifarious

8

Trích dẫn trực tiếp từ wiki hiệu suất Python:

Ngoại trừ lần đầu tiên, mỗi lần một từ được nhìn thấy, bài kiểm tra của câu lệnh if không thành công. Nếu bạn đang đếm một số lượng lớn các từ, nhiều từ có thể xảy ra nhiều lần. Trong tình huống mà việc khởi tạo một giá trị chỉ diễn ra một lần và việc tăng giá trị đó sẽ diễn ra nhiều lần, thì việc sử dụng câu lệnh try sẽ rẻ hơn.

Vì vậy, có vẻ như cả hai lựa chọn đều khả thi tùy theo tình hình. Để biết thêm chi tiết, bạn có thể muốn xem liên kết này: Thử ngoại trừ hiệu suất


đó là một bài đọc thú vị, nhưng tôi nghĩ hơi không đầy đủ. Dict chỉ được sử dụng có 1 phần tử và tôi nghi ngờ dicts lớn hơn sẽ có tác động đáng kể đến hiệu suất
user2682863

3

Tôi nghĩ rằng quy tắc chung ở đây là A["blah"]bình thường sẽ tồn tại, nếu vậy hãy thử ngoại trừ là tốt nếu không thì hãy sử dụngif "blah" in b:

Tôi nghĩ rằng "thử" là rẻ trong thời gian nhưng "ngoại trừ" là đắt hơn.


10
Đừng tiếp cận mã từ quan điểm tối ưu hóa theo mặc định; tiếp cận nó từ góc độ dễ đọc và khả năng bảo trì. Trừ khi mục tiêu cụ thể là tối ưu hóa, đây là tiêu chí sai (và nếu đó là tối ưu hóa, câu trả lời là điểm chuẩn, không phải phỏng đoán).
Glenn Maynard

Tôi có lẽ nên đặt điểm cuối cùng trong dấu ngoặc đơn hoặc bằng cách nào đó - ý chính của tôi là điểm đầu tiên và tôi nghĩ nó có thêm lợi thế của điểm thứ hai.
neil

3

Tôi nghĩ ví dụ thứ hai là những gì bạn nên làm trừ khi mã này có ý nghĩa:

try:
    A["foo"] = B["foo"]
    A["bar"] = B["bar"]
    A["baz"] = B["baz"]
except KeyError:
    pass

Hãy nhớ rằng mã sẽ ngừng hoạt động ngay khi có khóa không có trong B . Nếu mã này có ý nghĩa, thì bạn nên sử dụng phương pháp ngoại lệ, nếu không hãy sử dụng phương pháp thử nghiệm. Theo tôi, bởi vì nó ngắn hơn và thể hiện rõ ràng ý định, nó dễ đọc hơn rất nhiều so với phương pháp ngoại lệ.

Tất nhiên, những người nói với bạn để sử dụng updatelà chính xác. Nếu bạn đang sử dụng phiên bản Python hỗ trợ đọc hiểu từ điển, tôi thực sự thích mã này:

updateset = {'foo', 'bar', 'baz'}
A.update({k: B[k] for k in updateset if k in B})

"Hãy nhớ rằng mã sẽ hủy bỏ ngay khi có khóa không có trong B." - đây là lý do tại sao cách tốt nhất là chỉ đặt mức tối thiểu tuyệt đối trong khối try:, thường thì đây là một dòng duy nhất. Ví dụ đầu tiên sẽ tốt hơn khi là một phần của vòng lặp, chẳng hạn nhưfor key in ["foo", "bar", "baz"]: try: A[key] = B[key]
Zim

2

Quy tắc trong các ngôn ngữ khác là dành các ngoại lệ cho các điều kiện ngoại lệ, tức là các lỗi không xảy ra khi sử dụng thường xuyên. Không biết quy tắc đó áp dụng cho Python như thế nào, vì quy tắc đó không nên tồn tại StopIteration.


Tôi nghĩ rằng hạt dẻ này có nguồn gốc từ các ngôn ngữ mà việc xử lý ngoại lệ rất tốn kém và do đó có thể có tác động đáng kể đến hiệu suất. Tôi chưa bao giờ thấy bất kỳ lời biện minh hay lý lẽ thực sự nào đằng sau nó.
John La Rooy

@JohnLaRooy Không, hiệu suất không thực sự là lý do. Ngoại lệ là một loại goto không cục bộ , mà một số người coi là cản trở khả năng đọc của mã. Tuy nhiên, việc sử dụng các ngoại lệ theo cách này được coi là thành ngữ trong Python nên điều trên không áp dụng.
Ian Goldby

trả về có điều kiện cũng là "goto không cục bộ" và nhiều người thích phong cách đó thay vì kiểm tra các vệ tinh ở cuối khối mã.
cowbert,

1

Cá nhân tôi nghiêng về phương pháp thứ hai (nhưng sử dụng has_key):

if B.has_key("blah"):
  A["blah"] = B["blah"]

Theo cách đó, mỗi thao tác gán chỉ có hai dòng (thay vì 4 dòng với try / exception) và bất kỳ ngoại lệ nào được đưa ra sẽ là lỗi thực sự hoặc những thứ bạn đã bỏ lỡ (thay vì chỉ cố gắng truy cập các khóa không có ở đó) .

Hóa ra (xem các nhận xét về câu hỏi của bạn), has_keykhông được dùng nữa - vì vậy tôi đoán nó tốt hơn được viết dưới dạng

if "blah" in B:
  A["blah"] = B["blah"]

1

Bắt đầu Python 3.8và giới thiệu các biểu thức gán (PEP 572) ( :=toán tử), chúng ta có thể nắm bắt giá trị điều kiện dictB.get('hello', None)trong một biến valueđể cả hai kiểm tra xem nó có phải không None(như dict.get('hello', None)trả về giá trị được liên kết hoặc None) và sau đó sử dụng nó trong phần nội dung của điều kiện:

# dictB = {'hello': 5, 'world': 42}
# dictA = {}
if value := dictB.get('hello', None):
  dictA["hello"] = value
# dictA is now {'hello': 5}

Điều này không thành công nếu giá trị == 0
Eric

0

Mặc dù câu trả lời được chấp nhận nhấn mạnh về nguyên tắc "nhìn trước khi bạn nhảy" có thể áp dụng cho hầu hết các ngôn ngữ, nhưng nhiều ngôn ngữ hơn có thể là cách tiếp cận đầu tiên, dựa trên nguyên tắc python. Chưa kể nó là một phong cách mã hóa hợp pháp trong python. Điều quan trọng là đảm bảo rằng bạn đang sử dụng khối thử ngoại trừ trong ngữ cảnh phù hợp và đang làm theo các phương pháp hay nhất. Ví dụ. làm quá nhiều thứ trong một khối try, bắt một ngoại lệ rất rộng, hoặc tệ hơn- mệnh đề ngoại trừ trần trụi, v.v.

Dễ dàng xin tha thứ hơn là xin phép. (EAFP)

Xem tài liệu tham khảo về trăn tại đây .

Ngoài ra, blog này của Brett, một trong những nhà phát triển cốt lõi, đã nói ngắn gọn về phần lớn điều này.

Xem một cuộc thảo luận SO khác tại đây :

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.