![]() | ![]() | ![]() | |||||||||||
![]() |
|
||||||||||||
![]() | ![]() | ![]() | |||||||||||||||
![]() |
|
||||||||||||||||

Техническая поддержка
ONLINE
![]() | ![]() | ![]() | |||||||||||||||||
![]() |
|
||||||||||||||||||
4 совета как ЛУЧШЕ писать циклы For на Python
ruticker 04.03.2025 15:24:41 Текст распознан YouScriptor с канала ZProger [ IT ]
распознано с видео на ютубе сервисом YouScriptor.com, читайте дальше по ссылке 4 совета как ЛУЧШЕ писать циклы For на Python
В данном видео я покажу очень плохие записи цикла `for`, которые многие используют. Я бы даже сказал, 90% разработчиков пишут код примерно вот так, как показано здесь. Проблема в том, что данная запись очень медленная. В этом видео я покажу альтернативную запись для каждого из примеров. Причём у нас будет не только четыре примера, а в каждом файле у нас будет несколько сравнений. Самое интересное, что мой подход, во-первых, намного короче именно по строчкам кода, а во-вторых, он в два раза быстрее. То есть вот такая запись будет выполняться в два раза дольше, чем та, которую я покажу. Также многие делают видео, где показывают похожие примеры, но они просто рассказывают, как это всё применяется. Например, вместо цикла `for` можно использовать другие технологии, но не показывают на практике. В данном же видео я буду использовать `perf Counter`. Я вам реально покажу, что это выполняется ровно в два раза дольше. Итак, самое первое, что мы делаем — это создаём список из миллиона значений. Далее мы бежим по каждому элементу списка и добавляем всё это. В итоге получаем сумму всех чисел. Но это плохой пример, потому что у нас есть встроенная функция, которая принимает итератор и далее получает сумму всех чисел. Эта функция намного сильнее оптимизирована, чем тот же цикл `for`, потому что цикл `for` в большинстве случаев работает очень медленно. В данном же примере это всего лишь две строчки по сравнению с данным количеством. Но также нас интересует производительность: насколько данная запись выполняется быстрее, чем следующая запись, и выполняется ли она быстрее. Возможно, циклы всё же будут быстрее следующей записи. Итак, я добавляю всем известный `perf Counter`, далее запускаю изначально циклы и функцию для получения суммы из чисел, получаю текущее время и делаю вычитание от изначального времени, то есть время перед запуском нашей функции. В итоге я получаю одинаковое число. Это говорит о том, что операции выполняются полностью идентично, и при этом у нас здесь время выполнения 0,76 секунды, здесь 43. Во-первых, в два раза меньше кода, во-вторых, в два раза быстрее скорость. Это первый пример, когда не стоит использовать циклы `for`, и лучше использовать встроенные функции. Но у нас ещё целых три примера, причём второй пример у нас будет вместе с байт-кодом. Я на примере байт-кода покажу, почему так происходит и покажу очень странные примеры с циклами. Итак, следующая очень жёсткая ошибка — это доступ по индексам. Это на самом деле очень большая проблема, потому что я вам покажу, как это работает на уровне байт-кода, и вы больше никогда не будете подобно использовать, потому что это очень трудозатратная операция. Итак, кратко: что здесь происходит? Часто новички берут какой-то итерируемый объект, в нашем случае это список, получают его длину и далее помещают в `range`, чтобы у нас попадал индекс. То есть сначала нулевой индекс, потом первый, второй и так далее. Итак, до количества наших значений в данном случае у нас 999.999. Так вот, далее у нас есть переменная `temp`, в которую нужно записывать это значение. Они обращаются по списку и получают данный индекс, чтобы вытащить из списка значения. Так вот, вроде всё нормально, но на деле это не так. Во-первых, зачем вам лишние ресурсы для того, чтобы сначала разбивать это на индексы, а далее помещать значения, если вы можете пробежаться по `numbers` примерно следующим образом? Это только первое, но бывает такое, что вам нужно использовать индексы. То есть, например, вот здесь вы получаете значения, но далее вам нужен индекс, например, для того чтобы изменить значения. Так вот, основное, что я хочу показать в данном примере — это `enumerate`. Возможно, вы замечали на различных сайтах, Telegram-каналах, YouTube-каналах и так далее, где все советуют использовать именно `enumerate`, когда вам нужно получить индекс и также значения. Так вот, почему все советуют именно `enumerate`? Неужели только потому, что это удобнее? Вы можете получить сразу два значения, обращаясь просто к `enumerate`. Так вот, на деле всё не так просто. Дело в том, что `enumerate`, во-первых, более оптимизирован, чем данная конструкция. Итак, он также выполняет меньше байт-кода, и сейчас мы также это проверим, потому что многие не показывают, как она работает, но я же хочу показать это всё более подробно. Итак, для начала мы получим тесты производительности. То есть я покажу скорость выполнения доступа по индексам и также скорость выполнения по `enumerate`. Но также у вас остаётся вопрос: почему именно это работает быстрее? Почему какая-то функция `enumerate` быстрее, чем тот же цикл `for`? Так вот, это тоже мы рассмотрим, но после тестов производительности. Так вот, разница тоже есть. У нас всего лишь миллион значений. Итак, чисто для теста я ставлю уже целых 10 миллионов итераций, получаем примерно следующую разницу. Она не очень критичная, но всё же разница есть. Итак, далее отвечаю на вопрос: почему именно `enumerate` работает быстрее, чем тот же `range` вместе с `len(numbers`)? Так вот, посмотрим, изначально как это работает в виде байт-кода. Выделяем нашу строчку и получаем следующий байт-код. Как вы можете заметить, это примерно следующий код. Давайте также посмотрим на `enumerate`. Это у нас уже следующий код, он уже визуально выглядит намного меньше. Итак, далее слева у нас функция, которая работает быстрее, справа у нас функция `index access`, которая работает медленно. Так вот, я думаю, понятно, почему она работает медленно. Во-первых, посмотрите на количество инструкций. Итак, изначально у нас идёт подгрузка имён — это именно `enumerate`, `numbers`, `len` и также `range` — это функции, которые нам нужно загрузить в наше пространство. Далее идёт выполнение данных функций, также получение итерации, далее сам цикл `for`, и далее самое интересное — это распаковка. То есть в `enumerate` есть распаковка значений, и данные значения помещаются в индекс и также в наши переменные, которые мы с вами записали. Так вот, в `index access` у нас работает примерно всё так же, только у нас, во-первых, выполняется функция два раза, и далее самое интересное — это у нас уже получение значений. В данном случае мы получаем значения и помещаем все их в переменные. Здесь же мы получаем одно значение, используем `jump`, то есть прыжок на восемьдесят четвёртую строчку, то есть опять получаем итерацию заново и опять идём вниз. Именно из-за этого оно работает медленнее, потому что намного больше действий. Следующий пример — это когда вы пытаетесь пробежаться сразу по двум спискам. То есть вы получаете длину одного списка, далее присваиваете в определённую переменную результат списка `A` с данным индексом и также результат списка `B`. Но первая проблема в том, что вы можете выйти за пределы списка. Я даже поставил вот здесь вот `Fix me` метку. Вторая проблема в том, что это медленно и также занимает много места. Но используя новый пример, мы вспоминаем, что у нас есть `zip`, которая делает это всё намного эффективнее. Мы получаем по сути точно такую же логику. То есть у нас используется только `zip` вместо `range` и также `len`, и заметьте, что вот здесь вот мы используем обращение по индексу. То есть это ещё отдельная операция. Вот здесь же мы обращаемся напрямую до значения. Также здесь выполняется `len` и также `range` — это два дополнительных действия. `Zip` же выполняет только одно. Далее также выполняю примеры для того, чтобы получить скорость. И кстати, если вы знаете подобные моменты в Python с циклами `for`, то есть где она работает быстрее либо же медленнее, вы можете написать об этом в комментариях, и я разберу это всё более подробно. Либо же, возможно, у вас есть вопросы, я также на них отвечу и сделаю отдельное видео. Итак, в итоге получаем тоже разницу в скорости, хоть и не такую большую, но всё же она есть. Далее очень жёсткий пример с использованием глобальной переменной. Итак, у нас есть такой список, где содержится покупки пользователей. То есть у нас есть подтверждённая покупка и также сумма, либо же не подтверждённая покупка и также её сумма. Далее мы делаем такую функцию для того, чтобы заполнить наш список и получить миллион таких записей. Далее нам нужно посчитать количество подтверждённых покупок. Первая реализация на циклах. То есть мы просто бежим по нашему списку, получаем на каждой итерации кортеж, и далее нулевое значение — это у нас либо же нет, и первое значение — это у нас сама сумма. Так вот, мы просто проверяем значение ноль. Если оно подтверждено, мы складываем `res` и также первый индекс. Вторая реализация на списках. Мы точно также бежим по `users_buy`, но проверяем всё это вот здесь вот. То есть если же нулевой индекс у нас подтверждён, возвращаем первый индекс и далее всё это суммируем, потому что, как мы помним, это всё работает быстрее, чем делать сложение. И последний пример — это уже с использованием генераторов. То есть здесь у нас `list comprehension`, далее у нас обычный генератор. Многие, кстати, говорят, что генераторы работают всегда быстрее, чем тот же `list comprehension`. Вот сейчас вы сможете увидеть, что это не так. Далее я использую точку входа. Изначально заполняем наш список, добавляем туда миллион значений и далее просто выполняем каждую функцию по отдельности. Итак, в итоге цикл у нас выполняется дольше всего, `list comprehension` выполняется быстрее всего, и тот же генератор, о котором все говорят, что это самая быстрая технология, он выполняется медленнее, чем `list comprehension`. Именно поэтому нужно всё тестировать самому, для того чтобы у вас не появилось ложное представление о том, что какая-то технология работает быстрее, чем другая, только потому что вам кто-то так сказал. Если видео было полезным, то попрошу набрать 1.000 лайков под данным видео. Я готовлю новые примеры, `asyncio` и так далее. Если вы знаете подобные фишки, либо же встречали какие-то странности в Python, то попрошу вас написать об этом в комментариях, потому что это очень полезно. Другие могут тоже об этом узнать, это будет также дополнительный источник информации. Также весь исходный код я оставляю в Telegram-канале, вы сможете всё это протестировать. На канале недавно вышло много крутых видео с ускорением Python, также видео, которое позволяет ускорить ваш проект просто в сотни раз, и также видео со сравнением `requests` и `http`. Оставляю всё это в подсказках. Вы можете перейти и посмотреть.
Залогинтесь, что бы оставить свой комментарий