Понимание списка Python против выражения генератора

Python


Знаете ли вы разницу между следующими синтаксисами?

[x for x in range(5)]

(x for x in range(5))

tuple(range(5))

Эта статья познакомит вас с разницей здесь.

5 фактов о списках

Во-первых, краткий обзор списков (часто называемых «массивами» в других языках программирования):

Список — это тип данных, который может быть представлен в виде набора элементов. Простой список выглядит так: [0, 1, 2, 3, 4, 5]
Элементами списка являются все возможные типы данных и их комбинации:

>>> a = 12
>>> b = "this is text"
>>> my_list = [0, b, ['element', 'another element'], (1, 2, 3), a]
>>> print(my_list)
[0, 'this is text', ['element', 'another element'], (1, 2, 3), 12]

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

>>> a = ['red', 'green', 'blue']
>>> print(a[0])
red

В отличие от строк, списки в Python изменяемы. Это означает, что вы можете заменять, добавлять или удалять элементы.
Вы можете создать список, используя цикл for и функцию range().

>>> my_list = []
>>> for x in range(10):
...     my_list.append(x * 2)
... 
>>> print(my_list)
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

Хорошо, так что же такое понимание списка?

Часто считающиеся частью функционального программирования в Python, генераторы списков позволяют создавать списки с использованием циклов for, которые содержат меньше кода.

Используйте понимание списка, чтобы увидеть реализацию предыдущего примера:

>>> comp_list = [x * 2 for x in range(10)] 
>>> print(comp_list)
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

Приведенный выше пример слишком упрощен, чтобы дать вам представление о синтаксисе. Тот же результат может быть достигнут с помощью более простой функции list(range(0, 19, 2)) .

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

>>> comp_list = [x ** 2 for x in range(7) if x % 2 == 0] 
>>> print(comp_list)
[4, 16, 36]

Другой доступный вариант — использовать генератор списков для объединения нескольких списков и создания списка списков. На первый взгляд синтаксис кажется сложным. Может быть полезно рассматривать списки как внешние и внутренние последовательности.

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

>>> nums = [1, 2, 3, 4, 5]
>>> letters = ['A', 'B', 'C', 'D', 'E']
>>> nums_letters = [[n, l] for n in nums for l in letters]
#the comprehensions list combines two simple lists in a complex list of lists.
>>> print(nums_letters)
>>> print(nums_letters)
[[1, 'A'], [1, 'B'], [1, 'C'], [1, 'D'], [1, 'E'], [2, 'A'], [2, 'B'], [2, 'C'], [2, 'D'], [2, 'E'], [3, 'A'], [3, 'B'], [3, 'C'], [3, 'D'], [3, 'E'], [4, 'A'], [4, 'B'], [4, 'C'], [4, 'D'], [4, 'E'], [5, 'A'], [5, 'B'], [5, 'C'], [5, 'D'], [5, 'E']]
>>>

Давайте попробуем это с текстом, или строковый объект правильный.

>>> iter_string = "some text"
>>> comp_list = [x for x in iter_string if x !=" "]
>>> print(comp_list)
['s', 'o', 'm', 'e', 't', 'e', 'x', 't']

Понимание не ограничивается списками. Вы также можете создавать словари и устанавливать понимания.

>>> dict_comp = {x:chr(65+x) for x in range(1, 11)}
>>> type(dict_comp)
<class 'dict'>  
>>> print(dict_comp)
{1: 'B', 2: 'C', 3: 'D', 4: 'E', 5: 'F', 6: 'G', 7: 'H', 8: 'I', 9: 'J', 10: 'K'}

>>> set_comp = {x ** 3 for x in range(10) if x % 2 == 0}
>>> type(set_comp)
<class 'set'>  
>>> print(set_comp)
{0, 8, 64, 512, 216}

Разница между Iterable и Iterator

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

Iterable — это «последовательность» данных, которую вы можете перебирать с помощью цикла. Простейшим видимым примером итерации может быть список целых чисел — [1, 2, 3, 4, 5, 6, 7]. Другие типы данных, такие как строки, словари, кортежи, наборы и т. д., могут повторяться.

По сути, любой объект, имеющий метод iter(), может использоваться как итерируемый. Вы можете проверить с помощью функции hasattr() в интерпретаторе.

>>> hasattr(str, '__iter__')
True  
>>> hasattr(bool, '__iter__')
False

При переборе последовательности данных реализуется протокол итератора. Например, когда вы используете цикл for, за кулисами происходит следующее:

iter() вызывает первый метод объекта, чтобы преобразовать его в объект итератора.
Вызовите этот метод для объекта итератора, чтобы получить следующий элемент последовательности. следующий()
Если у StopIteration нет элементов для вызова, создается исключение.

>>> simple_list = [1, 2, 3]
>>> my_iterator = iter(simple_list)
>>> print(my_iterator)
<list_iterator object at 0x7f66b6288630>  
>>> next(my_iterator)
1  
>>> next(my_iterator)
2  
>>> next(my_iterator)
3  
>>> next(my_iterator)
Traceback (most recent call last):  
  File "<stdin>", line 1, in <module>
StopIteration

выражение генератора

В Python генераторы предоставляют удобный способ реализации протокола итератора. Генератор — это итерация, созданная с помощью функции с оператором yield.

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

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

>>> def my_gen():
...     for x in range(5):
...             yield x

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

Синтаксис и концепции аналогичны спискам:

>>> gen_exp = (x ** 2 for x in range(10) if x % 2 == 0) 
>>> for x in gen_exp:
...     print(x)
0  
4  
16  
36  
64

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

Генераторы списков и выражения возвращают разные типы данных.

>>> list_comp = [x ** 2 for x in range(10) if x % 2 == 0]
>>> gen_exp = (x ** 2 for x in range(10) if x % 2 == 0)
>>> print(list_comp)
[0, 4, 16, 36, 64]
>>> print(gen_exp)
<generator object <genexpr> at 0x7f600131c410>

Главное преимущество генератора перед списком в том, что он занимает намного меньше памяти. Мы можем проверить объем памяти, занимаемой обоими типами, с помощью метода sys.getsizeof().

Примечание. В Python 2 использование функции range() на самом деле не дает преимущества в размере, поскольку он по-прежнему сохраняет весь список элементов в памяти. Однако в Python 3 этот пример работает, потому что range() возвращает объект диапазона.

>>> from sys import getsizeof
>>> my_comp = [x * 5 for x in range(1000)]
>>> my_gen = (x * 5 for x in range(1000))
>>> getsizeof(my_comp)
9024  
>>> getsizeof(my_gen)
88

Генератор создает один элемент за раз, поэтому он более эффективен с точки зрения памяти, чем список.

Например, когда вы хотите выполнить итерацию по списку, Python резервирует память для всего списка. Генератор не хранит всю последовательность в памяти, а только «генерирует» следующий элемент последовательности по мере необходимости.

последние мысли

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

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

Посмотреть оригинальный английский

Посмотреть другие статьи: www.apexyun.com

Общедоступный номер: Galaxy № 1

Контактный адрес электронной почты: public@space-explore.com

(Пожалуйста, не перепечатывайте без разрешения)