Использование временной переменной против многократного чтения того же ключа / значения из словаря

1

Предыстория: мне нужно прочитать один и тот же ключ/значение из словаря (точно) дважды.

Вопрос: Есть два способа, как показано ниже,

Способ 1. Прочитайте его одним и тем же ключом дважды, например,

sample_map = {'A':1,}
...
if sample_map.get('A', None) is not None:
    print("A value in map is {}".format(sample_map.get('A')))

Способ 2. Прочитайте его один раз и сохраните в локальной переменной, например,

sample_map = {'A':1,}
...
ret_val = sample.get('A', None)
if ret_val is not None:
    print("A value in map is {}".format(ret_val))

Какой способ лучше? Каковы их плюсы и минусы?

Обратите внимание, что я знаю, что print() может обрабатывать ret_val из None. Это гипотетический пример, и я просто использую его для иллюстрации.

  • 0
    Кстати, пример в порядке. Конечно, print может обрабатывать None , но он обрабатывает это, говоря, A's value in map is None , что не соответствует действительности. Поэтому такая проверка имеет смысл.
  • 0
    Спасибо всем вам за ваш вклад. Этот пример на самом деле для иллюстрации, поэтому то, какое значение отображается, менее важно :). Я ищу комментарии больше с точки зрения производительности и читабельности. Большое спасибо за ваши комментарии снова.
Теги:
performance
dictionary
coding-style
anti-patterns
code-duplication

3 ответа

1

В этих условиях я бы тоже не использовал. Что вас действительно интересует, является ли A действительным ключом, а KeyError (или его отсутствие), поднятый __getitem__, скажет вам, есть оно или нет.

try:
    print("A value in map is {}".format(sample['A'])
except KeyError:
    pass

Или, конечно, кто-то сказал бы, что в блоке try слишком много кода, и в этом случае предпочтительным будет метод 2.

try:
    ret_val = sample['A']
except KeyError:
    pass
else:
    print("A value in map is {}".format(ret_val))

или код, который у вас уже есть:

ret_val = sample.get('A')  # None is the default value for the second argument
if ret_val is not None:
    print("A value in map is {}".format(ret_val))
0

Нет никакой эффективной разницы между опциями, которые вы опубликовали.

Python: List vs Dict для поиска таблицы

Поиск в дикторе примерно о (1). То же самое касается переменной, которую вы сохранили.

Эффективность примерно такая же. В этом случае я бы пропустил определение дополнительной переменной, так как не так много происходит.

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

# At this point, assuming that these are floats is OK, since no thresholds had text values
                if vname in paramRanges:
                    """
                    Making sure the variable is one that we have a threshold for
                    """
                    # We might care about it
                    # Don't check the equal case, because that won't matter
                    if float(tblChanges[vname][0]) < float(tblChanges[vname][1]):

                        # Check lower tolerance
                        # Distinction is important because tolerances are not always the same +/-
                        if abs(float(tblChanges[vname][0]) - float(tblChanges[vname][1])) >= float(
                                paramRanges[vname][2]):
                            # Difference from default is greater than tolerance
                            # vname : current value, default value, negative tolerance, tolerance units, change date
                            alerts[vname] = (
                                float(tblChanges[vname][0]), float(tblChanges[vname][1]), float(paramRanges[vname][2]),
                                paramRanges[vname][0], tblChanges[vname][2]
                            )
                        if abs(float(tblChanges[vname][0]) - float(tblChanges[vname][1])) >= float(
                                paramRanges[vname][1]):
                            alerts[vname] = (
                                float(tblChanges[vname][0]), float(tblChanges[vname][1]), float(paramRanges[vname][1]),
                                paramRanges[vname][0], tblChanges[vname][2]
                            )
0

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


Единственное реальное преимущество повторения get - сохранение инструкции присваивания.

Если ваш код не переполнен в середине сложного выражения, это просто означает сохранение одной строки вертикального пространства, что не является ничем, но не является огромной сделкой.

Если ваш код забит в середине сложного выражения, вытягивать get из может заставить вас переписать вещи немного. Вы, возможно, придется, например, превратить lambda в def, или повернуть while цикл с простым условием в while True: с, if …: break. Обычно это признак того, что вы, например, действительно хотели def в первую очередь, но "обычно" не "всегда". Итак, здесь вы можете нарушить правило большого пальца, но сначала посмотрите на раздел внизу.


На другой стороне…

Для dict.get стоимость исполнения повторения метода довольно мала и вряд ли повлияет на ваш код. Но что, если вы измените код, чтобы взять произвольный объект отображение от вызывающего абонента, а кто - то передает вам, скажем, прокси - сервер, который делает get, сделав запрос к базе данных или RPC на удаленном сервере?

Для однопоточного кода вызов dict.get с теми же аргументами дважды подряд, не делая ничего между ними, является правильным. Но что, если вы принимаете dict, переданный вызывающим, а вызывающий имеет фоновый поток, также изменяющий один и тот же dict? Тогда ваш код будет прав, если вы поместите Lock или другую синхронизацию вокруг двух обращений.

Или, что, если ваше выражение было чем-то, что может повлиять на какое-то состояние или сделать что-то опасное?

Даже если ничего подобного не будет проблемой в вашем коде, если этот факт не станет очевидным для всех, кто читает ваш код, им все равно придется думать о возможности затрат на производительность и гонок ToCToU и так далее.

И, конечно же, это делает по крайней мере две из ваших линий дольше. Предполагая, что вы пытаетесь написать читаемый код, который придерживается 72 или 79 или 99 столбцов, горизонтальное пространство - это дефицитный ресурс, а вертикальное пространство - гораздо меньше. Я думаю, что ваша вторая версия легче сканировать, чем ваша первая, даже без всех этих других соображений, но представьте, что вы сделаете выражение, скажем, более 20 символов.


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

К сожалению, до Python 3.7 вы обычно не можете. Это либо неуклюжий (например, требующий дополнительного вложенного понимания, либо лямбда только для того, чтобы дать вам возможность привязать переменную) или невозможно.

Но в Python 3.8 выражения назначения PEP 572 обрабатывают этот случай.

if (sample := sample_map.get('A', None)) is not None:
    print("A value in map is {}".format(sample))

Я не думаю, что это большое использование выражения присваивания (см. PEP для некоторых лучших примеров), тем более, что я, вероятно, напишу так, как предложил chepner... но он показывает, как получить лучшее из обоих миров ( назначая временное и встраиваемое в выражение), когда вам действительно нужно.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню