`if key in dict` проти` try / Osim` - яка читабельніша ідіома?


93

У мене питання про ідіоми та читабельність, і, здається, у цьому конкретному випадку існує сутичка філософії Python:

Я хочу побудувати словник A зі словника B. Якщо певний ключ не існує в B, тоді нічого не робіть і продовжуйте далі.

Який шлях кращий?

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

або

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

"Зробити і попросити прощення" проти "простоти та явності".

Що краще і чому?


1
Другий приклад можна краще написати як if "blah" in B.keys(), або if B.has_key("blah").
girasquid

2
це A.update(B)не працює для вас?
SilentGhost

21
@Luke: has_keyприпинено на користь inта перевіряє B.keys()зміну операції O (1) на O (n).
kindall

4
@Luke: не це не так. .has_keyзастаріло і keysстворює непотрібний список у py2k, а зайвий у py3k
SilentGhost

2
'build' A, як і в, A порожній для початку? І ми хочемо лише певні ключі? Використовуйте розуміння: A = dict((k, v) for (k, v) in B if we_want_to_include(k)).
Karl Knechtel

Відповіді:


76

Винятки не є умовними.

Умовна версія зрозуміліша. Це природно: це прямолінійне регулювання потоку, для чого розроблені умовні роботи, а не винятки.

Версія винятку в основному використовується як оптимізація при виконанні цих пошуків у циклі: для деяких алгоритмів це дозволяє виключити тести з внутрішніх циклів. Тут він не має такої вигоди. Вона має ту невелику перевагу, що дозволяє уникнути необхідності повторювати "blah"двічі, але якщо ви робите багато з них, ви, мабуть, move_keyвсе одно маєте допоміжну функцію.

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


3
Я не згоден. Якщо ви кажете "робити X, а якщо це не працює, робіть Y". Основною причиною проти умовного рішення тут є те, що вам доводиться писати "blah"частіше, що призводить до більш схильної до помилок ситуації.
glglgl

6
І, особливо в Python, EAFP дуже широко використовується.
glglgl

8
Ця відповідь була б правильною для будь-якої мови, яку я знаю, крім Python.
Томаш Зато - відновити Моніку

3
Якщо ви використовуєте винятки, ніби вони є умовними умовами в Python, я сподіваюся, що ніхто інший не повинен їх читати.
Гленн Мейнард,

Отже, який остаточний вирок? :)
floatingpurr

60

Існує також третій спосіб, який дозволяє уникнути як винятків, так і подвійного пошуку, що може бути важливим, якщо пошук дорогий:

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

Якщо ви очікуєте, що словник міститиме Noneзначення, ви можете використати ще езотеричні константи, наприклад NotImplemented, Ellipsisабо створити нову:

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

У будь-якому випадку, використання update()є для мене найбільш читабельним варіантом:

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

14

З мого розуміння, ви хочете оновити dict A ключовими парами значень з dict B

update є кращим вибором.

A.update(B)

Приклад:

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

"Якщо конкретний ключ не існує в B" Вибачте, мав би бути більш зрозумілим, але я хочу скопіювати значення, лише якщо конкретні ключі в B існують. Не всі в Б.
LeeMobile

1
@LeeMobile -A.update({k: v for k, v in B.iteritems() if k in specificset})
Всезначний

8

Пряма цитата з вікі продуктивності Python:

За винятком вперше, кожного разу, коли слово бачиться, якщо тест тесту не вдається. Якщо ви рахуєте велику кількість слів, багато з них, ймовірно, траплятимуться кілька разів. У ситуації, коли ініціалізація значення відбуватиметься лише один раз, а збільшення цього значення відбуватиметься багато разів, дешевше використовувати оператор try.

Отже, здається, що обидва варіанти життєздатні залежно від ситуації. Щоб отримати докладнішу інформацію, ви можете переглянути це посилання: Спробуй-крім-продуктивності


це цікаве читання, але я думаю дещо неповне. Використовуваний дикт містить лише 1 елемент, і я підозрюю, що більші дикти матимуть значний вплив на продуктивність
user2682863

3

Я думаю, загальне правило тут, як A["blah"]правило, буде існувати, якщо так, спробуйте, за винятком, це добре, якщо ні, то використовуйтеif "blah" in b:

Я думаю, що "спробувати" дешево в часі, але "крім" дорожче.


10
Не підходите до коду з точки зору оптимізації за замовчуванням; підходити до цього з точки зору читабельності та ремонтопридатності. Якщо метою не є конкретно оптимізація, це неправильні критерії (і якщо це оптимізація, то відповідь - це порівняльний аналіз, а не здогадування).
Гленн Мейнард,

Ймовірно, мені слід було поставити останню точку в дужках або якось неясніше - моєю основною темою було перше, і я думаю, що це має додаткову перевагу другого.
Ніл

3

Я думаю, другий приклад - це те, на що вам слід піти, якщо цей код не має сенсу:

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

Майте на увазі, що код перерветься, як тільки з’явиться ключ, якого немає B. Якщо цей код має сенс, то вам слід скористатися методом винятку, інакше - методом тесту. На мій погляд, оскільки він коротший і чітко виражає намір, його набагато легше читати, ніж метод винятку.

Звичайно, люди, які кажуть вам використовувати, updateє правильними. Якщо ви використовуєте версію Python, яка підтримує розуміння словника, я б настійно віддав перевагу такому коду:

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

"Майте на увазі, що код перерветься, як тільки буде ключ, якого немає в B." - ось чому найкраще застосовувати лише абсолютний мінімум у блоці try: block, як правило, це один рядок. Перший приклад був би кращим як частина циклу, наприкладfor key in ["foo", "bar", "baz"]: try: A[key] = B[key]
Зім

2

Правилом інших мов є резервування винятків для виняткових умов, тобто помилок, які не трапляються під час регулярного використання. Не знаю, як це правило застосовується до Python, оскільки StopIteration не повинно існувати за цим правилом.


Я думаю, що цей каштан походить з мов, де обробка винятків є дорогою, і тому може мати значний вплив на продуктивність. Я ніколи не бачив жодного реального виправдання чи міркувань за цим.
John La Rooy

@JohnLaRooy Ні, продуктивність насправді не причина. Винятки - це різновид нелокального goto , який, на думку деяких людей, перешкоджає читаності коду. Однак використання винятків таким чином вважається ідіоматичним у Python, тому вищезазначене не застосовується.
Ян Голдбі,

умовні повернення також є "нелокальними goto", і багато людей віддають перевагу цьому стилю замість перевірки сторожових в кінці блоку коду.
cowbert

1

Особисто я схиляюся до другого методу (але з використанням has_key):

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

Таким чином, кожна операція присвоєння складає лише два рядки (замість 4 із спробою / винятком), і будь-які винятки, які виникають, будуть справжніми помилками або речами, які ви пропустили (замість того, щоб просто намагатися отримати доступ до ключів, яких немає) .

Як виявляється (див. Коментарі до вашого запитання), has_keyзастарілий - тому, мабуть, краще писати як

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

1

Починаючи Python 3.8та вводячи вирази присвоєння (PEP 572) ( :=оператор), ми можемо захопити значення умови dictB.get('hello', None)у змінну value, щоб обидва перевірити, чи не є воно None(як dict.get('hello', None)повертає або пов'язане значення, або None), а потім використовувати його в тілі умова:

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

Це не вдається, якщо значення == 0
Ерік,

0

Хоча акцент на прийнятій відповіді на принципі "дивись перед тим, як стрибнути" може застосовуватися до більшості мов, першим підходом може бути більш пітонічний, заснований на принципах пітона. Не кажучи вже про те, що це законний стиль кодування в python. Важливо, переконайтесь, що ви використовуєте спробу, окрім блоку, у правильному контексті та дотримуєтесь найкращих практик. Напр. робити занадто багато речей у блоці try, вловлювати дуже широкий виняток, або ще гірше - оголене за винятком застереження тощо.

Простіше просити прощення, ніж дозволу. (EAFP)

Див. Посилання на документи python тут .

Крім того, цей блог від Brett, одного з основних розробників, торкається більшої частини цього стисло.

Дивіться ще SO обговорення тут :

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