8. Списки¶
Список (англ.: list) есть упорядоченный набор значений, где каждое значение доступно по индексу. Значения, входящие в список, называются элементами.
Списки похожи на строки, так как строки тоже являются упорядоченными наборами элементов — символов. Но в отличие от строк, элементы списка могут быть любых типов. Списки и строки, а также другие типы, являющиеся упорядоченными наборами, называются последовательностями.
8.2. Списочные значения¶
Есть несколько способов создать новый список, простейший из них — заключить элементы в квадратные скобки, [ и ] :
В первом примере у нас список из четырех целых чисел. Во втором — список из трех строк. Элементы списка не обязательно относятся к одному типу. Следующий список содержит строку, число с плавающей точкой, целое число и. еще один список:
Список внутри другого списка называют вложенным.
Наконец, список может вовсе не содержать элементов. Такой список называют пустым и обозначают [] .
В логическом выражении пустой список, так же, как 0 или пустая строка, считается ложью:
Мы можем присваивать списочные значения переменным и передавать списки в качестве аргументов при вызове функций:
8.3. Доступ к элементам¶
Для доступа к элементам списка используется тот же оператор [] , что и для доступа к символам строки. Выражение в квадратных скобках задает индекс. Не забывайте, что индексы начинаются с 0:
В качестве индекса можно использовать любое целочисленное выражение:
Если попытаться получить доступ к несуществующему элементу, то получим ошибку выполнения:
Если индекс отрицательный, то счет идет от конца списка:
Выражение numbers[-1] дает последний элемент списка, numbers[-2] — второй от конца, а элемента numbers[-3] не существует.
Часто в качестве индекса используется переменная цикла.
Этот цикл while считает от 0 до 4. Когда переменная цикла i становится равна 4, условие становится ложным и цикл завершается. Таким образом, тело цикла выполняется для i со значениями 0, 1, 2 и 3.
В каждой итерации переменная i используется как индекс для списка, чтобы вывести i -тый элемент. Этот прием называется обход списка.
8.4. Длина списка¶
Функция len возвращает длину списка, то есть, количество элементов в списке. В качестве верхней границы в цикле удобно использовать возвращаемое этой функцией значение, а не константу. При этом, если размер списка изменится, вам не придется просматривать программу и вносить изменения во все циклы, работающие с этим списком; они будут работать корректно со списком любого размера:
В последней итерации i равно len(horsemen) - 1 , то есть, индексу последнего элемента списка. Когда i становится равным len(horsemen) , условие цикла становится ложным и тело цикла не выполняется. И это правильно, поскольку len(horsemen) недопустимый для данного списка индекс.
Хотя список может содержать другой список, вложенный список считается одним элементом. Длина этого списка равна 4:
8.5. Проверка вхождения в список¶
Оператор in проверяет вхождение элемента в последовательность и дает в результате логическое значение. Мы уже использовали его со строками, но он также работает со списками и другими последовательностями:
Поскольку ‘pestilence’ входит в список horsemen , то оператор in возвращает True . Поскольку ‘debauchery’ не входит в список, in возвращает False .
Используя not вместе с in , можно проверить, что элемент не является элементом списка:
8.6. Операции над списками¶
Оператор + конкатенирует списки:
Оператор * повторяет элементы списка заданное число раз:
В первом примере 0 повторяется четыре раза. Во втором три раза повторяются элементы 1, 2, 3 .
8.7. Срезы списков¶
Мы выполняли срезы строк, но срезы также работают для списков:
8.8. Функция range ¶
В программировании часто требуются списки последовательных целых чисел, и Python предоставляет простой способ для их создания:
Функция range принимает два аргумента и возвращает список целых, начиная от числа, заданного первым аргументом, и до числа, заданного вторым аргументом, не включая последнее.
Можно вызывать range и по-другому. При вызове с единственным аргументом функция возвращает список, начинающийся с 0:
Третий аргумент, если он указан, задает шаг между соседними значениями в списке. Получим список чисел от 1 до 10 с шагом 2:
Если шаг задан отрицательным числом, то начальное число должно быть больше конечного:
Иначе результатом будет пустой список:
8.9. Списки изменяемы¶
В отличие от строк, списки изменяемы. Это означает, мы можем изменять их элементы. Используя оператор [] в левой части присваивания, можно избирательно обновить один из элементов:
Оператор [] может использоваться со списком в любом месте выражения. Если он появляется слева от оператора присваивания, он изменяет элемент списка. В приведенном примере первый элемент списка fruit изменяется с 'banana' на 'pear' , а последний — с 'quince' на 'orange' . Присваивание отдельному элементу не работает для строк:
Но работает для списков:
Используя срез, можно изменить несколько элементов сразу:
Можно также удалить элементы из списка, присвоив им пустой список:
А можно добавить элементы в список, втиснув их в пустой срез в нужном месте:
8.10. Удаление списка¶
Удаление элементов с помощью присваивания срезов довольно вычурно, и потому чревато ошибками. Python предлагает альтернативный способ, более легкий для чтения и понимания, и, к тому же, более универсальный.
del удаляет элемент из списка:
Как и следовало ожидать, del работает с отрицательными индексами, и генерирует ошибку выполнения, если заданный индекс выходит за границы разрешенного диапазона.
С del можно использовать срез:
8.11. Объекты и значения¶
Выполним предложения присваивания:
Теперь мы знаем, что и a и b указывают на строку "banana" . Но мы не можем сказать, указывают ли они на одну и ту же строку.
Есть два варианта:
В первом случае a и b указывают на два разных объекта с одинаковыми значениями. Во втором случае они ссылаются на один и тот же объект. Объект — это что-то, с чем может быть связано имя переменной.
У каждого объекта имеется уникальный идентификатор, который можно получить с помощью функции id . Отобразив идентификаторы объектов, на которые указывают a и b , мы узнаем, связаны ли эти переменные с одним и тем же объектом:
Мы два раза получили один и тот же идентификатор, а это значит, что Python создал только одну строку, и обе переменные, a и b , связаны с ней. Вы, вероятно, получите другое значение идентификатора.
Интересно, что списки ведут себя иначе. Если создать два списка, то мы получим два объекта:
На диаграмме это выглядит так:

Переменные a и b ссылаются на разные объекты, имеющие одинаковые значения.
8.12. Альтернативные имена¶
Поскольку переменные ссылаются на объекты, то, если мы присвоим одну переменную другой, обе переменные будут ссылаться на один и тот же объект:
На диаграмме это выглядит так:

Поскольку два разных имени, a и b , связаны с одним и тем же списком, будем называть их альтернативными именами. Изменения, сделанные с использованием одного имени, оказывают влияние и на другое:
Хотя такое поведение может быть полезным, иногда оно оказывается нежелательным. Вообще говоря, лучше избегать альтернативных имен, работая с изменяемыми объектами. А вот для неизменяемых объектов альтернативные имена не представляют никаких проблем. Поэтому Python создает альтернативные имена для строк, когда представляется случай сэкономить память компьютера.
8.13. Клонирование списков¶
Если нужно изменить список и при этом сохранить копию оригинального списка, то понадобится сделать копию самого списка, а не ссылки на него. Этот процесс иногда называют клонированием.
Простейший способ клонировать список — воспользоваться оператором среза:
Получение любого среза списка a приводит к созданию нового списка. В данном случае срез включает весь список.
Теперь можно изменять список b , не беспокоясь об a :
8.14. Списки и циклы for ¶
Цикл for также работает со списками. Синтаксис такой:
Это предложение эквивалентно следующему фрагменту кода:
Цикл for более лаконичен, поскольку мы можем обойтись без переменной цикла i . Вот рассмотренный выше цикл, переписанный с помощью for .
Он читается почти по-английски: для (каждого) horseman (англ.: всадник) в (списке) horsemen (англ.: всадники) напечатать horseman.
Любое списочное выражение может быть использовано в цикле for :
В первом примере выводятся все числа, кратные 3, между 0 и 19. Во втором примере выражается энтузиазм по поводу разных видов фруктов.
Поскольку списки изменяемы, часто выполняется обход списка с изменением каждого из его элементов. Следующий пример возводит в квадрат все числа в списке от 1 до 5 :
Подумайте над выражением range(len(numbers)) и разберитесь, как оно работает. В данном случае нам нужно как значение, так и индекс элемента списка для того, чтобы мы могли присвоить ему новое значение.
Такой прием довольно распространен в программировании, поэтому Python предлагает более красивый способ реализовать его:
Здесь функция enumerate в каждой итерации возвращает очередной индекс и связанное с ним значение. Еще один пример того, как работает enumerate :
8.15. Списочные параметры¶
При передаче списка в качестве аргумента передается ссылка на список, а не его копия. А поскольку списки изменяемы, то изменение параметра внутри функции означает также и изменение аргумента. Например, функция ниже принимает список в качестве аргумента и умножает каждый элемент списка на 2:
Если поместить функцию double_stuff в файл ch08.py , то сможем так протестировать ее:
Параметр a_list и переменная things являются альтернативными именами одного и того же объекта.
Если функция модифицирует списочный параметр, изменения будут видны в вызывающем коде.
8.16. Чистые и модифицирующие функции¶
Функции, которые принимают списки как аргументы и изменяют эти списки в ходе выполнения, называются модифицирующими, а изменения, которые они делают, называются побочным эффектом.
Чистая функция не производит побочных эффектов. Все ее связи с вызывающей программой сводятся к параметрам, которых она не изменяет, и возвращаемому значению. Вот чистая функция double_stuff :
Эта версия double_stuff не изменяет своих аргументов:
При использовании чистой функции double_stuff , для изменения things вам понадобится присвоить возвращаемое значение things :
8.17. Какая функция лучше?¶
Все, что можно сделать с помощью модифицирующих функций, может быть сделано и при помощи чистых функций. На самом деле, некоторые языки программирования поддерживают только чистые функции. Есть мнение, что программы, использующие только чистые функции, быстрее разрабатывать и в них закрадывается меньше ошибок. И все же, иногда модифицирующие функции удобны, а в отдельных случаях программы с чистыми функциями менее эффективны.
Вообще, мы рекомендуем писать чистые функции всегда, когда это разумно, и прибегать к модифицирующим только в случаях, когда их использование дает несомненное преимущество.
8.18. Вложенные списки¶
Вложенный список — это список, являющийся элементом другого списка. В следующем списке элемент с индексом 3 есть вложенный список:
Если ввести nested[3] , то получим [10, 20] . Извлечь элемент из вложенного списка можно за два шага:
А можно объединить эти два шага в выражение:
Оператор квадратная скобка вычисляется слева направо, так что это выражение берет 3-й элемент списка nested и извлекает из него 1-ый элемент.
8.19. Матрицы¶
Вложенные списки часто используют для того, чтобы представлять матрицы. Например, матрицу

можно представить так:
matrix есть список из трех элементов, в котором каждый элемент задает строку матрицы. Можно получить целую строку матрицы, как обычно:
Также можно извлечь отдельный элемент матрицы, используя два индекса:
Первый индекс выбирает строку, а второй — столбец. Хотя этот способ представления матриц самый распространенный, он не является единственным. Его вариацией является список столбцов вместо списка строк. Позднее мы увидим более радикальную альтернативу, использующую словарь.
8.20. Разработка через тестирование¶
Разработка через тестирование (англ.: Test-driven development, TDD) — это практика разработки программ, в которой программа создается серией небольших итераций, в каждой из которых сначала пишутся автоматические тесты, а затем код, реализующий тестируемую функциональность. От итерации к итерации растет реализованная и оттестированная функциональность.
Продемонстрируем разработку через тестирование с помощью доктестов. Скажем, нам нужна функция, которая создает матрицу с rows строк и columns столбцов, принимая аргументы для rows и columns .
Сначала подготовим тест для этой функции в файле matrices.py :
Выполнив скрипт, видим, что тест не прошел:
Тест не проходит, поскольку тело функции не содержит ничего, кроме строки в тройных кавычках, и поэтому возвращает None . Наш тест требует, чтобы функция возвращала матрицу 3 x 5, заполненную нулями.
Правила разработки через тестирование говорят, что для начала нужно написать самый простой вариант, который бы удовлетворял тест. Так что, в этом случае, просто вернем ожидаемый результат:
Теперь при выполнении скрипта тест успешно проходит, но наша реализация make_matrix всегда возвращает один и тот же результат, а это явно не то, что имелось в виду. В качестве мотивации дальнейших улучшений добавим тест:
Как и следовало ожидать, тест не проходит:
Этот процесс называется разработкой через тестирование, потому что код пишется только тогда, когда имеется тест, который не проходит. Мотивированные последним тестом, теперь напишем более общее решение:
Это решение, похоже, работает, поскольку тесты проходят. Однако, начав пользоваться новой функцией, быстро обнаружим баг:
Мы хотели присвоить значение 7 элементу во второй строке и третьем столбце, но, вместо этого, значение 7 получили все элементы третьего столбца!
По размышлении становится ясно, что в нашем текущем решении каждая строка матрицы — всего лишь ссылка на один и тот же список. Это определенно не то, что нам нужно. Приступая к исправлению бага, сначала напишем тест, демонстрирующий наличие этого бага:
Теперь, имея тест, который демонстрирует баг в программе, мы должны найти лучшее решение:
Процесс разработки через тестирование имеет ряд преимуществ. Этот процесс:
- заставляет конкретно думать о задаче, которую нужно решить, прежде чем пытаться ее решить,
- поощряет разбивать сложные задачи на более мелкие и простые, и пошагово приближаться к решению задачи в целом,
- дает набор автоматических тестов для программы, облегчая внесение в нее изменений и дополнений в дальнейшем.
8.21. Строки и списки¶
В Python есть функция list , которая принимает значение некоторой последовательности как аргумент и создает список из ее элементов.
Также существует функция str , которая берет любое значение Python как аргумент и возвращает его строковое представление.
Как видно из последнего примера, с помощью str не удается соединить вместе элементы списка символов. Это можно сделать с помощью функции join из модуля string :
Две очень полезные функции модуля string имеют дело со списками строк. Функция split разбивает строку на слова, возвращая список слов. По умолчанию считается, что слова отделяются друг от друга одним или более пробельными символами:
Необязательный аргумент позволяет указать, какие символы считать разделителями слов. В следующем примере в качестве разделителя указана последовательность двух символов ai :
Заметьте, что ai не попадает в список.
Функция string.join делает обратное функции string.split . Она принимает два аргумента: список строк и разделитель, который будет разделять элементы списка в результирующей строке.
8.22. Глоссарий¶
8.23. Упражнения¶
Напишите цикл, который обходит список
и выводит длину каждого элемента. Что происходит, когда вы передаете целое число функции len ? Замените 1 на 'one' и выполните вашу программу снова.
Создайте файл ch08e02.py следующего содержания:
Добавляйте следующие наборы доктестов в докстроку в начале файла и пишите код Python, который обеспечит прохождение доктестов. Добавляйте по одному набору доктестов за один раз.
Понимание списочных представлений в Python 3
List comprehensions предлагает краткий способ созданияlists на основе существующих списков. При использовании понимания списков списки могут быть построены с использованием любыхiterable, включаяstrings иtuples.
Синтаксически списки состоят из итерации, содержащей выражение, за которым следует предложение for . За этим могут следовать дополнительные предложения for или if , поэтому знакомство сfor loops иconditional statements поможет вам лучше понять смысл списка.
Компоненты списков предоставляют альтернативный синтаксис созданию списков и других последовательныхdata types. В то время как другие методы итерации, такие как циклы for , также могут использоваться для создания списков, понимание списков может быть предпочтительным, поскольку оно может ограничивать количество строк, используемых в вашей программе.
Список Пониманий
В Python списки понимаются так:
Список, или другой итерируемый, присваивается переменной. Дополнительные переменные, которые обозначают элементы в итерируемом объекте, создаются вокруг предложения for . Ключевое слово in используется, как и в циклах for , для перебора iterable .
Давайте рассмотрим пример, который создает список на основе строки:
Здесь новый список присваивается переменной shark_letters , а letter используется для замены элементов, содержащихся в повторяемой строке ‘shark’ .
Чтобы подтвердить, как выглядит новый список shark_letters , мы вызываем его в print() и получаем следующий вывод:
Список, который мы создали с пониманием списка, состоит из элементов в строке ‘shark’ , то есть по одной строке для каждой буквы.
Компоненты списка можно переписать как циклы for , хотя не каждый цикл for можно переписать как понимание списка.
Используя наше понимание списка, которое создало список shark_letters выше, давайте перепишем его как цикл for . Это может помочь нам лучше понять, как работает понимание списка.
При создании списка с помощью цикла for переменная, назначенная списку, должна быть инициализирована пустым списком, как это находится в первой строке нашего блока кода. Затем цикл for выполняет итерацию по элементу, используя переменную letter в повторяемой строке ‘shark’ . В цикле for каждый элемент в строке равенadded to the list with the list.append(x) method.
Переписав понимание списка в виде цикла for , мы получим тот же результат:
Компоненты списков могут быть переписаны как циклы for , а некоторые циклы for могут быть переписаны в виде списков, чтобы сделать код более лаконичным.
Использование условных выражений со списками
При составлении списков можно использовать условные операторы для изменения существующих списков или других последовательных типов данных при создании новых списков.
Давайте посмотрим на пример оператора if , используемого в понимании списка:
Понимание списка использует кортеж fish_tuple как основу для нового списка под названием fish_list . Ключевые слова for и in используются, как и вsection above, а теперь добавлен оператор if . Оператор if говорит, что нужно добавлять только те элементы, которые не эквивалентны строке ‘octopus’ , поэтому новый список принимает только элементы из кортежа, которые не соответствуют ‘octopus’ .
Когда мы запустим это, мы увидим, что fish_list содержит те же строковые элементы, что и fish_tuple , за исключением того факта, что строка ‘octopus’ была опущена:
Поэтому в нашем новом списке есть каждый элемент исходного кортежа, кроме строки, которая исключена условным оператором.
Создаваемый список number_list будет заполнен квадратами значений каждого элемента в диапазоне от 0 до 9if, значение элемента делится на 2. Вывод следующий:
Чтобы еще немного разобраться в том, что делает список, давайте подумаем о том, что было бы напечатано, если бы мы просто вызывали x for x in range(10) . Наша маленькая программа и выходные данные будут выглядеть так:
Функциональное программирование на Питоне
Функциональное программирование является одной из парадигм, поддерживаемых языком программирования Python. Основными предпосылками для полноценного функционального программирования в Python являются: функции высших порядков, развитые средства обработки списков, рекурсия, возможность организации ленивых вычислений. Элементы функционального программирования в Питоне могут быть полезны любому программисту, так как позволяют гармонично сочетать выразительную мощность этого подхода с другими подходами.
Содержание
Возможности
Определение и использование функции
Функция в Питоне может быть определена с помощью оператора def или лямбда-выражением. Следующие операторы эквивалентны:
В определении функции фигурируют формальные аргументы. Некоторые из них могут иметь значения по умолчанию. Все аргументы со значениями по умолчанию следуют после аргументов без значений по умолчанию.
При вызове функции задаются фактические аргументы. Например:
В начале идут позиционные аргументы. Они сопоставляются с именами формальных аргументов по порядку. Затем следуют именованные аргументы. Они сопоставляются по именам и могут быть заданы в вызове функции в любом порядке. Разумеется, все аргументы без значений по умолчанию должны быть заданы. Повторы в именах аргументов недопустимы.
Функция всегда возвращает только одно значение (или None , если значение не задано в операторе return или этот оператор не встречен по достижении конца определения функции). Однако, это незначительное ограничение, так как возвращаемым значением может быть кортеж.
Определив функцию с помощью лямбда-выражения, можно тут же ее использовать:
Лямбда-выражения удобны для определения не очень сложных функций, которые передаются затем другим функциям.
Функции в Python являются объектами первого класса, то есть, они могут употребляться в программе наравне с объектами других типов данных.
Списочные выражения
Списочные выражения (list comprehension, иногда используется термин «списковые включения») — наиболее выразительное из функциональных средств Питона. Например, для вычисления списка квадратов натуральных чисел, меньших 10, можно использовать выражение:
Встроенные функции высших порядков
В Питоне есть функции, одним из аргументом которых являются другие функции: map() , filter() , reduce() , apply() .
Функция map() позволяет обрабатывать одну или несколько последовательностей с помощью заданной функции:
Аналогичного (только при одинаковой длине списков) результата можно добиться с помощью списочных выражений:
filter()
Функция filter() позволяет фильтровать значения последовательности. В результирующем списке только те значения, для которых значение функции для элемента истинно:
Тоже самое с помощью списковых выражений:
reduce()
Для организации цепочечных вычислений в списке можно использовать функцию reduce() . Например, произведение элементов списка может быть вычислено так:
Вычисления происходят в следующем порядке:
Цепочка вызовов связывается с помощью промежуточного результата ( res ). Если список пустой, просто используется третий параметр (в случае произведения нуля множителей это 1):
Разумеется, промежуточный результат необязательно число. Это может быть любой другой тип данных, в том числе и список. Следующий пример показывает реверс списка:
Для наиболее распространенных операций в Питоне есть встроенные функции:
В Python 3 встроенной функции reduce() нет, но её можно найти в модуле functools .
apply()
Функция для применения другой функции к позиционным и именованным аргументам, заданным списком и словарем соответственно:
В последних версиях Питона вместо функции apply() можно применить специальный синтаксис (в продолжение примера выше):
Замыкания
Функции, определяемые внутри других функций, представляют собой полноценные замыкания (англ. closures ):
Итераторы
Другие средства функционального программирования доступны из стандартной библиотеки (например, модуль itertools ) и других библиотек.
Следующий пример иллюстрирует применение перечисляющего и сортирующего итераторов (итератор не может быть напечатан оператором print , поэтому оставшиеся в нем значения были помещены в список):
Следующий пример иллюстрирует использование модуля itertools :
В следующем примере иллюстрируется функция groupby (группировать по), с помощью которой порождается список пар значение ключа и соответствующий ключу итератор (в этот итератор собраны все значения исходного списка с одинаковым значением ключа). В примере ключом является True или False в зависимости от положительности значения. (Для целей вывода каждый итератор превращается в список).
В модуле itertools есть и другие функции для работы с итераторами, позволяющие кратко (в функциональном стиле) и с вычислительной точки зрения — эффективно — выразить требуемые процессы обработки списков.
Модуль functools
В Python 2.5 появился модуль functools и в частности возможность частичного применения функций:
(Частичное применение функций также можно реализовать с помощью замыканий или функторов )
Ленивые вычисления
Ленивые вычисления можно организовать в Python несколькими способами, используя различные механизмы:
- простейшие логические операции or и and не вычисляют второй операнд, если результат определяется первым операндом
- лямбда-выражения
- определенные пользователем классы с ленивой логикой вычислений [1] или функторы
- Генераторы и генераторные выражения
- (Python 2.5) if-выражение имеет «ленивую» семантику (вычисляется только тот операнд, который нужен)
Пример, который иллюстрирует работу if-выражения. С помощью оператора print можно проследить, какие функции реально вызывались:
Некоторые примеры из книги рецептов:
- Ленивая сортировка
- Ленивый обход графа
- Ленивое вычисление свойств
- Карринг
Функторы
Функторами называют объекты, синтаксически подобные функциям, то есть поддерживающие операцию вызова. Для определения функтора нужно перегрузить оператор () с помощью метода __call__ . В Python функторы полностью аналогичны функциям, за исключением специальных атрибутов ( func_code и некоторых других). Например, функторы можно передавать в качестве функций обратного вызова (callback) в С-код. Функторы позволяют заменить некоторые приёмы, связанные с использованием замыкания, статических переменных и т. п.
Ниже представлено замыкание и эквивалентный ему функтор:
Следует отметить, что код, использующий замыкание, будет исполнятся быстрее, чем код с функтором. Это связанно с необходимостью получения атрибута val у переменной self (т.е. функтор проделывает на одну Python операцию больше). Также функторы нельзя использовать для создания декораторов с параметрами. С другой стороны, функторам доступны все возможности ООП в Python, что делает их очень полезными для функционального программирования. Например, можно написать функтор, который будет «запоминать» исполняемые над ним операции и затем повторять их. Для этого достаточно соответствующим образом перегрузить специальные методы.
Функторы привносят в Python возможность ленивых вычислений, присущую функциональным языкам: вместо вычисления результата выражения — динамическое определение новых функций комбинированием имеющихся.
Определенный подобным образом функтор создает значительные накладные расходы, так как при каждом вызове проходит по вызовам всех вложенных lambda . Можно оптимизировать функтор, применив технику генерирования байткода во время исполнения. Соответствующий пример и тесты на скорость есть в Примерах Python программ. При использовании этой техники скорость исполнения не будет отличаться от «статического» кода (если не считать времени, требующегося на однократное конструирование результирующей функции). Вместо байтокода Python можно генерировать на выходе, например, код на языке программирования C, других языках программирования или XML-файлы.
Несмотря на накладные расходы, ленивое вычисление может дать заметный выигрыш в скорости в случаях, когда действия, оборачиваемые ленивым функтором, достаточно дороги — например, включают объёмные вычисления или доступ к диску. Предположим некоторый промежуточный результат X лениво вычисляется перед условным оператором; для него будет создана цепочка функторов. В той ветке условного оператора, где значение X не требуется по ходу вычисления, эта цепочка функторов будет просто отброшена, не приведя к дорогостоящему вычислению. В другой ветке, где X требуется для вычисления конечного результата функции, цепочка функторов произведёт его вычисление. При этом программисту не нужно отслеживать, в какой из веток алгоритма значение может не потребоваться: он может рассчитывать, что дорогостоящее вычисление X произойдёт только тогда, когда его результат не будет отброшен.
CoffeeScript: Подробное руководство по циклам

Как известно, CoffeeScriptпредлагает несколько иной набор управляющих конструкций, нежели JavaScript.
Не смотря на то, что разработчики языка максимально упростили грамматику и дали подробное описание всех инструкций, сделать более или менее нестандартный цикл для многих остается большой сложностью.
В этой статье я попытаюсь максимально подробно рассказать о принципах работы с циклами в CoffeeScript и тесно связанными с ними управляющими конструкциями.
Весь код сопровождается сравнительными примерами на JavaScript.
Инструкция for-in
Начнем с самого простого цикла for:
В CoffeeScript он будет записан так:
Для определения количества итераций используются диапазоны.
В нашем случае, диапазон от 0. 10 означает: выполнить 10 итераций цикла.
Но как быть если требуется задать условие типа i <= 10?
На первый взляд ничего не изменилось, но если присмотреться, то можно заметить, что в диапазоне одной точкой стало меньше.
В итоге, мы получим следующую запись:
Если начальное значение диапазона больше конечного [10..0], то мы получим обратный цикл с инвертированным результатом:
Хочу заметить, также допустимо использование отрицательных значений диапазона:
А так, можно заполнить массив отрицательными значениями:
Теперь рассмотрим реальную ситуацию, на примере функции которая, вычисляет факториал числа n:
JavaScript:
CoffeeScript:
Как видно из примера выше, код на CoffeeScript более компактный и читабельный по сравнению с JavaScript.
Однако и этот код можно немного упростить:
В этой статье, это не последний пример вычисления факториала, более эффективные способы будет рассмотрены чуть позже.
Позволю себе немного отступится от темы и упомянуть еще один интересный момент связанный с применением конструкции [. ] (slice).
Иногда к чужом коде можно встретить примерно такую конструкцию:
Что в конечно счете будет означать следующее:
На первый взгляд, отличить диапазоны от слайсов довольно сложно. Основных отличий два:
Во-первых, в слайсах можно пропустить одно крайнее значение
Здесь мне бы хотелось обратить особое внимание на то, что мы получим после трансляции этого выражения:
Это может быть удобно, например, для получения списка аргументов функции:
Хочу заметить, что в CoffeeScript для получения списка аргументов функции есть более безопасный и изящный вариант (splats):
Также допустимо использование арифметических и логических операций:
Во-вторых, перед слайсами допустимо наличие объекта
В-третьих, в слайсах допустимо использование перечислений
В этом примере выполняется простая операция конкатенации:
Более полезный пример:
List comprehension
Наиболее яркой синтаксической конструкцией для работы с объектами в CoffeeScript, являются списочные выражения (List comprehension).
Пример того, как можно получить список всех вычислений факториала от 1 до n:
Теперь давайте рассмотрим более интересный пример и выведем список первых пяти членов объекта location:
На JavaScript этот код выглядел бы так:
Для того чтобы вывести список элементов (не индексов) массива нужно задать еще один параметр:
C одной стороны, списочные выражения представляет собой очень эффективный и компактный способ для работы с объектами. С другой стороны, нужно четко представлять какой код будет получен после трансляции в JavaScript.
К примеру, код выше, который выводит список элементов от 0 до 2, более эффективно можно переписать так:
На этом месте прошу обратить особое внимание на пробел перед между именем метода и открывающей скобкой. Наличие этого пробела обязательно!
Если пропустить пробел, то мы получим следующее:
Теперь, вам наверное интересно узнать почему вариант с методом .filter() оказался наиболее предпочтителен?
Дело в том, что когда мы используем инструкцию for-of, транслятор подставляет более медленный вариант цикла чем требуется, а именно for-in:
Результат трансляции:
Скажем прямо, итоговый код ужасен.
Теперь давайте посмотрим на код полученный при использовании метода filter:
Как видите, мы получили идеальный и эффективный код!
Если вы используете CoffeeScript на сервере, то вам не о чем беспокоится, ели нет, то стоит помнить, что IE9- не поддерживает метод filter. Поэтому вы сами должны позаботиться о его наличии!
Оператор then
Как известно, для интерпретации выражений, парсер CoffeeScript анализирует отступы, переводы строк, символы возврата каретки и пр.
Ниже представлен типичный цикл для возведения чисел от 1 до n в степень двойки:
Для наглядности, мы использовали перевод строки и отступ.
Однако в реальной ситуации, большинство разработчиков предпочтут записать это выражение в одну строчку:
В инструкциях while, if/else, и switch/when оператор then указывает анализатору на разделение выражений.
Оператор by
До этого момента мы рассматривали только «простые» циклы, сейчас пора поговорить о циклах с пропусками значений в определенный шаг.
Выведем только четные числа от 2 до 10:
На JavaScript этот код выглядел бы так:
Опрератор by применяется к диапазону элементов, в которых можно установить шаг итерации.
Также мы можем работать не только с числами или элементами массива, но и со строками:
Операторы by и then могут примеятся совместно:
Хотя этот пример немного надуман и в реальной ситуации шаг «в один» слудует упостить, тем не менее совместная работа операторов by-then позволяет писать очень компактный и эффективный код.
Оператор own
В JavaScript довольно часто используется метод .hasOwnProperty(), который в отличии от оператора in не проверяет свойства в цепочке прототипов объекта:
Рассмотрим пример использования метод .hasOwnProperty() в теле цикла for-in:
Несмотря на то, что мы добавили метод .toString() в прототип объекта object, в теле цикла он перечислен не будет. Хотя к нему можно обратиться напрямую:
В CoffeeScript для этих целей предусмотрен специальный оператор own:
Если нужно использовать второй ключ инструкции for-of , то достаточно его указать через запятую, при этом добавлять еще раз оператор own не нужно:
Условные операторы if/else
Сейчас мне бы хотелось обратить внимаение на один очень важный момент, который связан с совместным использованием циклов с операторами if/else.
Иногда в JavaScript приложениях мы можем встретить подобный код:
Не будем обсуждать и тем более осуждать разработчиков, которые так пишут.
Для нас представляет интерес только как корректно записать выражение в CoffeeScript.
Первое что приходит в голову, сделать так:
Прекрасно. однако согласно правилам лексического анализа CoffeeScriptперед инструкцией if будет обнаружено неожидаемое значение терминала, что приведет к ошибке парсинга!
Из предыдущего материала мы помним, что записать выражение в одну строчку мы можем реализовать с помощью оператора then:
Однако и это не помогло, мы снова видим ошибку парсинга.
Давайте попробуем разобраться.
Дело в том, что инструкция if подчиняется тем же правилам, что и другие инструкции, для которых возможно применение оператора then. А именно, для того чтобы наше выражение правильно распарсилось нужно после выражения с if добавить еще раз оператор then:
Таким образом мы получим следующий код:
Иногда бывают ситуации, когда перед циклом нужно проверить выполнение к.л. условия:
Благодаря использованию недетерминированной обработки данных и списочным выражениям, наш код мы можем представить следующим образом:
Обратитие внимание, что то в этом случае мы не стали использовать опрератор then, при этом никаких ошибок парсинга не произошло!
Условный оператор when
Мы уже рассмотрели операторы by и then, настало время поговорить о следующем операторе в нашем списке, а именно об условном операторе when.
И начнем мы пожалуй с коррекции предыдущего примера:
В этом случае, кода получился немного больше в чем предыдущем варианте, однако он приобрел куда больше выразительности и смысла.
Давайте рассмотрим еще один пример, как можно вывести порядок чисел от 1 до 10 по модулю натурального числа n:
После трансляции в JavaScript код:
Как видите использование оператора when дает нам еще больше возможностей для работы с массивами.
Инструкция for-of
Вы уже видели примеры использования инструкции for-of, когда рассматривали списочные выражения. Теперь давайте более подробно познакомимся с инструкцией for-of, которая наряду с for-in позволяет перебирать свойства объекта.
Давайте сразу проведем сравнительную аналогию с инструкцией for-in в JavaScript:
Как видите для получения значения свойств объекта мы использовали следующий синтаксис: object[i].
В CoffeeScript же, все проще, во-первых мы можем получить значение объекта используя списочные выражения:
Во-вторых, для более сложных выражений мы можем применить более разверную нотацию с применением уже знакомых нам операторов:
В JavaScript тот же результат можно получить так:
Еще один пример эффективного использования for-in:
Напомню, что самым эффективным способом получения списка свойств объекта, явлется метод keys():
Для того чтобы получить значения свойств, метод keys() нужно использовать совместно с методом map():
Инструкция while
По мимо инструкций for-of/in в CoffeeScript также реализована инструкция while.
Когда мы рассматривали инструкцию for-in, я обещал показать еще более эффективный способ вычисления фактриала числа n, время как раз подходящее:
На вскидку хочу добавить, что самое элегантное решение вычисления факториала следующее:
Инструкция loop
На этой инструкции мы не будем долго останавливаться, потому что единственное ее назначение это создание бесконечного цикла:
Инструкция until
Инструкция until аналогична инструкуции while, за одиним исключением, что в выражение добавляется отрицание.
Это может быть полезно например для нахождения следующей позиции набора символов в строке:
Как видно из примера, цикл выполняется до тех пока результат выражения не станет равен нулю.
Инструкция do-while
Скажу сразу, что в CoffeeScript отсутствует реализация инструкции do-while. Однко с помощью нехитрых манипуляций эмитировать ее частичное поведение можно с помощью инструкции loop:
Методы массивов (filter, forEach, map и пр.)
Как известно в CoffeeScript доступны абсолютно все те же методы, что и в JavaSctipt.
Разбирать всю эту группу методов нет смысла, рассмотрим лишь общий принцип работы на примере метода map().
Создадим массив из трех элементов и возведем каждый из них в квадрат:
Рассмотрим еще один пример:
Вторым аргументом, метод map принимает контекст вызова:
Как видите this внутри map указывает на Window, чтобы сменить контекст вызова, сделать это нужно явно:
В CoffeeScript для этой цели предназначен специальный оператор =>:
Иными словами используйте эти методы массивов максимально, где это только возможно.
Кроссбраузерную реализация этих методов я разместил на github’e
Реальный пример использования методов map и filter в CoffeeScript, также можно посмотреть в одном из моих проектов на github’e
Инструкция do / Замыкания
Как известно, в JavaScript активно используются замыкания, при этом CoffeeScript тоже не лишает нас этого удовольствия.
Для создания анонимной самозавязывающейся функции в CoffeeScript используется инструкция do, котороая принимает произвольное число аргументов.
Суть этого кода сводится к заполнению массива. При этом, элементы массива содержат не элементарные значения, а функции, каждая из которых возвращает индекс своего элемента.
На JavaScript код выглядел бы так:
Вложенные инструкции
Вложенные инструкции особо ничем не отличаются от других инструкций и подчиняются тем же правилам:
Для примера, заполним массив парными элементами от 1 до 3:
Как видите нет ничего сложного!
Возможно у вас появится желание записать это в одну строчку. Что же, давайте теперь попробуем упростить запись:
PS: лично я бы, не стал использовать такую запись, однако, вы должны знать, что так тоже допустимо писать, ведь рано или поздно вам придется работать с чужим кодом.
А что если нужно добавить перед вторым циклом какое-то выражение?
В качестве примера выведем три пары элементов от 0-3:
Это правильный вариант и особо улучшать здесь нечего. Записать все в одну строчку тоже не получится, потому что перед вторым циклом требуется явная идентация. А вот тело цикла можно записать в короткой нотации.
В третьей строке можно использовать как префиксную так и постфиксную форму записи.
ECMASctipt 6
Как вы знаете, в будущем стандарте ECMASctipt 6 планируется имплементировать генераторы, итераторы и возможно списочные выражения. А Firefox уже сейчас поддерживает большую часть драфтового стандарта. И дело в том, что будущий синтаксис ES6 практически более чем полностью не совместим с сегодняшним CoffeeScript.
К примеру инструкция for. of, сейчас носит более общий характер нежели это нужно:
На выходе мы получим следующее извращенство :
Будущий стандарт дает возможность использовать итерацию через объекты, куда проще:
Здорово, не правда ли?
Также будет доступны генераторы выражений:
Возможным станет и такая запись:
Станут доступными итераторы:
Итераторы также можно применять совместно с генераторами выражений:
С выходом нового стандарта и многие фишки из CoffeScript перестанут быть таковыми, а разработчикам ядра очевидно предстоит очень много работы, чтобы чтобы удержать «сахарные» позиции. Пожелаем им удачи.