"если ключ в dict" против "попробуй/кроме" - что является более читабельной идиомой?

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

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

Какой способ лучше?

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

или

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

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

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

Ответ 1

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

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

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

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

Ответ 2

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

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)

Ответ 3

Из того, что я понимаю, вы хотите обновить 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}
>>> 

Ответ 4

Прямая цитата из вики-сайта Python:

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

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

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

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

Ответ 8

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

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

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

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

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

Ответ 9

Начиная с 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}

Ответ 10

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

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

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

Ответ 11

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

Проще просить прощения, чем разрешения. (ЭСПЦ)

См. ссылку на документацию по Python здесь.

Кроме того, этот блог от Бретта, одного из основных разработчиков, кратко затрагивает большую часть этого.

Смотрите еще одно обсуждение здесь: