Приведён фрагмент на Python: "def foo(nums): return [nums.pop() for _ in range(len(nums))]". Объясните, что делает этот код, почему его поведение может быть неожиданным, и предложите безопасную альтернативу
Что делает код - Функция берет список `nums` и выполняет списковое включение `[nums.pop() for _ in range(len(nums))]`. Значит на каждой итерации вызывается `nums.pop()` (удаляет и возвращает последний элемент), а количество итераций равно заранее вычисленному range(len(nums))\text{range}(\text{len}(nums))range(len(nums)). - Итог: возвращается список элементов исходного списка в обратном порядке, а исходный список опустошается. Например, для [1, 2, 3][1,\,2,\,3][1,2,3] результат будет [3, 2, 1][3,\,2,\,1][3,2,1] и `nums` станет [][][]. Почему поведение может быть неожиданным - Код мутирует входной объект (опустошает `nums`), что не явно из внешнего вида функции и может ломать код, который ожидает сохранение списка. - Понятие «итерироваться по `nums`» здесь вводит в заблуждение: итерация управляется заранее вычисленным range(len(nums))\text{range}(\text{len}(nums))range(len(nums)), но сами элементы берутся через побочные эффекты `pop()`. - В многопоточном окружении или при нетипичных реализациях `pop` это может привести к ошибкам (например, `IndexError`), если длина меняется внешне. Безопасные альтернативы (не мутируют входной список) - Быстро и ясно (создает обратную копию, исходный список остаётся): return nums[::-1] - Эквивалентно и явнее: return list(reversed(nums)) - Если нужно опустошить исходный список, но сделать это явно и безопасно: result = nums[::-1] nums.clear() return result Если намеренно хотите собирать, явно показывайте намерение мутации: result = [] while nums: result.append(nums.pop()) return result Любой из первых двух вариантов предпочтительнее, если вы не хотите побочных эффектов.
- Функция берет список `nums` и выполняет списковое включение `[nums.pop() for _ in range(len(nums))]`. Значит на каждой итерации вызывается `nums.pop()` (удаляет и возвращает последний элемент), а количество итераций равно заранее вычисленному range(len(nums))\text{range}(\text{len}(nums))range(len(nums)).
- Итог: возвращается список элементов исходного списка в обратном порядке, а исходный список опустошается. Например, для [1, 2, 3][1,\,2,\,3][1,2,3] результат будет [3, 2, 1][3,\,2,\,1][3,2,1] и `nums` станет [][][].
Почему поведение может быть неожиданным
- Код мутирует входной объект (опустошает `nums`), что не явно из внешнего вида функции и может ломать код, который ожидает сохранение списка.
- Понятие «итерироваться по `nums`» здесь вводит в заблуждение: итерация управляется заранее вычисленным range(len(nums))\text{range}(\text{len}(nums))range(len(nums)), но сами элементы берутся через побочные эффекты `pop()`.
- В многопоточном окружении или при нетипичных реализациях `pop` это может привести к ошибкам (например, `IndexError`), если длина меняется внешне.
Безопасные альтернативы (не мутируют входной список)
- Быстро и ясно (создает обратную копию, исходный список остаётся):
return nums[::-1]
- Эквивалентно и явнее:
return list(reversed(nums))
- Если нужно опустошить исходный список, но сделать это явно и безопасно:
result = nums[::-1]
nums.clear()
return result
Если намеренно хотите собирать, явно показывайте намерение мутации:
result = []
while nums:
result.append(nums.pop())
return result
Любой из первых двух вариантов предпочтительнее, если вы не хотите побочных эффектов.