0 added
0 removed
Original
2026-01-01
Modified
2026-02-26
1
<p>Python создавался с прицелом на активное и эффективное использование встроенных коллекций. Поэтому при выборе подхода к решению задач Python склоняет программиста к определенному стилю работы. Сам язык подталкивает использовать императивный стиль в сочетании с<a>процедурным</a>и<a>объектно-ориентированным программированием</a>.</p>
1
<p>Python создавался с прицелом на активное и эффективное использование встроенных коллекций. Поэтому при выборе подхода к решению задач Python склоняет программиста к определенному стилю работы. Сам язык подталкивает использовать императивный стиль в сочетании с<a>процедурным</a>и<a>объектно-ориентированным программированием</a>.</p>
2
<p>Те же коллекции в Python являются объектами и чаще всего модифицируются по месту - отсюда изменяемое состояние. Однако в языке нашлось место и для элементов<a>функционального программирования</a>. Его часто считают подвидом декларативного программирования, потому что функциональный код выглядит как конвейер для преобразования данных.</p>
2
<p>Те же коллекции в Python являются объектами и чаще всего модифицируются по месту - отсюда изменяемое состояние. Однако в языке нашлось место и для элементов<a>функционального программирования</a>. Его часто считают подвидом декларативного программирования, потому что функциональный код выглядит как конвейер для преобразования данных.</p>
3
<p>В этом уроке мы рассмотрим императивный и функциональный код на Python. В обоих случаях мы попробуем вывести на экран отсортированный список уникальных элементов из некоторого набора чисел.</p>
3
<p>В этом уроке мы рассмотрим императивный и функциональный код на Python. В обоих случаях мы попробуем вывести на экран отсортированный список уникальных элементов из некоторого набора чисел.</p>
4
<h2>Процедурное решение</h2>
4
<h2>Процедурное решение</h2>
5
<p>В процедурном решении программа разбита на подпрограммы, изменяющие состояние вместо возврата значения. Код будет выглядеть так:</p>
5
<p>В процедурном решении программа разбита на подпрограммы, изменяющие состояние вместо возврата значения. Код будет выглядеть так:</p>
6
<p>Это решение достаточно эффективно - оно расходует память только на копию исходного списка, потом сжимает ее и сортирует по месту. У этого решения предсказуемый расход памяти, не зависящий от входных данных. А еще после применения filter_and_sort() данные можно дальше обрабатывать так же по месту, но уже другими процедурами.</p>
6
<p>Это решение достаточно эффективно - оно расходует память только на копию исходного списка, потом сжимает ее и сортирует по месту. У этого решения предсказуемый расход памяти, не зависящий от входных данных. А еще после применения filter_and_sort() данные можно дальше обрабатывать так же по месту, но уже другими процедурами.</p>
7
<p>Этот код эффективен, но заставляет программиста многое держать в уме. Это повышает вероятность ошибки. Например, мы можем забыть получить копию списка INPUT перед началом обработки - тогда мы случайно отсортируем сам исходный список INPUT.</p>
7
<p>Этот код эффективен, но заставляет программиста многое держать в уме. Это повышает вероятность ошибки. Например, мы можем забыть получить копию списка INPUT перед началом обработки - тогда мы случайно отсортируем сам исходный список INPUT.</p>
8
<p>Такое ошибочное изменение не тех сущностей может привести к неприятным последствиям. Причем такие ошибки не лежат на поверхности - программа выведет нужные числа, поэтому ошибка будет не видна с первого взгляда.</p>
8
<p>Такое ошибочное изменение не тех сущностей может привести к неприятным последствиям. Причем такие ошибки не лежат на поверхности - программа выведет нужные числа, поэтому ошибка будет не видна с первого взгляда.</p>
9
<h2>Функциональное решение</h2>
9
<h2>Функциональное решение</h2>
10
<p>Напишем функциональный вариант с элементами декларативного стиля:</p>
10
<p>Напишем функциональный вариант с элементами декларативного стиля:</p>
11
<p>Код описывает, какой результат мы хотим получить:</p>
11
<p>Код описывает, какой результат мы хотим получить:</p>
12
<ul><li>print() - напечатанное</li>
12
<ul><li>print() - напечатанное</li>
13
<li>'\n'.join(...) - объединение через перевод строки</li>
13
<li>'\n'.join(...) - объединение через перевод строки</li>
14
<li>map(str, ...) - последовательность строк</li>
14
<li>map(str, ...) - последовательность строк</li>
15
<li>sorted(set(...))- полученных из отсортированного множества исходных чисел</li>
15
<li>sorted(set(...))- полученных из отсортированного множества исходных чисел</li>
16
</ul><p>Обратите внимание, что в коде нет ни одной переменной. Ничего не нужно защитно копировать, и самого кода гораздо меньше.</p>
16
</ul><p>Обратите внимание, что в коде нет ни одной переменной. Ничего не нужно защитно копировать, и самого кода гораздо меньше.</p>
17
<p>Попробуем проанализировать, как этот код работает:</p>
17
<p>Попробуем проанализировать, как этот код работает:</p>
18
<ul><li>Создается промежуточное множество set()</li>
18
<ul><li>Создается промежуточное множество set()</li>
19
<li>Внутри sorted() создается список - не обычный list(), но все равно копия</li>
19
<li>Внутри sorted() создается список - не обычный list(), но все равно копия</li>
20
<li>Для каждого итогового числа будет создано по строке</li>
20
<li>Для каждого итогового числа будет создано по строке</li>
21
<li>Функция '\n'.join() соберет строку, размер которой будет равен сумме длин всех строк с числами и количеству переводов строк</li>
21
<li>Функция '\n'.join() соберет строку, размер которой будет равен сумме длин всех строк с числами и количеству переводов строк</li>
22
</ul><p>Как видите, за лаконичность кода приходится платить ресурсами.</p>
22
</ul><p>Как видите, за лаконичность кода приходится платить ресурсами.</p>
23
<p>Даже если исходный список содержит не слишком много повторов, то промежуточные структуры займут в памяти в разы больше места, чем исходный список. Так происходит из-за множества промежуточных строк, которые заметно тяжелее исходных чисел. Получается, что потребление памяти сильно зависит от состава входных данных.</p>
23
<p>Даже если исходный список содержит не слишком много повторов, то промежуточные структуры займут в памяти в разы больше места, чем исходный список. Так происходит из-за множества промежуточных строк, которые заметно тяжелее исходных чисел. Получается, что потребление памяти сильно зависит от состава входных данных.</p>
24
<h2>Истина где-то посередине</h2>
24
<h2>Истина где-то посередине</h2>
25
<p>Функциональное решение читается хорошо, если разработчик в принципе знаком с таким стилем. Читаемость кода - важная характеристика, поэтому при небольшом объеме входных данных можно выбирать такой вариант.</p>
25
<p>Функциональное решение читается хорошо, если разработчик в принципе знаком с таким стилем. Читаемость кода - важная характеристика, поэтому при небольшом объеме входных данных можно выбирать такой вариант.</p>
26
<p>Процедурное решение читается достаточно тяжело: нужно буквально выполнять код в уме. Еще в такой код сложнее вносить изменения - он слишком специфичен и завязан на текущую задачу. А еще нужно помнить, что процедура filter_and_sort() модифицирует свой аргумент.</p>
26
<p>Процедурное решение читается достаточно тяжело: нужно буквально выполнять код в уме. Еще в такой код сложнее вносить изменения - он слишком специфичен и завязан на текущую задачу. А еще нужно помнить, что процедура filter_and_sort() модифицирует свой аргумент.</p>
27
<p>Зато процедурный код работает эффективно и предсказуемо. Если у вас много данных с небольшим количеством повторов, то этот вариант может оказаться предпочтительным. Однако оба решения можно и улучшить, немного отступив от парадигмы.</p>
27
<p>Зато процедурный код работает эффективно и предсказуемо. Если у вас много данных с небольшим количеством повторов, то этот вариант может оказаться предпочтительным. Однако оба решения можно и улучшить, немного отступив от парадигмы.</p>
28
<p>Например, в функциональном решении можно применить цикл:</p>
28
<p>Например, в функциональном решении можно применить цикл:</p>
29
<p>Этот код не создает промежуточных строк, потому что команда print() и так умеет выводить числа. Большая строка не склеивается перед выводом - это самая заметная экономия.</p>
29
<p>Этот код не создает промежуточных строк, потому что команда print() и так умеет выводить числа. Большая строка не склеивается перед выводом - это самая заметная экономия.</p>
30
<p>Усложним ситуацию и представим, что все входные элементы уникальны. В таком случае множество будет копией исходного списка - внутри sorted() будет еще одна копия. В итоге у нас будет две копии.</p>
30
<p>Усложним ситуацию и представим, что все входные элементы уникальны. В таком случае множество будет копией исходного списка - внутри sorted() будет еще одна копия. В итоге у нас будет две копии.</p>
31
<p>Посмотрим, как еще можно улучшить императивный вариант:</p>
31
<p>Посмотрим, как еще можно улучшить императивный вариант:</p>
32
<p>Этот вариант читается гораздо лучше, потому что мы избавились от процедуры, которая модифицирует копию исходного списка. Нет нужды копировать входной список явно - неявная копия будет в sorted(). Но теперь, если понадобится еще поработать с результатом сортировки перед печатью, то придется накопить значения в промежуточный список.</p>
32
<p>Этот вариант читается гораздо лучше, потому что мы избавились от процедуры, которая модифицирует копию исходного списка. Нет нужды копировать входной список явно - неявная копия будет в sorted(). Но теперь, если понадобится еще поработать с результатом сортировки перед печатью, то придется накопить значения в промежуточный список.</p>
33
<p>В большинстве случаев программисты на Python выбирают функциональный подход и дорабатывают его. Так они приходят к балансу - код все еще остается достаточно компактным, и работает вполне эффективно.</p>
33
<p>В большинстве случаев программисты на Python выбирают функциональный подход и дорабатывают его. Так они приходят к балансу - код все еще остается достаточно компактным, и работает вполне эффективно.</p>