34. Выражения comprehension: компактный способ создавать списки, словари и множества
Выражения comprehension — одна из самых элегантных возможностей Python, позволяющая создавать и преобразовывать коллекции в одной читаемой строке кода. Вместо написания нескольких строк с циклами и операциями append выражения comprehension позволяют выразить ту же логику более кратко и часто более ясно.
В этой главе мы рассмотрим, как использовать list comprehension, dictionary comprehension и set comprehension, чтобы писать более питоничный код. Мы увидим, как включать условную логику, когда выбирать comprehension вместо традиционных циклов, и как обрабатывать более сложные сценарии со вложенными итерациями.
34.1) List comprehension для создания и преобразования списков
34.1.1) Базовый синтаксис list comprehension
List comprehension предоставляет компактный способ создать новый список, применяя выражение к каждому элементу существующей последовательности. Базовый синтаксис:
[expression for item in iterable]Это создаёт новый список, где каждый элемент — результат вычисления expression для каждого item в iterable (любой последовательности, по которой можно пройти циклом, например список, range или строка).
Начнём с простого примера. Предположим, мы хотим создать список квадратов чисел от 0 до 4:
# Традиционный подход с циклом
squares = []
for number in range(5):
squares.append(number ** 2)
print(squares) # Output: [0, 1, 4, 9, 16]С list comprehension мы можем выразить это более кратко:
# Используем list comprehension
squares = [number ** 2 for number in range(5)]
print(squares) # Output: [0, 1, 4, 9, 16]Оба подхода дают одинаковый результат, но comprehension компактнее и, когда вы освоитесь с синтаксисом, часто легче читается. Comprehension ясно показывает, что мы создаём список значений, возведённых в квадрат.
34.1.2) Преобразование существующих данных
List comprehension отлично подходит для преобразования данных из одной формы в другую. Рассмотрим несколько практических примеров.
Преобразование температур из градусов Цельсия в градусы Фаренгейта:
# Данные температуры в градусах Цельсия
celsius_temps = [0, 10, 20, 30, 40]
# Преобразуем в градусы Фаренгейта по формуле: F = C * 9/5 + 32
fahrenheit_temps = [temp * 9/5 + 32 for temp in celsius_temps]
print(fahrenheit_temps) # Output: [32.0, 50.0, 68.0, 86.0, 104.0]Преобразование строк в верхний регистр:
# Коды товаров в смешанном регистре
product_codes = ["abc123", "def456", "ghi789"]
# Приводим к верхнему регистру
uppercase_codes = [code.upper() for code in product_codes]
print(uppercase_codes) # Output: ['ABC123', 'DEF456', 'GHI789']34.1.3) Создание списков из объектов range
List comprehension естественно работает с range(), о котором мы узнали в главе 12. Это полезно для генерации последовательностей со специфическими шаблонами:
# Генерируем чётные числа от 0 до 10
evens = [n * 2 for n in range(6)] # n идёт от 0 до 5, поэтому n*2 даёт 0, 2, 4, 6, 8, 10
print(evens) # Output: [0, 2, 4, 6, 8, 10]
# Генерируем кратные 5
multiples_of_five = [n * 5 for n in range(1, 6)]
print(multiples_of_five) # Output: [5, 10, 15, 20, 25]34.1.4) Comprehension vs построение списков с append
Важно понимать, что list comprehension создаёт весь список одной операцией, тогда как традиционный подход с циклом наращивает список постепенно. Оба способа дают одинаковый результат, но comprehension обычно быстрее для создания новых списков и считается более Pythonic.
Вот сравнение бок о бок:
# Традиционный подход с циклом
result = []
for i in range(5):
result.append(i * 3)
print(result) # Output: [0, 3, 6, 9, 12]
# Подход с list comprehension
result = [i * 3 for i in range(5)]
print(result) # Output: [0, 3, 6, 9, 12]Оба подхода корректны, но comprehension более лаконичен и ясно выражает намерение: «создать список значений, где каждое значение равно i * 3».
34.2) Условная логика внутри list comprehension
34.2.1) Фильтрация с условиями if
Одна из самых мощных возможностей list comprehension — способность фильтровать элементы на основе условия. Можно добавить часть if в конце comprehension, чтобы включать только те элементы, которые удовлетворяют определённым критериям:
[expression for item in iterable if condition]Часть if работает как фильтр: Python вычисляет условие для каждого элемента, и только элементы, для которых условие равно True, будут включены в результирующий список. Элементы, которые не удовлетворяют условию, полностью пропускаются.
Посмотрим на это в действии на простом примере:
# Получаем только чётные числа от 0 до 9
numbers = range(10)
evens = [n for n in numbers if n % 2 == 0]
print(evens) # Output: [0, 2, 4, 6, 8]Здесь n % 2 == 0 проверяет, является ли число чётным. В новый список включаются только числа, прошедшие эту проверку.
Фильтрация оценок студентов:
# Результаты теста студентов
scores = [45, 78, 92, 65, 88, 55, 73, 95]
# Берём только проходные баллы (>= 70)
passing_scores = [score for score in scores if score >= 70]
print(passing_scores) # Output: [78, 92, 88, 73, 95]34.2.2) Преобразование отфильтрованных элементов
Вы можете сочетать фильтрацию с преобразованием, применяя выражение к отфильтрованным элементам:
# Оценки студентов
scores = [45, 78, 92, 65, 88, 55, 73, 95]
# Берём проходные баллы и масштабируем их в диапазон 0–10
scaled_passing = [score / 10 for score in scores if score >= 70]
print(scaled_passing) # Output: [7.8, 9.2, 8.8, 7.3, 9.5]
# Сначала фильтрует (оставляет только >= 70), затем преобразует (делит на 10)Преобразование и фильтрация строк:
# Названия продуктов со смешанным качеством
products = ["apple", "BANANA", "cherry", "DATE", "elderberry"]
# Берём версии в верхнем регистре для продуктов с именами длиннее 5 символов
long_products_upper = [product.upper() for product in products if len(product) > 5]
print(long_products_upper) # Output: ['BANANA', 'CHERRY', 'ELDERBERRY']34.2.3) Использование условных выражений (if-else) в comprehension
Иногда вы хотите по-разному преобразовывать элементы в зависимости от условия, а не отфильтровывать их. Для этого используйте условное выражение(conditional expression) (которое мы изучали в главе 10) в части выражения comprehension:
[expression_if_true if condition else expression_if_false for item in iterable]Это отличается от фильтрации. Здесь каждый элемент включается в результат — if-else определяет, какое выражение применить к каждому элементу. Условное выражение (из главы 10) находится в части выражения, до части for.
Обратите внимание на различие синтаксиса:
- Фильтрация:
[expr for item in seq if condition]—ifв конце, безelse - Условное выражение:
[expr_if if cond else expr_else for item in seq]—if-elseв выражении, доfor
# Классифицируем числа как чётные или нечётные
numbers = range(6)
classifications = ["even" if n % 2 == 0 else "odd" for n in numbers]
print(classifications) # Output: ['even', 'odd', 'even', 'odd', 'even', 'odd']Применение разных преобразований в зависимости от условий:
# Оценки студентов
scores = [45, 78, 92, 65, 88, 55, 73, 95]
# Добавляем бонусные баллы к непроходным оценкам, проходные оставляем как есть
adjusted_scores = [score + 10 if score < 70 else score for score in scores]
print(adjusted_scores) # Output: [55, 78, 92, 75, 88, 65, 73, 95]В обоих примерах обратите внимание:
- Каждый элемент из исходного списка присутствует в результате
if-elseопределяет, во что преобразуется каждый элемент- Никакие элементы не отфильтровываются
34.2.4) Понимание различия: фильтрация vs условное выражение
Крайне важно понимать разницу между этими двумя шаблонами:
Фильтрация (if в конце) — некоторые элементы исключаются:
# Включаем только положительные числа
numbers = [-2, 5, -1, 8, 0, 3]
positives = [n for n in numbers if n > 0]
print(positives) # Output: [5, 8, 3]
print(len(positives)) # Output: 3 (only 3 items)
# Процесс: Проверить условие → Если True, включить элемент → Если False, пропустить элементУсловное выражение (if-else в выражении) — все элементы включаются, но по-разному преобразуются:
# Преобразуем отрицательные числа в ноль, положительные оставляем
numbers = [-2, 5, -1, 8, 0, 3]
non_negatives = [n if n > 0 else 0 for n in numbers]
print(non_negatives) # Output: [0, 5, 0, 8, 0, 3]
print(len(non_negatives)) # Output: 6 (all 6 items)
# Процесс: проверить условие → если True, использовать первое выражение → если False, использовать второе выражение → результат всегда включается34.3) Dictionary comprehension
34.3.1) Базовый синтаксис dictionary comprehension
Так же как list comprehension создаёт списки, dictionary comprehension создаёт словари(dictionary). Синтаксис похож, но вы задаёте и ключ, и значение:
{key_expression: value_expression for item in iterable}Это создаёт новый словарь, где каждая пара ключ-значение генерируется из iterable.
Начнём с простого примера, который создаёт словарь, сопоставляющий числа с их квадратами:
# Создаём словарь чисел и их квадратов
squares_dict = {n: n ** 2 for n in range(5)}
print(squares_dict) # Output: {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}Создание словаря из двух списков:
# Имена студентов и их оценки
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
# Создаём словарь, сопоставляющий имена и оценки
student_scores = {names[i]: scores[i] for i in range(len(names))}
print(student_scores) # Output: {'Alice': 85, 'Bob': 92, 'Charlie': 78}Более элегантный способ объединить две последовательности — использовать zip(), о котором мы узнаем в главе 37. Пока что подход на основе индексов работает хорошо.
34.3.2) Преобразование существующих словарей
Dictionary comprehension отлично подходит для преобразования существующих словарей. Вы можете модифицировать ключи, значения или и то и другое.
При итерации по словарю в comprehension используйте .items(), чтобы получить доступ и к ключам, и к значениям. Метод .items() возвращает пары ключ-значение, которые можно распаковать в части for:
# Исходные цены в долларах
prices = {"apple": 1.50, "banana": 0.75, "cherry": 2.00}
# Переводим в центы (умножаем на 100)
prices_in_cents = {fruit: price * 100 for fruit, price in prices.items()}
print(prices_in_cents) # Output: {'apple': 150.0, 'banana': 75.0, 'cherry': 200.0}Преобразование ключей:
# Коды товаров в нижнем регистре
codes = {"abc": 100, "def": 200, "ghi": 300}
# Переводим ключи в верхний регистр
uppercase_codes = {code.upper(): quantity for code, quantity in codes.items()}
print(uppercase_codes) # Output: {'ABC': 100, 'DEF': 200, 'GHI': 300}Преобразование и ключей, и значений:
# Имена студентов и оценки
scores = {"alice": 85, "bob": 92, "charlie": 78}
# Делаем имена с заглавной буквы и масштабируем оценки в диапазон 0–10
formatted_scores = {name.capitalize(): score / 10 for name, score in scores.items()}
print(formatted_scores) # Output: {'Alice': 8.5, 'Bob': 9.2, 'Charlie': 7.8}34.3.3) Фильтрация элементов словаря
Как и list comprehension, dictionary comprehension может включать условия для фильтрации элементов:
# Оценки студентов
scores = {"Alice": 85, "Bob": 65, "Charlie": 92, "David": 55, "Eve": 78}
# Берём только проходные баллы (>= 70)
passing_scores = {name: score for name, score in scores.items() if score >= 70}
print(passing_scores) # Output: {'Alice': 85, 'Charlie': 92, 'Eve': 78}Фильтрация по характеристикам ключа:
# Складские остатки товаров
inventory = {"apple": 50, "banana": 30, "apricot": 20, "cherry": 40}
# Берём только товары, начинающиеся на 'a'
a_products = {product: quantity for product, quantity in inventory.items()
if product.startswith('a')}
print(a_products) # Output: {'apple': 50, 'apricot': 20}34.3.4) Создание словарей из последовательностей
Dictionary comprehension полезен для создания справочных словарей из последовательностей:
# Список слов
words = ["python", "java", "ruby", "javascript"]
# Создаём словарь, сопоставляющий каждое слово с его длиной
word_lengths = {word: len(word) for word in words}
print(word_lengths) # Output: {'python': 6, 'java': 4, 'ruby': 4, 'javascript': 10}34.3.5) Использование условных выражений в dictionary comprehension
Вы можете использовать условные выражения, чтобы вычислять значения по-разному в зависимости от условий:
# Оценки студентов
scores = {"Alice": 85, "Bob": 65, "Charlie": 92, "David": 55}
# Добавляем статус "Pass" или "Fail"
scores_with_status = {name: "Pass" if score >= 70 else "Fail"
for name, score in scores.items()}
print(scores_with_status) # Output: {'Alice': 'Pass', 'Bob': 'Fail', 'Charlie': 'Pass', 'David': 'Fail'}Применение разных преобразований:
# Цены на продукты
prices = {"apple": 1.50, "banana": 0.75, "cherry": 2.50}
# Применяем скидку к дорогим товарам (> $2.00)
discounted_prices = {product: price * 0.9 if price > 2.00 else price
for product, price in prices.items()}
print(discounted_prices) # Output: {'apple': 1.5, 'banana': 0.75, 'cherry': 2.25}34.4) Set comprehension
34.4.1) Базовый синтаксис set comprehension
Set comprehension создаёт множества(set) с использованием синтаксиса, похожего на list comprehension, но с фигурными скобками:
{expression for item in iterable}Результат — множество, а значит дубликаты автоматически удаляются, и порядок не гарантируется.
# Создаём множество квадратов
squares_set = {n ** 2 for n in range(6)}
print(squares_set) # Output: {0, 1, 4, 9, 16, 25}Ключевое отличие от list comprehension в том, что множества автоматически устраняют дубликаты:
# List comprehension — сохраняет дубликаты
numbers = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
squared_list = [n ** 2 for n in numbers]
print(squared_list) # Output: [1, 4, 4, 9, 9, 9, 16, 16, 16, 16]
# Set comprehension — удаляет дубликаты
squared_set = {n ** 2 for n in numbers}
print(squared_set) # Output: {16, 1, 4, 9} (order may vary)Обратите внимание, что порядок вывода множества может отличаться от показанного здесь. Множества — неупорядоченные коллекции, поэтому Python может отображать элементы в любом порядке.
34.4.2) Извлечение уникальных значений
Set comprehension идеально подходит, когда нужно извлечь уникальные значения из коллекции:
# Ответы студентов (с дубликатами)
responses = ["yes", "no", "yes", "maybe", "no", "yes", "maybe"]
# Получаем уникальные ответы
unique_responses = {response for response in responses}
print(unique_responses) # Output: {'maybe', 'yes', 'no'}Извлечение уникальных символов из строк:
# Текст с повторяющимися символами
text = "mississippi"
# Получаем уникальные символы
unique_chars = {char for char in text}
print(unique_chars) # Output: {'m', 'i', 's', 'p'}34.4.3) Преобразование и фильтрация с set comprehension
Как и другие comprehension, set comprehension может включать преобразования и условия:
# Имена студентов
names = ["Alice", "bob", "CHARLIE", "david", "EVE"]
# Получаем уникальные первые буквы в верхнем регистре
first_letters = {name[0].upper() for name in names}
print(first_letters) # Output: {'A', 'B', 'C', 'D', 'E'}Фильтрация по условиям:
# Числа с дубликатами
numbers = [1, -2, 3, -4, 5, -2, 3, 6, -4]
# Получаем уникальные положительные числа
positive_numbers = {n for n in numbers if n > 0}
print(positive_numbers) # Output: {1, 3, 5, 6}34.4.4) Когда set comprehension наиболее полезен
Set comprehension особенно ценен, когда:
- Вам нужны уникальные значения: автоматически удаляет дубликаты
- Порядок не важен: множества неупорядочены, поэтому используйте их, когда последовательность не важна
- Вы будете выполнять операции над множествами: результат можно использовать для объединения, пересечения и т. п. (как мы изучали в главе 17)
# Записи студентов на два курса
course_a = ["Alice", "Bob", "Charlie", "David"]
course_b = ["Charlie", "David", "Eve", "Frank"]
# Получаем уникальных студентов по обоим курсам с помощью set comprehension
all_students = {student for course in [course_a, course_b] for student in course}
print(all_students) # Output: {'Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Frank'}34.5) Выбор: comprehension или циклы
34.5.1) Когда comprehension лучше
Comprehensions обычно предпочтительнее, когда вы создаёте новую коллекцию, преобразуя или фильтруя существующую. Они более лаконичны, часто более читаемы и обычно быстрее, чем эквивалентные циклы.
Comprehension особенно хороши, когда:
- Создаёте новую коллекцию из существующей:
# Хороший пример использования comprehension
prices = [10.99, 25.50, 8.75, 15.00]
discounted = [price * 0.9 for price in prices]- Преобразование простое:
# Понятно и лаконично
names = ["alice", "bob", "charlie"]
uppercase_names = [name.upper() for name in names]- Фильтрация по простым условиям:
# Легко понять
scores = [85, 92, 78, 65, 88, 55, 73, 95]
passing = [score for score in scores if score >= 70]34.5.2) Когда традиционные циклы лучше
Однако есть ситуации, когда традиционные циклы более уместны и читаемы:
Используйте циклы, когда:
- Логика сложная или включает несколько шагов:
# Слишком сложно для comprehension
results = []
for score in scores:
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
else:
grade = "F"
results.append({"score": score, "grade": grade})Хотя вы могли бы записать это как comprehension, читать это было бы значительно сложнее.
- Нужно выполнять действия, выходящие за рамки создания коллекции:
# Цикл понятнее, когда выполняется I/O или побочные эффекты
for filename in files:
with open(filename) as f:
content = f.read()
print(f"Processing {filename}")
# ... more processing- Нужно модифицировать существующую коллекцию на месте:
# Изменение списка на месте — нельзя использовать comprehension
numbers = [1, 2, 3, 4, 5]
for i in range(len(numbers)):
numbers[i] *= 2
print(numbers) # Output: [2, 4, 6, 8, 10]- Нужно использовать break или continue со сложной логикой:
# Поиск первого вхождения с дополнительной обработкой
found = None
for item in items:
if item.startswith("target"):
found = item
print(f"Found: {found}")
break34.5.3) Соображения читаемости
Самый важный фактор — читаемость. Если comprehension становится слишком длинным или сложным, разбейте его на традиционный цикл:
# Трудно читать — слишком много происходит в одной строке
result = [item.upper().strip() for item in items if len(item) > 5 and item.startswith('a')]
# Лучше — используйте цикл, когда логика сложная
result = []
for item in items:
if len(item) > 5 and item.startswith('a'):
cleaned = item.strip().upper()
result.append(cleaned)Хорошее правило: если ваш comprehension не помещается комфортно в одну строку (или максимум в две строки с чётким форматированием), вместо него стоит использовать цикл.
34.5.4) Соображения производительности
Comprehension обычно быстрее эквивалентных циклов, потому что оптимизированы на уровне интерпретатора. Однако для небольших и средних коллекций эта разница в производительности обычно несущественна.
# Оба дают одинаковый результат
# Comprehension немного быстрее
squares_comp = [n ** 2 for n in range(1000)]
# Цикл немного медленнее, но гибче
squares_loop = []
for n in range(1000):
squares_loop.append(n ** 2)Для большинства практических задач выбирайте на основе читаемости, а не производительности. Оптимизируйте скорость только если профилирование показывает, что конкретная операция является узким местом.
34.5.5) Комбинирование подходов
Иногда лучшее решение сочетает оба подхода:
# Используем comprehension для простого преобразования
student_data = [
{"name": "Alice", "score": 85},
{"name": "Bob", "score": 92},
{"name": "Charlie", "score": 78}
]
# Извлекаем оценки с помощью comprehension
scores = [student["score"] for student in student_data]
# Используем цикл для сложной обработки
for student in student_data:
score = student["score"]
if score >= 90:
print(f"{student['name']}: Excellent!")
elif score >= 80:
print(f"{student['name']}: Good job!")
else:
print(f"{student['name']}: Keep working!")34.6) Вложенные циклы и несколько частей for
34.6.1) Понимание нескольких частей for
Comprehension может включать несколько частей for, что эквивалентно вложенным циклам. Синтаксис:
[expression for item1 in iterable1 for item2 in iterable2]Это эквивалентно:
result = []
for item1 in iterable1:
for item2 in iterable2:
result.append(expression)Ключевой момент в том, что части for читаются слева направо, точно так же как вложенные циклы пишутся сверху вниз.
Начнём с простого примера, который создаёт все комбинации двух списков:
# Два списка значений
colors = ["red", "blue"]
sizes = ["S", "M", "L"]
# Создаём все комбинации
combinations = [(color, size) for color in colors for size in sizes]
print(combinations)
# Output: [('red', 'S'), ('red', 'M'), ('red', 'L'), ('blue', 'S'), ('blue', 'M'), ('blue', 'L')]Это создаёт все возможные пары «цвет–размер».
34.6.2) Создание пар координат
Типичный сценарий — генерация координатных пар:
# Создаём сетку координат 3x3
coordinates = [(x, y) for x in range(3) for y in range(3)]
print(coordinates)
# Output: [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]Создание таблицы умножения:
# Генерируем тройки для умножения
products = [(x, y, x * y) for x in range(1, 4) for y in range(1, 4)]
for x, y, product in products:
print(f"{x} × {y} = {product}")
# Output:
# 1 × 1 = 1
# 1 × 2 = 2
# 1 × 3 = 3
# 2 × 1 = 2
# 2 × 2 = 4
# 2 × 3 = 6
# 3 × 1 = 3
# 3 × 2 = 6
# 3 × 3 = 934.6.3) Развёртывание вложенных списков
Несколько частей for полезны для развёртывания вложенных структур:
# Вложенный список чисел
nested_numbers = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# Преобразуем в один список
flat = [num for sublist in nested_numbers for num in sublist]
print(flat) # Output: [1, 2, 3, 4, 5, 6, 7, 8, 9]Это эквивалентно:
flat = []
for sublist in nested_numbers:
for num in sublist:
flat.append(num)Выпрямление списка слов в список символов:
# Список слов
words = ["cat", "dog", "bird"]
# Получаем все символы из всех слов
all_chars = [char for word in words for char in word]
print(all_chars) # Output: ['c', 'a', 't', 'd', 'o', 'g', 'b', 'i', 'r', 'd']34.6.4) Добавление условий во вложенные comprehension
Можно добавлять условия, чтобы фильтровать результаты:
# Создаём пары, где сумма чётная
pairs = [(x, y) for x in range(5) for y in range(5) if (x + y) % 2 == 0]
print(pairs)
# Output: [(0, 0), (0, 2), (0, 4), (1, 1), (1, 3), (2, 0), (2, 2), (2, 4), (3, 1), (3, 3), (4, 0), (4, 2), (4, 4)]Поиск общих элементов между списками:
# Два списка чисел
list1 = [1, 2, 3, 4, 5]
list2 = [4, 5, 6, 7, 8]
# Находим элементы, где значения равны (общие элементы)
common = [x for x in list1 for y in list2 if x == y]
print(common) # Output: [4, 5]Примечание: для поиска общих элементов эффективнее использовать пересечение множеств: set(list1) & set(list2), что мы изучали в главе 17.
34.6.5) Вложенные dictionary comprehension
Вы также можете использовать несколько частей for в dictionary comprehension:
# Создаём словарь сумм координат
coord_sums = {(x, y): x + y for x in range(3) for y in range(3)}
print(coord_sums)
# Output: {(0, 0): 0, (0, 1): 1, (0, 2): 2, (1, 0): 1, (1, 1): 2, (1, 2): 3, (2, 0): 2, (2, 1): 3, (2, 2): 4}34.6.6) Когда стоит избегать вложенных comprehension
Хотя вложенные comprehension мощны, их быстро становится трудно читать. Рассмотрите следующие рекомендации:
Приемлемо — относительно просто:
# Два уровня вложенности, простое выражение
matrix = [[i * j for j in range(3)] for i in range(3)]
print(matrix) # Output: [[0, 0, 0], [0, 1, 2], [0, 2, 4]]Становится сложно — подумайте о цикле:
# Три уровня вложенности — трудно читать
result = [[[i + j + k for k in range(2)] for j in range(2)] for i in range(2)]
# Better as nested loops for clarityПрактическое правило: если у вас больше двух частей for или если выражение сложное, используйте традиционные вложенные циклы:
# Понятнее с явными циклами
result = []
for i in range(2):
middle = []
for j in range(2):
inner = []
for k in range(2):
inner.append(i + j + k)
middle.append(inner)
result.append(middle)Comprehension с несколькими частями for — мощные инструменты, но помните: ясность важнее краткости. Если вложенный comprehension становится трудно понять, лучше использовать явные циклы.