Словники та значення за замовчуванням


213

Якщо припустити connectionDetails, що це словник Python, що є найкращим, найелегантнішим, найбільш "пітонічним" способом рефакторингу такого коду?

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

Відповіді:


311

Подобається це:

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

40
Зауважте, що другий аргумент - це значення, а не ключ.
Марцін

7
+1 для читабельності, але if/elseнабагато швидше. Це може чи не може грати ролі.
Тім Піцкер

7
@Tim, чи можете ви надати посилання на те, чому if/elseшвидше?
nishantjr

2
@Tim: Я припускав, що однією з переваг використання мови вищого рівня є те, що перекладач зможе «побачити» всередині функцій і оптимізувати її - що користувачеві не доведеться стільки займатися мікрооптимізаціями. . Чи не для цього потрібні такі речі, як компіляція JIT?
nishantjr

3
@nishantjr: У Python (принаймні CPython, найпоширеніший варіант) немає компіляції JIT. PyPy дійсно може вирішити це швидше, але я цього не встановив, оскільки стандартний Python завжди був досить швидким для моїх цілей. Загалом, у реальному житті це навряд чи має значення - якщо вам потрібно зробити хрускіт із критичним часом, Python, мабуть, не є мовою вибору ...
Тім Піткер

99

Ви також можете використовувати defaultdictподібне так:

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

Ви можете передавати будь-яку звичайну функцію замість лямбда:

from collections import defaultdict
def a():
  return 4

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

7
Я прийшов сюди з якоїсь іншої проблеми, ніж питання щодо ОП, і ваше рішення саме її вирішує.
0xc0de

Я поставив би це +1, але, на жаль, він не вписується у getподібні методи.
0xc0de

Ця відповідь була корисною для мене, щоб забезпечити доповнення до словника, що містив ключі за замовчуванням. Моя реалізація трохи занадто довга, щоб описати її у відповіді StackOverflow, тому я писав про це тут. persagen.com/2020/03/05/…
Вікторія Стюарт

24

Хоча .get()це приємна ідіома, вона відбувається повільніше if/else(і повільніше, ніж try/exceptякщо більшість часу можна очікувати наявності ключа у словнику):

>>> 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
Я досі не бачу, чому if/then було б швидше. В обох випадках потрібно пошук по словником, і якщо виклик get()НЕ так багато повільніше, ніж інші рахунки для уповільнення?
Єнс

1
@Jens: Функціональні дзвінки дорогі.
Тім Піцкер

1
Що не має бути великою справою в густонаселеному словнику, правильно? Значення виклику функції не матиме великого значення, якщо фактичний пошук коштує дорого. Це, мабуть, має значення лише в прикладах іграшок.
AturSams

2
@zehelvion: Пошук словника не O(1)залежить від розміру словника, тому накладні виклики функцій є актуальними.
Тім Піцкер

35
це химерно, якщо накладні виклики функції змусять вас вирішити проти використання get. Використовуйте те, що найкраще читають члени вашої команди.
Йохен Бедерсдорфер

19

Для кількох різних значень за замовчуванням спробуйте це:

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
Це хороше ідіоматичне рішення, але є підводний камінь. Несподівані результати можуть призвести, якщо з'єднанняDetails поставляється разом Noneіз порожнімString як одне із значень у парах ключ-значення. У defaultsсловнику потенційно може бути ненавмисне викреслене одне його значення. (Дивись також stackoverflow.com/questions/6354436 )
dreftymac

9

Існує метод у словниках python для цього: dict.setdefault

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

Однак цей метод встановлює значення connectionDetails['host']для someDefaultValueякщо ключ hostномер не встановлено, в відміну від того, що поставлено питання.


1
Зверніть увагу , що setdefault()повертає значення, так що це працює , а також: host = connectionDetails.setdefault('host', someDefaultValue). Тільки будьте обережні, що він встановить connectionDetails['host']значення за замовчуванням, якщо ключа раніше не було.
ash108

7

(це пізня відповідь)

Альтернативою є підклас dictкласу та реалізація __missing__()методу, такий:

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

Приклади:

>>> 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

Перевіряючи підозру @Tim Pietzcker щодо ситуації в PyPy (5.2.0-alpha0) для Python 3.3.5, я вважаю, що дійсно і те, .get()і if/ elseтак виконують подібне. Насправді здається, що у випадку if / else є навіть лише один пошук, якщо умова та призначення містять один і той же ключ (порівняйте з останнім випадком, коли є два пошукові запити).

>>>> 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

Для цього можна використовувати функцію лямби в якості однолінійного. Створіть новий об’єкт, до connectionDetails2якого звертається як до функції ...

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

Тепер використовуйте

connectionDetails2(k)

замість

connectionDetails[k]

який повертає значення словника, якщо kвін знаходиться в клавішах, інакше він повертається"DEFAULT"


Я підтримав вас, але проблема з вашим рішенням полягає в тому, що дикт працює з [], але лямбди працюють з ()
yukashima huksay
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.