Max ([x for x in something]) vs max (x для x в чем-то): почему существует разница и что это такое?

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

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

Оказалось, что это что-то вроде этого (EDIT: точный код ниже):

# my version:
max_q = max([x for x in self.getQValues(state)])

# reference version which worked:
max_q = max(x for x in self.getQValues(state))

Теперь это меня озадачило. Я попробовал несколько экспериментов с интерпретатором Python (2.7), выполнив тесты с использованием max в списках с квадратными скобками и без них. Результаты оказались точно такими же.

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

Что здесь происходит? Почему мой код производит разные результаты, чем код ссылки (в версии 2.7)? Как переход в понимании без скобок отличается от перехода в понимании с помощью скобок?

EDIT 2: точный код:

# works
max_q = max(self.getQValue(nextState, action) for action in legal_actions)

# doesn't work (i.e., provides different results)
max_q = max([self.getQValue(nextState, action) for action in legal_actions])

Я не думаю, что это должно быть помечено как дублирующее - да, другой вопрос касается разницы между объектами понимания и объектами списка, но не почему max() будет давать разные результаты при задании "некоторого списка, построенного по X понимание, а не только" понимание Х".

Ответы

Ответ 1

Вы пропускаете локальную переменную, которая влияет на более поздний код?

# works
action = 'something important'
max_q = max(self.getQValue(nextState, action) for action in legal_actions)
assert action == 'something important'

# doesn't work (i.e., provides different results)
max_q = max([self.getQValue(nextState, action) for action in legal_actions])
assert action == 'something important'  # fails!

Понятия генератора и словаря создают новую область видимости, но до py3, понимания списков нет, для обратной совместимости

Легкий способ тестирования - измените свой код на:

max_q = max([self.getQValue(nextState, action) for action in legal_actions])
max_q = max(self.getQValue(nextState, action) for action in legal_actions)

Предполагая, что self.getQValue является чистым, то единственным прочным побочным эффектом первой строки будет беспорядок с локальными переменными. Если это сломает его, то причина вашей проблемы.

Ответ 2

Использование [] вокруг понимания списка фактически генерирует список в вашу переменную или в этом случае в вашу функцию max. Без скобок вы создаете объект generator, который будет передан в функцию max.

results1 = (x for x in range(10))
results2 = [x for x in range(10)]
result3 = max(x for x in range(10))
result4 = max([x for x in range(10)])
print(type(results1)) # <class 'generator'>
print(type(results2)) # <class 'list'>
print(result3) # 9
print(result4) # 9

Насколько я знаю, они должны работать по существу одинаково в пределах функции max.

Ответ 3

Я не знаю, почему у вас разные значения в вашем проекте, но я могу привести вам живой пример, когда это произойдет. Генератор более эффективен, чем список, поэтому у нас будет другое использование памяти. Я использую Python 3.

Здесь функция, которая возвращает текущее использование памяти Python:

import os
import psutil


def memory_usage():
    """Get process virtual memory (vms) usage in MB."""
    process = psutil.Process(os.getpid())
    memory = process.memory_info()[1] / (1024.0 * 1024.0)
    return memory

Попробуйте этот код:

# Generator version:
max_q = max(memory_usage() for i in range(100000))
print(max_q)  # 7.03125

Я тестировал код несколько раз, и на моей машине я получаю что-то более 7.

Замените версию генератора версией списка:

# List version:
max_q = max([memory_usage() for i in range(100000)])
print(max_q)  # 11.44921875

Я получаю что-то более 11 на моей машине.

Как вы видите, код почти такой же, но вы получите другой результат.

Может быть в вашем проекте getQValue() дает вам разные значения на основе уже рассчитанных. Но эти существующие значения могут быть удалены сборщиком мусора быстрее, если вы используете генератор.