Từ điển và giá trị mặc định


213

Giả sử connectionDetailslà một từ điển Python, cách tái cấu trúc mã tốt nhất, thanh lịch nhất, "pythonic" nhất như thế này là gì?

if "host" in connectionDetails:
    host = connectionDetails["host"]
else:
    host = someDefaultValue

Câu trả lời:


311

Như thế này:

host = connectionDetails.get('host', someDefaultValue)

40
Lưu ý rằng đối số thứ hai là một giá trị, không phải là khóa.
Marcin

7
+1 cho khả năng đọc, nhưng if/elsenhanh hơn nhiều. Điều đó có thể hoặc không thể đóng một vai trò.
Tim Pietzcker

7
@Tim, bạn có thể cung cấp một tài liệu tham khảo về lý do tại sao if/elsenhanh hơn?
nishantjr

2
@Tim: Tôi đã giả định rằng một trong những lợi thế của việc sử dụng ngôn ngữ cấp cao hơn là trình thông dịch sẽ có thể 'nhìn thấy' bên trong các chức năng và tối ưu hóa nó - rằng người dùng sẽ không phải đối phó với tối ưu hóa vi mô nhiều như vậy . Không phải đó là những thứ như biên dịch JIT để làm gì sao?
nishantjr

3
@ Vecantjr: Python (ít nhất là CPython, biến thể phổ biến nhất) không có trình biên dịch JIT. PyPy thực sự có thể giải quyết vấn đề này nhanh hơn, nhưng tôi chưa cài đặt được vì Python tiêu chuẩn luôn đủ nhanh cho mục đích của tôi cho đến nay. Nói chung, không có vấn đề gì trong cuộc sống thực - nếu bạn cần thực hiện thao tác bấm số cực kỳ quan trọng, Python có thể không phải là ngôn ngữ được lựa chọn ...
Tim Pietzcker

99

Bạn cũng có thể sử dụng defaultdictnhư vậy:

from collections import defaultdict
a = defaultdict(lambda: "default", key="some_value")
a["blabla"] => "default"
a["key"] => "some_value"

Bạn có thể vượt qua bất kỳ chức năng thông thường thay vì lambda:

from collections import defaultdict
def a():
  return 4

b = defaultdict(a, key="some_value")
b['absent'] => 4
b['key'] => "some_value"

7
Tôi đến đây vì một số vấn đề khác với câu hỏi của OP và giải pháp của bạn giải quyết chính xác.
0xc0de

Tôi sẽ +1 nó nhưng thật đáng buồn là nó không phù hợp với gethoặc các phương pháp tương tự.
0xc0de

Câu trả lời này rất hữu ích với tôi để đảm bảo bổ sung vào từ điển bao gồm các khóa mặc định. Việc triển khai của tôi là một chút quá dài để mô tả trong câu trả lời StackOverflow, vì vậy tôi đã viết về nó ở đây. Persagen.com/2020/03/05/ từ
Victoria Stuart

24

Mặc dù .get()là một thành ngữ hay, nhưng nó chậm hơn if/else(và chậm hơn try/exceptnếu sự hiện diện của khóa trong từ điển có thể được mong đợi hầu hết thời gian):

>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="try:\n a=d[1]\nexcept KeyError:\n a=10")
0.07691968797894333
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="try:\n a=d[2]\nexcept KeyError:\n a=10")
0.4583777282275605
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="a=d.get(1, 10)")
0.17784020746671558
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="a=d.get(2, 10)")
0.17952161730158878
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="if 1 in d:\n a=d[1]\nelse:\n a=10")
0.10071221458065338
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="if 2 in d:\n a=d[2]\nelse:\n a=10")
0.06966537335119938

3
Tôi vẫn không thấy tại sao if/then sẽ nhanh hơn. Cả hai trường hợp yêu cầu tra cứu từ điển, và trừ khi gọi trình get()quá chậm hơn nhiều, những gì các tài khoản khác cho suy thoái?
Jens

1
@Jens: Các cuộc gọi chức năng rất tốn kém.
Tim Pietzcker

1
Đó không phải là một vấn đề lớn trong một từ điển đông dân, đúng không? Có nghĩa là cuộc gọi chức năng sẽ không có vấn đề gì nhiều nếu việc tra cứu thực tế tốn kém. Nó có lẽ chỉ quan trọng trong các ví dụ đồ chơi.
AturSams

2
@zehelvion: Tra cứu từ điển O(1)không phụ thuộc vào kích thước từ điển, do đó, chức năng gọi qua chức năng có liên quan.
Tim Pietzcker

35
thật kỳ lạ nếu chi phí gọi hàm sẽ khiến bạn quyết định không sử dụng get. Sử dụng những gì các thành viên trong nhóm của bạn có thể đọc tốt nhất.
Jochen Bedersdorfer

19

Đối với nhiều mặc định khác nhau, hãy thử điều này:

connectionDetails = { "host": "www.example.com" }
defaults = { "host": "127.0.0.1", "port": 8080 }

completeDetails = {}
completeDetails.update(defaults)
completeDetails.update(connectionDetails)
completeDetails["host"]  # ==> "www.example.com"
completeDetails["port"]  # ==> 8080

3
Đây là một giải pháp thành ngữ tốt, nhưng có một cạm bẫy. Các kết quả không mong muốn có thể xảy ra nếu ConnectionDetails được cung cấp cùng Nonehoặc blankString là một trong các giá trị trong các cặp khóa-giá trị. Các defaultstừ điển có thể có khả năng có một trong những giá trị của nó vô tình trống rỗng. (xem thêm stackoverflow.com/questions/6354436 )
dreftymac

9

Có một phương pháp trong từ điển python để làm điều này: dict.setdefault

connectionDetails.setdefault('host',someDefaultValue)
host = connectionDetails['host']

Tuy nhiên phương pháp này đặt giá trị của connectionDetails['host']để someDefaultValuenếu chủ chốt hostchưa được xác định, không giống như những gì các câu hỏi yêu cầu.


1
Lưu ý rằng setdefault()trả về giá trị, vì vậy điều này cũng hoạt động : host = connectionDetails.setdefault('host', someDefaultValue). Chỉ cần lưu ý rằng nó sẽ được đặt thành connectionDetails['host']giá trị mặc định nếu khóa không có trước đó.
tro 108

7

(đây là một câu trả lời muộn)

Một cách khác là phân dictlớp lớp và thực hiện __missing__()phương thức, như thế này:

class ConnectionDetails(dict):
    def __missing__(self, key):
        if key == 'host':
            return "localhost"
        raise KeyError(key)

Ví dụ:

>>> connection_details = ConnectionDetails(port=80)

>>> connection_details['host']
'localhost'

>>> connection_details['port']
80

>>> connection_details['password']
Traceback (most recent call last):
  File "python", line 1, in <module>
  File "python", line 6, in __missing__
KeyError: 'password'

4

Thử nghiệm sự nghi ngờ của @Tim Pietzcker về tình huống trong PyPy (5.2.0-alpha0) cho Python 3.3.5, tôi thấy rằng thực sự cả hai .get()if/ elsecách thực hiện tương tự nhau. Trên thực tế, dường như trong trường hợp if / other thậm chí chỉ có một lần tra cứu nếu điều kiện và phép gán liên quan đến cùng một khóa (so sánh với trường hợp cuối cùng có hai lần tra cứu).

>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="try:\n a=d[1]\nexcept KeyError:\n a=10")
0.011889292989508249
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="try:\n a=d[2]\nexcept KeyError:\n a=10")
0.07310474599944428
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="a=d.get(1, 10)")
0.010391917996457778
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="a=d.get(2, 10)")
0.009348208011942916
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="if 1 in d:\n a=d[1]\nelse:\n a=10")
0.011475925013655797
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="if 2 in d:\n a=d[2]\nelse:\n a=10")
0.009605801998986863
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="if 2 in d:\n a=d[2]\nelse:\n a=d[1]")
0.017342638995614834

1

Bạn có thể sử dụng chức năng lamba cho việc này như là một lớp lót. Tạo một đối tượng mới connectionDetails2được truy cập như hàm ...

connectionDetails2 = lambda k: connectionDetails[k] if k in connectionDetails.keys() else "DEFAULT"

Bây giờ sử dụng

connectionDetails2(k)

thay vì

connectionDetails[k]

trong đó trả về giá trị từ điển nếu kcó trong các khóa, nếu không nó sẽ trả về"DEFAULT"


Tôi ủng hộ bạn nhưng vấn đề với giải pháp của bạn là các công cụ này hoạt động với [] nhưng lambdas hoạt động với ()
yukashima huksay
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.