Генераторы списков, словарей и множеств. Списки в Python. Углубленный уровень Функции генераторы python

| |

Генераторы списков (list comprehensions) позволяют быстро создать список на основе существующих списков. При помощи генератора можно построить список на основе любого итерируемого типа данных (к примеру, из строк или кортежей).

В результате цикл создаст такой же список:

["8", "h", "o", "s", "t"]

Условные выражения в генераторах списков

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

Давайте рассмотрим генератор с выражением if:

fish_tuple = ("blowfish", "clownfish", "catfish", "octopus")
fish_list =
print(fish_list)

Список использует кортеж fish_tuple в качестве основы для нового списка fish_list.

Ключевые слова for и in используются так же, как в предыдущем размере. Кроме того, генератор содержит выражение if, благодаря чему он исключает из списка строку ‘octopus’.

Запросите содержимое списка fish_list и убедитесь, что он содержит все элементы fish_tuple, кроме ‘octopus’.

["blowfish", "clownfish", "catfish"]

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

Теперь рассмотрим другой пример, в котором используются математические операции, цифры и метод range().


print(number_list)

Читайте также:

Новый список number_list будет содержать квадратичные значения каждого чётного элемента в диапазоне от 0-9. В результате получится такой список:

Давайте разберём этот пример пошагово. Если вместо x ** 2 for x использовать просто x for x, список будет выглядеть так:

number_list =
print(number_list)

После этого в генератор было добавлено условное выражение:

number_list =
print(number_list)

Выражение if исключило из списка все нечётные числа.

Теперь осталось добавить оператор, который возведёт все элементы в квадрат:

number_list =
print(number_list)

Теперь каждый элемент списка будет возведён в квадрат.

Также в генераторах можно использовать вложенные выражения if:

number_list =
print(number_list)

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

Работа с файлами и каталогами

Python 3 поставляется с модулем os , что означает «операционная система». содержит множество функций для получения информации о локальных каталогах, файлах, процессах и переменных окружения (а в некоторых случаях, и для манипулирования ими). Python предлагает очень хороший унифицированный программный интерфейс для всех поддерживаемых операционных систем , так что ваши программы можно запускать на любом компьютере с минимальным количеством платформо-зависимого кода.

Текущий рабочий каталог

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

  1. Импортирование какого-либо модуля из папки примеров
  2. Вызов функции из этого модуля
  3. Объяснение результата

Всегда есть текущий рабочий каталог.

Если вы ничего не знаете о текущем рабочем каталоге, то, возможно, шаг 1 окажется неудачным и будет порождено исключение типа ImportError . Почему? Потому что Python будет искать указанный модуль в пути поиска оператора import , но не найдёт его, потому что каталог examples не содержится в путях поиска. Чтобы исправить это, вы можете сделать одно из двух:

  • либо добавить папку examples в путь поиска оператора import ;
  • либо сделать текущим рабочим каталогом папку examples .

Текущий рабочий каталог является неявным параметром, который Python постоянно хранит в памяти. Текущий рабочий каталог есть всегда, когда вы работаете в интерактивной оболочке Python, запускаете свой сценарии из командной строки или CGI -сценарий где-то на веб-сервере .

Модуль os содержит две функции для работы с текущим рабочим каталогом.

>>> import os

>>> print (os .getcwd () )
C:\Python31

>>> os .chdir ()

>>> print (os .getcwd () )

Работа с именами файлов и каталогов

Раз зашла речь о каталогах, я хочу обратить ваше внимание на модуль os .path . Он содержит функции для работы с именами файлов и каталогов.

>>> import os

>>> print (os .path .join ("/Users/pilgrim/diveintopython3/examples/" , "humansize.py" ) )
/Users/pilgrim/diveintopython3/examples/humansize.py

>>> print (os .path .join ("/Users/pilgrim/diveintopython3/examples" , "humansize.py" ) )
/Users/pilgrim/diveintopython3/examples\humansize.py

>>> print (os .path .expanduser ("~" ) )
c:\Users\pilgrim

>>> print (os .path .join (os .path .expanduser ("~" ) , "diveintopython3" , "examples" , "humansize.py" ) )
c:\Users\pilgrim\diveintopython3\examples\humansize.py

Модуль os .path также содержит функции для разбиения файловых путей, имён папок и файлов на их составные части.

>>> pathname = "/Users/pilgrim/diveintopython3/examples/humansize.py"

>>> os .path .split (pathname)
("/Users/pilgrim/diveintopython3/examples" , "humansize.py" )

>>> (dirname, filename) = os .path .split (pathname)

>>> dirname
"/Users/pilgrim/diveintopython3/examples"

>>> filename
"humansize.py"

>>> (shortname, extension) = os .path .splitext (filename)
>>> shortname
"humansize"
>>> extension
".py"

Получение содержимого каталога

Модуль glob понимает символы-джокеры, использующиеся в командных оболочках.

Модуль glob - это ещё один инструмент из стандартной библиотеки Python. Это простой способ программно получить содержимое папки, а также он умеет использовать символы-джокеры , с которыми вы наверняка знакомы, если работали в командной строке.

>>> os .chdir ("/Users/pilgrim/diveintopython3/" )
>>> import glob

>>> glob .glob ("examples/*.xml" )
[ "examples\\ feed-broken.xml" ,
"examples\\ feed-ns0.xml" ,
"examples\\ feed.xml" ]

>>> os .chdir ("examples/" )

>>> glob .glob ("*test*.py" )
[ "alphameticstest.py" ,
"pluraltest1.py" ,
"pluraltest2.py" ,
"pluraltest3.py" ,
"pluraltest4.py" ,
"pluraltest5.py" ,
"pluraltest6.py" ,
"romantest1.py" ,
"romantest10.py" ,
"romantest2.py" ,
"romantest3.py" ,
"romantest4.py" ,
"romantest5.py" ,
"romantest6.py" ,
"romantest7.py" ,
"romantest8.py" ,
"romantest9.py" ]

Получение сведений о файле

Любая современная операционная система хранит сведения о каждом файле (метаданные): дата создания, дата последней модификации, размер файла и т. д. Python предоставляет единый программный интерфейс для доступа к этим метаданным. Вам не надо открывать файл; всё, что требуется - имя файла.

>>> import os

>>> print (os .getcwd () )
c:\Users\pilgrim\diveintopython3\examples

>>> metadata = os .stat ("feed.xml" )

>>> metadata.st_mtime
1247520344.9537716

>>> import time

>>> time .localtime (metadata.st_mtime )
time .struct_time (tm_year= 2009 , tm_mon= 7 , tm_mday= 13 , tm_hour= 17 , tm_min= 25 ,
tm_sec= 44 , tm_wday= 0 , tm_yday= 194 , tm_isdst= 1 )

Получение абсолютных путей

В предыдущем разделе функция glob .glob () возвращала список относительных путей. В первом примере пути имели вид "examples\feed.xml" , а во втором относительные пути были даже короче, например, "romantest1.py" . Пока вы остаётесь в текущем рабочем каталоге, по этим относительным путям можно будет открывать файлы или получать их метаданные. Но если вы захотите получить абсолютный путь - то есть тот, который включает все имена каталогов до корневого или до буквы диска, вам понадобится функция os .path .realpath () .

>>> import os
>>> print (os .getcwd () )
c:\Users\pilgrim\diveintopython3\examples
>>> print (os .path .realpath ("feed.xml" ) )

c:\Users\pilgrim\diveintopython3\examples\feed.xml

Генераторы списков

В генераторах списков можно использовать любые выражения Python.

С помощью генераторов списков можно легко отобразить один список в другой, применив некоторую функцию к каждому элементу.

>>> a_list = [ 1 , 9 , 8 , 4 ]
>>> [ elem * 2 for elem in a_list]
[ 2 , 18 , 16 , 8 ]

>>> a_list
[ 1 , 9 , 8 , 4 ]

>>> a_list = [ elem * 2 for elem in a_list]
>>> a_list
[ 2 , 18 , 16 , 8 ]

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

>>> import os , glob
>>> glob .glob ("*.xml" )
[ "feed-broken.xml" , "feed-ns0.xml" , "feed.xml" ]

>>> [ os .path .realpath (f) for f in glob .glob ("*.xml" ) ]
[ "c:,
"c:,
"c:]

При генерировании списков можно также фильтровать элементы, чтобы отбросить некоторые значения.

>>> import os , glob

>>> [ f for f in glob .glob ("*.py" ) if os .stat (f) .st_size > 6000 ]
[ "pluraltest6.py" ,
"romantest10.py" ,
"romantest6.py" ,
"romantest7.py" ,
"romantest8.py" ,
"romantest9.py" ]

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

>>> import os , glob

>>> [ (os .stat (f) .st_size , os .path .realpath (f) ) for f in glob .glob ("*.xml" ) ]
[ (3074 , "c:\\ Users\\ pilgrim\\ diveintopython3\\ examples\\ feed-broken.xml" ) ,
(3386 , "c:\\ Users\\ pilgrim\\ diveintopython3\\ examples\\ feed-ns0.xml" ) ,
(3070 , "c:\\ Users\\ pilgrim\\ diveintopython3\\ examples\\ feed.xml" ) ]
>>> import humansize

>>> [ (humansize.approximate_size (os .stat (f) .st_size ) , f) for f in glob .glob ("*.xml" ) ]
[ ("3.0 KiB" , "feed-broken.xml" ) ,
("3.3 KiB" , "feed-ns0.xml" ) ,
("3.0 KiB" , "feed.xml" ) ]

Генераторы словарей

Генератор словаря похож на генератор списка, но вместо списка он создает словарь.

>>> import os , glob

>>> metadata = [ (f, os .stat (f) ) for f in glob .glob ("*test*.py" ) ]

>>> metadata[ 0 ]
("alphameticstest.py" , nt.stat_result (st_mode= 33206 , st_ino= 0 , st_dev= 0 ,
st_nlink= 0 , st_uid= 0 , st_gid= 0 , st_size= 2509 , st_atime= 1247520344 ,
st_mtime= 1247520344 , st_ctime= 1247520344 ) )

>>> metadata_dict = { f:os .stat (f) for f in glob .glob ("*test*.py" ) }

>>> type (metadata_dict)
< class "dict" >

>>> list (metadata_dict.keys () )
[ "romantest8.py" , "pluraltest1.py" , "pluraltest2.py" , "pluraltest5.py" ,
"pluraltest6.py" , "romantest7.py" , "romantest10.py" , "romantest4.py" ,
"romantest9.py" , "pluraltest3.py" , "romantest1.py" , "romantest2.py" ,
"romantest3.py" , "romantest5.py" , "romantest6.py" , "alphameticstest.py" ,
"pluraltest4.py" ]

>>> metadata_dict[ "alphameticstest.py" ] .st_size
2509

Также, как и в генераторах списков, вы можете включать в генераторы словарей условие if , чтобы отфильтровать входную последовательность с помощью выражения-условия, вычисляющегося для каждого элемента.

>>> import os , glob , humansize

В Python просто генераторы и генераторы списков - разные вещи. Здесь есть проблема перевода с английского. То, что мы привыкли называть генератором списка, в английском варианте звучит как "list comprehension " и к генераторам никакого отношения не имеет.

Слово "comprehension" (понимание, осмысление) оказывается как бы не в тему при переводе на русский. Получается что-то вроде "понимание списка". Поэтому мы говорим "генератор списка", понимая под словом "генератор" не объект, а синтаксическую конструкцию, которая генерирует, то есть создает, список.

С другой стороны, объекты-генераторы - это особые объекты-функции, которые между вызовами сохраняют свое состояние. В цикле for они ведут себя подобно итерируемым объектам, к которым относятся списки, словари, строки и др. Однако генераторы поддерживают метод __next__(), а значит являются разновидностью итераторов.

Быстрым способом создания относительно простых объектов-генераторов являются генераторные выражения - generator expressions . Синтаксис этих выражений похож на синтаксис генераторов списков. Однако они возвращают разные типы объектов. Первый - объект-генератор. Второй - список.

Сначала рассмотрим генераторы списков, чтобы привыкнуть к синтаксической конструкции.

Генераторы списков

В Python генераторы списков позволяют создавать и быстро заполнять списки.

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

>>> a = [ 1 , 2 , 3 ] >>> b = [ i+10 for i in a] >>> a [ 1 , 2 , 3 ] >>> b [ 11 , 12 , 13 ]

В примере выше генератором списка является выражение . Здесь a - итерируемый объект. В данном случае это другой список. Из него извлекается каждый элемент в цикле for. Перед for описывается действие, которое выполняется над элементом перед его добавлением в новый список.

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

>>> a = [ 1 , 2 , 3 ] >>> a = [ i+10 for i in a] >>> a [ 11 , 12 , 13 ]

Генераторы списков относятся к разряду "синтаксического сахара" языка программирования Python. Другими словами, без них можно обойтись:

>>> for index, value in enumerate (a) : ... a [ index] = value + 10 ... >>> a [ 11 , 12 , 13 ]

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

>>> ls0 = [ 1 , 2 , 3 ] >>> ls1 = ls0 >>> ls1.append (4 ) >>> ls0 [ 1 , 2 , 3 , 4 ] >>> ls1 = [ i+1 for i in ls1] >>> ls1 [ 2 , 3 , 4 , 5 ] >>> ls0 [ 1 , 2 , 3 , 4 ]

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

Перебираемым в цикле for объектом может быть быть не только список. В примере ниже в список помещаются строки файла.

>>> lines = [ line.strip () for line in open ("text.txt" ) ] >>> lines [ "one" , "two" , "three" ]

В генератор списка можно добавить условие:

>>> from random import randint >>> nums = [ randint(10 , 20 ) for i in range (10 ) ] >>> nums [ 18 , 17 , 11 , 11 , 15 , 18 , 11 , 20 , 10 , 19 ] >>> nums = [ i for i in nums if i%2 == 0 ] >>> nums [ 18 , 18 , 20 , 10 ]

Генераторы списков могут содержать вложенные циклы:

>>> a = "12" >>> b = "3" >>> c = "456" >>> comb = [ i+j+k for i in a for j in b for k in c] >>> comb [ "134" , "135" , "136" , "234" , "235" , "236" ]

Генераторы словарей и множеств

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

>>> a = { i:i**2 for i in range (11 , 15 ) } >>> a { 11 : 121 , 12 : 144 , 13 : 169 , 14 : 196 }

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

>>> a = { i for i in range (11 , 15 ) } >>> a set ([ 11 , 12 , 13 , 14 ] ) >>> b = { 1 , 2 , 3 } >>> b set ([ 1 , 2 , 3 ] )

Генераторы

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

>>> a = (i for i in range (2 , 8 ) ) >>> a < generator object < genexpr> at 0x7efc88787910 > >>> for i in a: ... print (i) ... 2 3 4 5 6 7

Второй раз перебрать генератор в цикле for не получится, так как объект-генератор уже сгенерировал все значения по заложенной в него "формуле". Поэтому генераторы обычно используются, когда надо единожды пройтись по итерируемому объекту.

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

Выражение, создающее генератор, это сокращенная запись следующего:

>>> def func(start, finish) : ... while start < finish: ... yield start * 0.33 ... start += 1 ... >>> a = func(1 , 4 ) >>> a < generator object func at 0x7efc88787a50 > >>> for i in a: ... print (i) ... 0.33 0.66 0.99

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

>>> b = (i*0.33 for i in range (1 , 4 ) ) >>> b < generator object < genexpr> at 0x7efc88787960 > >>> for i in b: ... print (i) ... 0.33 0.66 0.99

Сегодня я расскажу о таком типе данных, как списки , операциях над ними и методах, о генераторах списков и о применении списков.

Что такое списки?

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

Чтобы использовать списки, их нужно создать. Создать список можно несколькими способами. Например, можно обработать любой итерируемый объект (например, ) встроенной функцией list :

>>> list ("список" ) ["с", "п", "и", "с", "о", "к"]

Список можно создать и при помощи литерала:

>>> s = # Пустой список >>> l = [ "s" , "p" , [ "isok" ], 2 ] >>> s >>> l ["s", "p", ["isok"], 2]

Как видно из примера, список может содержать любое количество любых объектов (в том числе и вложенные списки), или не содержать ничего.

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

>>> c = [ c * 3 for c in "list" ] >>> c ["lll", "iii", "sss", "ttt"]

Возможна и более сложная конструкция генератора списков:

>>> c = [ c * 3 for c in "list" if c != "i" ] >>> c ["lll", "sss", "ttt"] >>> c = [ c + d for c in "list" if c != "i" for d in "spam" if d != "a" ] >>> c ["ls", "lp", "lm", "ss", "sp", "sm", "ts", "tp", "tm"]

Но в сложных случаях лучше пользоваться обычным циклом for для генерации списков.

Функции и методы списков

Создать создали, теперь нужно со списком что-то делать. Для списков доступны основные , а также методы списков.

Таблица "методы списков"

Метод Что делает
list.append (x) Добавляет элемент в конец списка
list.extend (L) Расширяет список list, добавляя в конец все элементы списка L
list.insert (i, x) Вставляет на i-ый элемент значение x
list.remove (x) Удаляет первый элемент в списке, имеющий значение x. ValueError, если такого элемента не существует
list.pop ([i]) Удаляет i-ый элемент и возвращает его. Если индекс не указан, удаляется последний элемент
list.index (x, ]) Возвращает положение первого элемента со значением x (при этом поиск ведется от start до end)
list.count (x) Возвращает количество элементов со значением x
list.sort () Сортирует список на основе функции
list.reverse () Разворачивает список
list.copy () Поверхностная копия списка
list.clear () Очищает список

Нужно отметить, что методы списков, в отличие от , изменяют сам список, а потому результат выполнения не нужно записывать в эту переменную.

>>> l = [ 1 , 2 , 3 , 5 , 7 ] >>> l . sort () >>> l >>> l = l . sort () >>> print (l ) None

И, напоследок, примеры работы со списками:

>>> a = [ 66.25 , 333 , 333 , 1 , 1234.5 ] >>> print (a . count (333 ), a . count (66.25 ), a . count ("x" )) 2 1 0 >>> a . insert (2 , - 1 ) >>> a . append (333 ) >>> a >>> a . index (333 ) 1 >>> a . remove (333 ) >>> a >>> a . reverse () >>> a >>> a . sort () >>> a [-1, 1, 66.25, 333, 333, 1234.5]

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

List comprehensions (генераторы списков), как это не странно, предназначены для удобной обработки списков, к которой можно отнести и создание новых списков, и модификацию существующих.

Допустим, нам необходимо получить список нечетных чисел, не превышающих 25. В принципе, только познакомившись с работой команды xrange решить эту проблему несложно.

Res = for x in xrange(1, 25, 2): res.append(x) ... print res

В общем-то, полученный результат - целиком нас устраивает всем, кроме длинной записи. Тут-то на помощь и придет наш «сахарок». В самом простом виде, он обычно

Res = ... print res

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

По желанию, можно добавить дополнительные условия фильтрации. Например, доработаем наш предыдущий пример так, чтобы исключались квадраты чисел, кратных 3.

Res = ... print res

Задание 6.1. Генератор списка

Нужно получить список двоичных чисел не являющихся квадратами.

Генераторы

Конструкции типа mylist = формируют списки элементов, значения которых хранятся в памяти. К ним можно применять конструкцию for i in mylist: print(i) для работы с элементами сколько угодно раз.

Генераторы это тоже итерируемые объекты, но прочитать их можно лишь один раз. Это связано с тем, что они не хранят значения в памяти, а генерируют их на лету:

Mygenerator = (x*x for x in range(3)) for i in mygenerator: print(i)

Всё то же самое, разве что используются круглые скобки вместо квадратных. НО: нельзя применить конструкцию for i in mygenerator второй раз, так как генератор может быть использован только единожды: он вычисляет 0, потом забывает про него и вычисляет 1, завершая вычислением 4 - одно за другим. Также нельзя получить число элементов функцией len() . К генераторам нельзя применить срезы mygenerator . Но, генераторы позволяют сократить объем памяти для запуска программы.

Yield

Yield это ключевое слово, которое используется примерно как return - отличие в том, что функция вернёт генератор.

Def createGenerator() : mylist = range(3) for i in mylist: yield i*i mygenerator = createGenerator() # создаём генератор for i in mygenerator: print(i)

Задание 6.2. Генератор тетраэдрических чисел

При помощи генератора треугольных чисел создать генератор тетраэдрических чисел.

Задание 6.3. Генератор переливаний

Имеется школьная задача о получении нужного объема при помощи бесконечного бассейна и двух ведер. Например: нужно получить 4 литра при помощи двух ведер емкостью 3 и 5 литров. Имеется ее решение методом бильярдного шара .

Необходимо создать генератор, выдающий пары чисел - наполненности сосудов. Пример работы:

Buckets = pool(3,5) for a,b in buckets: print("a=",a," b=",b) if b==4: break