`если ключ в dict` против` try / Кроме` - какая идиома читаемее? - PullRequest
83 голосов
/ 22 декабря 2010

У меня есть вопрос об идиомах и удобочитаемости, и, похоже, в этом конкретном случае есть конфликт философии Python:

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

Какой путь лучше?

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

или

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

"Делай и проси прощения" против "простота и понятность ".

Что лучше и почему?

Ответы [ 10 ]

62 голосов
/ 22 декабря 2010

Исключения не являются условными.

Условная версия более понятна. Это естественно: это простое управление потоком, для которого предназначены условные обозначения, а не исключения.

Версия исключения в основном используется в качестве оптимизации при выполнении этих поисков в цикле: для некоторых алгоритмов это позволяет исключить тесты из внутренних циклов. Здесь нет такой выгоды. У него есть небольшое преимущество, заключающееся в том, что он избегает необходимости произносить "blah" дважды, но если вы делаете много из них, вам, вероятно, все равно следует иметь вспомогательную функцию move_key.

В общем, я настоятельно рекомендую придерживаться условной версии по умолчанию, если у вас нет особых причин не делать этого. Условные обозначения - это очевидный способ сделать это, что обычно является сильной рекомендацией отдавать предпочтение одному решению другому.

49 голосов
/ 22 декабря 2010

Существует также третий способ, позволяющий избежать как исключений, так и двойного поиска, который может быть важен, если поиск дорогой:

value = B.get("blah", None)
if value is 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 голосов
/ 22 декабря 2010

Насколько я понимаю, вы хотите обновить 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}
>>> 
7 голосов
/ 28 апреля 2012

Прямая цитата из вики по производительности Python:

За исключением первого раза, каждый раз, когда слово просматривается, оператор if проваливается.Если вы считаете большое количество слов, многие из них могут встречаться несколько раз.В ситуации, когда инициализация значения происходит только один раз, а увеличение этого значения будет происходить много раз, дешевле использовать оператор try.

Так что, похоже, оба вариантажизнеспособный в зависимости от ситуации.Для получения более подробной информации вы можете проверить эту ссылку: Try-Кроме производительности

3 голосов
/ 22 декабря 2010

Я думаю, что второй пример - это то, что вы должны использовать, если этот код не имеет смысла:

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

Имейте в виду, что код будет прерван, как только появится ключ, которого нет в B. Если этот код имеет смысл, вам следует использовать метод исключения, в противном случае используйте метод test. По моему мнению, поскольку он короче и четко выражает намерение, его намного легче читать, чем метод исключения.

Конечно, люди, которые говорят вам использовать update, правы. Если вы используете версию Python, которая поддерживает словарь, я бы настоятельно предпочел этот код:

updateset = {'foo', 'bar', 'baz'}
A.update({k: B[k] for k in updateset if k in B})
3 голосов
/ 22 декабря 2010

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

Я думаю, что "попробовать" дешево во времени, но "кроме"дороже.

2 голосов
/ 22 декабря 2010

Правило в других языках - резервировать исключения для исключительных условий, то есть ошибок, которые не возникают при регулярном использовании.Не знаю, как это правило применяется к Python, так как StopIteration не должно существовать по этому правилу.

1 голос
/ 27 апреля 2019

Начиная с 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}
1 голос
/ 22 декабря 2010

Лично я склоняюсь ко второму методу (но использующему has_key):

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

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

Как выясняется (см. комментарии к вашему вопросу), has_key устарела - так что, я думаю, лучше записать как

if "blah" in B:
  A["blah"] = B["blah"]
0 голосов
/ 03 апреля 2018

Почему бы просто не сделать это:

def try_except(x,col):
    try:
        return x[col]
    except:
        return None

list(map(lambda x: try_except(x,'blah'),A))
...