Другое

Как правильно перемешать список в Python без потери данных

Узнайте, почему random.shuffle() возвращает None и как корректно перемешать список в Python. Руководство с примерами, альтернативами и советами по производительности.

Как перемешать список объектов в Python?

Я пытаюсь перемешать список объектов с помощью random.shuffle(), но он возвращает None вместо перемешанного списка. Вот мой код:

python
import random

b = [object(), object()]

print(random.shuffle(b))

Вывод:

None

Как правильно перемешать список объектов в Python, и почему random.shuffle() возвращает None?

random.shuffle() возвращает None, потому что он изменяет список на месте, а не создаёт новый перемешанный список. Чтобы перемешать список объектов, вы должны вызвать random.shuffle() для вашего списка и затем использовать изменённый список напрямую, а не захватывать возвращаемое значение.

Содержание


Понимание операций в месте

Поведение random.shuffle() возвращать None является преднамеренным и соответствует фундаментальному шаблону дизайна Python. В Python операции в месте всегда возвращают None как предупреждение о том, что операция была выполнена над исходным объектом [Stack Overflow].

Когда вы вызываете random.shuffle(b), функция не возвращает новый перемешанный список. Вместо этого она напрямую изменяет исходный список b, переставляя его элементы в случайном порядке. Такой подход экономит память, поскольку не создаёт дубликат списка в памяти.

python
import random

# Исходный список
b = [object(), object(), object()]
print("Before:", b)  # Before: [<object object at 0x...>, <object object at 0x...>, <object object at 0x...>]

# Перемешивание в месте - возвращает None
result = random.shuffle(b)
print("Return value:", result)  # Return value: None
print("After:", b)  # After: [<object object at 0x...>, <object object at 0x...>, <object object at 0x...>] (перемешан)

Этот шаблон сохраняется во многих методах Python, которые изменяют объекты в месте, например:

  • list.sort() (возвращает None)
  • list.append() (возвращает None)
  • dict.update() (возвращает None)

Правильное использование random.shuffle()

Чтобы корректно перемешать список объектов, выполните следующие шаги:

Базовое перемешивание

python
import random

# Создаём список объектов
objects = [object(), object(), object(), object()]

# Перемешиваем список в месте
random.shuffle(objects)

# Используем перемешанный список напрямую
print("Shuffled objects:", objects)

Работа с разными типами объектов

random.shuffle() работает с любой последовательностью объектов, не только с простыми типами данных:

python
import random

# Перемешивание экземпляров пользовательского класса
class Person:
    def __init__(self, name):
        self.name = name
    
    def __repr__(self):
        return f"Person({self.name})"

people = [Person("Alice"), Person("Bob"), Person("Charlie")]
random.shuffle(people)
print("Shuffled people:", people)

# Перемешивание смешанных типов объектов
mixed = [1, "hello", 3.14, [1, 2, 3], object()]
random.shuffle(mixed)
print("Shuffled mixed list:", mixed)

Сохранение исходного порядка

Если вам нужно оставить исходный список неизменным, а работать с его перемешанной копией:

python
import random

original = [object(), object(), object()]
shuffled_copy = original.copy()  # или list(original)
random.shuffle(shuffled_copy)

print("Original:", original)
print("Shuffled:", shuffled_copy)

Альтернативные методы перемешивания

Хотя random.shuffle() является самым распространённым методом, существуют и другие варианты в зависимости от ваших потребностей:

Использование random.sample() для создания нового перемешанного списка

Если вам нужен новый перемешанный список без изменения исходного:

python
import random

original = [object(), object(), object(), object()]
shuffled = random.sample(original, k=len(original))

print("Original:", original)
print("Shuffled:", shuffled)

NumPy shuffle() для больших массивов

Для числовых данных или при работе с массивами NumPy:

python
import numpy as np

# Для обычных списков
data = [1, 2, 3, 4, 5]
np.random.shuffle(data)
print("NumPy shuffled:", data)

# Для массивов NumPy
array_data = np.array([1, 2, 3, 4, 5])
np.random.shuffle(array_data)
print("NumPy array shuffled:", array_data)

Реализация алгоритма Фишера-Йейтса

Для образовательных целей или когда нужна большая гибкость:

python
import random

def fisher_yates_shuffle(arr):
    """Современная версия алгоритма Фишера-Йейтса"""
    for i in range(len(arr) - 1, 0, -1):
        j = random.randint(0, i)
        arr[i], arr[j] = arr[j], arr[i]

# Использование
my_list = [1, 2, 3, 4, 5]
fisher_yates_shuffle(my_list)
print("Fisher-Yates shuffled:", my_list)

Распространённые ошибки и решения

Ошибка 1: Захват возвращаемого значения

python
# Неправильно
shuffled = random.shuffle(my_list)
print(shuffled)  # Это печатает None

Решение: Используйте исходный список после его перемешивания:

python
# Правильно
random.shuffle(my_list)
shuffled = my_list  # Теперь shuffled содержит перемешанный список

Ошибка 2: Предположение о создании нового списка

python
# Неправильно - ожидается новый список
original = [1, 2, 3]
shuffled = random.shuffle(original)  # shuffled равно None

Решение: Сначала создайте копию, если нужно сохранить оригинал:

python
# Правильно
original = [1, 2, 3]
shuffled_copy = original.copy()
random.shuffle(shuffled_copy)

Ошибка 3: Использование shuffle с неизменяемыми последовательностями

python
# Неправильно - кортежи неизменяемы
my_tuple = (1, 2, 3)
random.shuffle(my_tuple)  # TypeError: 'tuple' object does not support item assignment

Решение: Сначала преобразуйте в список:

python
# Правильно
my_tuple = (1, 2, 3)
my_list = list(my_tuple)
random.shuffle(my_list)

Проблемы производительности

Временная сложность

random.shuffle() использует алгоритм Фишера-Йейтса (также известный как шутник Кнута), который имеет:

  • Временную сложность: O(n) — линейное время
  • Память: O(1) — постоянное место (в месте)

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

Эффективность памяти

Поскольку random.shuffle() работает в месте, он более экономичен по памяти, чем методы, создающие новые списки:

python
import random
import sys

# Большой список
large_list = list(range(1000000))

# Перемешивание в месте (экономия памяти)
random.shuffle(large_list)
print("Memory after in-place shuffle:", sys.getsizeof(large_list))

# sample создаёт новый список (потребляет больше памяти)
sampled = random.sample(large_list, k=len(large_list))
print("Memory after sample:", sys.getsizeof(sampled))

Потокобезопасность

random.shuffle() не потокобезопасен. Если вы работаете в многопоточной среде, рассмотрите использование random.SystemRandom() или numpy.random.shuffle(), которые могут иметь лучшую потокобезопасность:

python
import random

# Потокобезопасная версия с SystemRandom
system_random = random.SystemRandom()
system_random.shuffle(my_list)

Заключение

Кратко о ключевых моментах перемешивания списков в Python:

  1. random.shuffle() возвращает None по дизайну из‑за того, что он изменяет исходный список в месте, а не создаёт новый перемешанный список.
  2. Правильное использование — вызвать random.shuffle() для вашего списка и затем использовать изменённый список напрямую, а не захватывать возвращаемое значение.
  3. Для сохранения исходного порядка создайте копию списка перед перемешиванием.
  4. Альтернативные методы как random.sample() или функция shuffle из NumPy могут быть более подходящими для конкретных случаев.
  5. Проблемы производительности в пользу random.shuffle() благодаря O(n) времени и O(1) памяти.

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

Источники

  1. Python - Why does random.shuffle return None? - Stack Overflow
  2. numpy.random.shuffle — NumPy v2.3 Manual
  3. Inplace shuffle function returns none - Google Groups
  4. Python - getting none on return in the function? random.shuffle(list) - Stack Overflow
  5. Python - Random shuffle list becomes None - Stack Overflow
  6. Python - Shuffling a list of objects - Stack Overflow
Авторы
Проверено модерацией
Модерация