4 совета как ЛУЧШЕ писать циклы For на Python
ฝัง
- เผยแพร่เมื่อ 23 ม.ค. 2023
- ⭐ Курс ООП и Приват канал: www.zproger-school.com/?...
⭐ Телеграм канал: t.me/+Ke91A0BZ3Y80ZjAx
⭐ Исходный код: t.me/codeblog8/171
4 совета для написания лучших циклов For на Python.
В этом видео мы рассмотрим практические примеры циклов for, и их альтернативные решения, которые будут работать намного быстрее, и при этом сделают ваш код чище.
Мы рассмотрим примеры с enumerate, zip, list comprehension и генераторами Python, и сделаем тесты производительности с примерами байт-кода.
📁 Github: github.com/Zproger
📁 Все плейлисты с уроками: bit.ly/39GaY89
📁 Связаться со мной: zproger777@gmail.com
📁 Поддержать канал: github.com/Zproger/donate
На мой взгляд - если чел использует цикл 'For' вместо 'Sum' то это просто незнание встроенных команд / функций и т.д.
А ведь до этого можно и случайно додуматься, к примеру когда в голову придёт мысль "Хм, ну даже в Microsoft Exel есть SUM. И в паскале на уроках информатики в школе что-то такое вроде тоже было, хоть и в сраку паскаль ибо слишком сложно. А вдруг и здесь тоже получится?"))
(Это моя реальная история из жизни, пхахах))
Я знаю о функциях prod и sum, но практически никогда их не использую, так как в большинстве случаев оказывается что в формуле будет не просто сумма и все равно придется писать цикл.
Да нет ,это дает разнообразие решения задач
@@13-th_Lord паскаль слишком сложно? нууууу... вроде самый простой и логичный язык. синтаксис у него конечно так себе...
В книге «питон.Чистый код» посвящена была эта тема, а функции zip() в первые увидел в книге «Однострочники Питон»
Очень маленькие детали!Очень рад, что автор показывает их
Было бы еще интереснее, если ты показал как итераторы работают, создав свой класс(протокол итераторов)
Затраты памяти ещё сравните у функций с генератором и с листом в 4 примере
хорошая идея, в следующих видео добавлю и затраты ресурсов для наглядности
@@zproger и сразу станет видно, что не в скорости прелесть генератора :-)
@@splinter928 в этом конкретном случае не станет
@@dmitriyneledva4693 Из-за того что немного памяти занимает эти данные?
Пишите на ассемблере
Спасибо. Ценный контент.
Лучший! Спасибо за качественный контент
Спасибо
Не используйте таймер как замер скорости, во 1х это показывает что разницу во времени только на вашей конфигурации, во 2х даже если вы запустите 2 раза или более значения будут отличаться, так как слишком много от чего будет зависеть время выполнения(например в данный момент при замере первого алгоритма у вас был простой а при замере второго алгоритма ос полезла проверять обновления или ещё че и все уже не достоверное отличие). Лучше поищите информацию как замерить сложность алгоритма в Пайтон.
Ну или надо делать пару тысяч замеров по одной и той же задаче и выводить среднее. Тогда будет более-менее близко к правде
Надо использовать модуль timeit, он запускает фрагмент кода n раз (1000000 по умолчанию)
Суть не в том, за какое время выполнится код, суть в том, чтобы увидеть разницу между 2 подходами. Он написал и запустил оба варианта одновременно, а это значит, что замер покажет разницу правильно. Ну, конечно, если вдруг компьютер не запустит все обновления и не начнёт искать вирусы ровно в тот момент, когда ты запускаешь код, получается ровно через 0.7 секунды))
@@beardedman721замер правильно разницу не покажет, но статически значимые отличия гипотез получим именно в этих случаях…
Благодарю! 🔥👍
Рад что понравилось =))
Отличная подача, подписался на канал!
Благодарю
первая задача res = n * (n + 1) // 2 я понимаю что не тема ролика, но молодому программисту обязательно нужно повторять что программист это в первую очередь умный человек который оптимизирует свои алгоритмы поиска решения.
А почему бы не использовать filter + map + sum вместо list comprehension + sum и generator + sum?
А в зависимости от железа один и тот же код, например на разных процессорах может выполняться разное время? Например процессоры с разными наборами инструкции?
Даже более того, в разных версиях питона один код может довольно разное время исполняться.
Спасибо, я очень рад, обьяснение прекрасно.
Благодарю
Привет. Можно спросить не по теме видео?) Я вот пытаюсь парсить крипто сайты, но содержимое формирует JS код. Подскажи куда двигаться, чтобы максимально быстро захватить результат. Пробовал requests-html использовать, но после рендера результат тот же (без подгружаемого контента).
Селениум. Есть бесплатный курс на канале
@@zproger Понял, пасиба. С селениум и seleniumwire знаком. Но думал, что может более быстрые варианты есть.
а что быстрее крутится списки и циклы или pandas когда 1к + данных?
Будет ли разница в скорости или затрате памяти если мы передадим глобальную переменную в функцию типо "def function(переменная)" или через global?
не знаю, нужно тестировать :)
А имеет ли это значение? В любом случае глобальная переменная намекает на проблемы в коде
Поскольку все большие объекты передаются по ссылке, то разницы не должно быть вообще. И то и другое просто ссылка.
@@CrazyElf1971 В питоне даже числа передаются по ссылке.
Привет. Я новый подписчик и всего 2 года изучаю все это. Сейчас перешел на линукс. Расскажи пожалуйста, как ты сделал или собрал себе code oss
Просто говорят, что в коде обычном телеметрия и т.д. Покажешь как такую как у тебя поставить?
А что быстрее работает: списки или кортежи? По кортежам тоже можно сумму находить.
Привет, подскажите пожалуйста какой Линукс дистрибутив используете?
ZorinOS
Спасибо
Если вам нужна производительность очень советую посмотреть в сторону numpy. Разница порой набирается на целый порядок
А просто потому что, numpy написан на С, и некоторые свои функции преобразовывает под тот же С, когда свой код остаётся на питоне.
P.s. Для тех, кто в танке, язык "С" работает в разы быстрее, чем питон.
@@z.dekuch1987 язык "С" не работает "в разы быстрее питона" - скомпилированные программы писанные на С работают быстрее чем скрипты интерпретируемые питоном, и не в разы а на порядки быстрее 😉
Если нужна производительность лучше не писать на python
Numpy + Numba тогда.
И можно оставаться на пайтоне.
Обе библиотеки разрабатывались для научных вычислений, как я понял.
Намба особенно впечатляет, код не усложняет и изучать не нужно.
из "тонкостей" только использование кэша.
Если уж до конца идти, то что в первом примере не сделать sum(range(1_000_000))?
Ну или (1_000_000 * (1_000_000 + 1)) // 2 - 1_000_000?
Доброго времени суток. Какую программу используюте для написания кода?
Vs Code
@@zproger благодарю.
У меня есть 2 функции для деления текста на страницы.
обрезание страницы было реализовано через цикл фор как раз по индексам.
Заменил рейндж лен на енумерейт время выполнения функции возросло в 2 раза
на первой минуте звучит фраза, что sum - принимает итератор, что абсолютно не верно. sum - принимает iterable, т.е итерируемый объект.
Спасибо за исправление, оговорился
Подскажи, что у тебя за дистрибутив и какое графическое окружение стоит ?
Zorin OS + дефолт окружение
Интересно, а паттерн мэтч с вычислимыми полями работает быстрее условных операторов?..
как лучше писать рекурсию: через math, рекурсию, или через фор?
генераторы будут лучше по времени если ожидается получения негативного условия в выражении формирования. Здесь же в примере все значения участвуют, пример в пользу списочного выражения. Но разница все равно копеечная, экономия в памяти все равно выводит выражения генераторы на первое место.
Можно дулать просто list(range(1_000_000))
Или может ваш пример как то лучше?
для первого примера еще можно попробовать через reduce
Крсава! Все четко! Хорошо я так и писал)
Благодарю
Спасибо большое за информацию я только только начинаю спасибо
Пожалуйста, желаю успешного изучения!
А как смотреть этот байткод? Я возможно прослушал в видео
Спасибо большое за видео🙂 Давай побольше такого рода сравнений, а то тема, что python медленный очень сильно засела в башке у людей моего окружения. Вот хотя бы такими видео буду потихоньку развивать у них сомнения
.да и еще, как с использованием enumirate найти в списке максимальное кол-во повторяющихся элементов?
@@mak32 Лучше используйте collections.Counter для этого
Спасибо
Если они пишут на плюсах или расте , мало что у тебя получится, хотя есть синтетические примеры где питон обгонят плюса. Правда там обычно используются написанные отцами на тех плюсах библиотеки питона против банального авторскогобыдлокода на плюсах. Прелесть питона не в скорости, и там где она реально нужна на нем писать никогда не будут, будут на расте. Питон нужен для скорости/дешевизны разработки и поддержки
@@mikeofs1304 проблема в том, что большинство людей используют питон пяткой правой ноги.
Пример из жизни. Либа для аналитики. Питон. Работа с таблицами. Вместо сортировки pandas, которая оптимизирована для этих целей -- пузырек. Нашел случайно, когда на 10+млн строках ноут уже помирал... Как по мне -- такое надо показывать. Т.к. те, кто не знаком с питоном будут решать задачу стандартными алгоритмами, когда есть готовые либы "написанные отцами на тех плюсах" или те же sum(list). Пустяк, но целому отделу аналитики, работающему на тб данных работу ускорит
Почему у тебя в первом примере используется создание списка, если это не так экономно в плане использования памяти, как просто sum(range(1_000_000))?
Я так понял, это чтобы сравнить работу кода уже с готовым произвольным списком. Так то если бы именно такую сумму нужно было посчитать, то так быстрее. А ещё быстрее взять формулу и посчитать эту сумму через пару арифметических действий, без суммирования элементов списка.
На 4:25 сначала один байт код enumirate, а в следующем кадре уже сравнивается с другим байткодом. При этом если сравнить первоначально показанный байткод, то для enumirate это 5 инструкций, а для for это всего 3, если считать от FOR_ITER до JUMP_BACKWARD. Интересно было бы сравнить enumirate с простым циклом for num in numbers с инкремент внутри переменной счётчика i объявленной вне цикла.
Спасибо за видео, хоть и знал про эти методы, но лишним не будет. Про zip вообще уже забыл, когда изучал python пару раз использовал, с тех пор вообще о нем забыл. И кстати в следующий раз декоратор для замера времени используй, чтобы меньше кода было, просто не все сразу поймут просматривая это. А так все чётко.
Какой хороший питон, Я щас с# изучаю, и в начале не мог привыкнуть к этим ;, {}, строгой типизации, void, множество типов данных. Но про питон тоже не забываю.
@@ismailisabekov8424 А почему void прям отдельно? 🤔
@@nakidai потому что не привык задавать типы, к тому же каждый раз писать void, если метод ничего не возвращает. Я привык в питоне к примеру написал функцию которая просто выполняет какую-то задачу, но ничего не возвращает. Думаю, это из-за привычек, и void по началу Я забывал писать чаще, чем те же ;
@@ismailisabekov8424 Изучал С++ школьником, почти 20 лет назад уже. Сейчас всё делаю на питоне. Блин, как же это красиво: строгая типизация у всего в том числе у функий, приватные и публичные члены классов. Мой внутренний перфекционист часто негодует, что этого нет в питоне. Особенно бесит, что содержимое классов можно менять извне.
Это база!
Больше всего обидно, что реьята, которые сейчас монут учиться не учатся, а сразу бегут в разработку
практика самое лучше обучение :)
Спасибо, очень крутое объяснение у тебя!
Все же интересно, почему генератор получился медленнее лист компликейшина...
Возможно сделаю видео с тестами
Потому что плюс генератора в занимаемой памяти, а не в скорости.
Какой у тебя шрифт в ВС коде?
Стандартный, ничего не менял
обычно когда доходит до оптимизации кода, проблема не в том, что цикл работает в 1.5-2 раза медленнее, а в том какими структурами данных ты пользуешься. Но с точки зрения написания кода, да примеры из видео как минимум читать приятнее, потому что Pythonic way
В первом примере можно сократить до print(sum(range(1_000_000)))
Есть такое, об этом писал в телеграмм канале после публикации видео
я конечно нечего ещё не понимаю что тут сказано но было очень интересно
Есть одна проблема, когда без индекса не обойтись - когда вы планируете изменить значения в списке. И enumerate и проход по элементам создают копию, с которой вы работаете, сам элемент не меняется
Но при этом enumerate даёт вам и индекс элемента, вы можете этим индексом воспользоваться, чтобы поменять значение в списке.
Четко
Запускал 2 тест раз 5, странно, но enumerate всегда немного уступал(Python 3.11)
Что если в случаях когда нужен индекс вместо enumerate использовать просто счетчик, например index = 0 , а в цикле index+=1?
Итерироваться через for, но увеличивать индекс, заданный вне цикла? Полагаю, по производительности сопоставимо с enumerate, но выглядеть это будет очень грязно.
Последний пример можно реализовать через фильтр по 0 элементу списка по далее вытащить 1 элементы и потом сделать суммирование итога. Получится сильно быстрее.
Вообще, for, while нужно использовать только тогда, когда идёт сложная трансформация данных, для всего остального есть генераторы, встроенные методы переборки вроде map и collections
конкретно тут это не будет быстрее, потому что глупо просеивать список из tuple, а потом суммировать вторые элементы из этих tuple.
а как в Пандасе циклов избежать. Особенно если используешь .loc ?
Если количество итераций цикла поставить 10**10 или 10000000000, первый вариант будет работать медленнее)
list(set[x for x in data if x is not None]) и такой вариант list({x for x in data if x is not None}]
Посыл у автора верный - имея возможность стоит всегда делать выбор в пользу коробочных функций, так как их уже оптимизировали до нас + проверять постулаты на достоверность. Но сама подача материала просто пиздец. Делаем тесты на ОДНОМ замере Карл... А еще история с "байт кодом" позабавила. Возможно, автор понимает что там написано (ощущение что нет), но делать выводы исходя только из количества инструкций это конечно топ. Так сказать разбор от бога.
Почему в сравнении суммы и цикла участвует ещё и создание списка? Нельзя его сначала отдельно создать, а потом передать в функцию?
А ещё и вывод в консоль 😂. Не люблю негатив, но таких косяков очень много у автора канала.
Еще можно сократить код немного и вместо `numbers = [num for num in range(1_000_000)]` писать `numbers = range(1_000_000)`
Но в первом случае тип объекта list, а во втором range, если нужен list, то просто надо написать так `numbers = list(range(1_000_000))`
Не знаю как это с точки зрения производительности, но по клавишам вы нажимаете меньше, спасибо за внимание)
меньше памяти занимает, range не хранит все эти числа в отличие от list
range это итератор, а с помощью генератора получим список
Я , конечно, не программист, но если надо обрабатывать миллион и более записей, то лучше использовать какую-нибудь sql базу данных, а там уже своя встроенная оптимизация запросов.
Генератор списка и генераторное выражение не отличаются по скорости, отличие в потребление памяти, генератор списка, что очевидно создает список со всеми значениями, а генераторное выражение отдает по одному значению(имеет свойства объекта итеретора).
А еще в качестве примера можно было попробовать while циклы и рекурсии.
Сам удивился в свое время сравнению скорости.
По результатом получается так: list comprehension, генераторы уже медленнее и в конце map.
Да, но как говорили в комментах ниже, стоит бы еще затраченные ресурсы проверить в дальнейшем
Это код который пишет junior----- ?
collections вам в помощь!
Циклы только тогда, когда не справляется collections.
Enumerate кайфовая штука для создания платформера
Через пол года попросите чатгпт ускорить ваш код и он сам все оптимизирует. Пока только в лимит токенов упирается. А что будет если скомпилировать код? Будут ли вообще различия в скорости?
Питон не компилируется
@@zproger питон можно компилить)
Вот 2 пример - это про меня 😎
😎 😎
Шок контент - этот 🍀клевер приносит удачу, по слухам, если его скопировать и вставить в код, он сделает код оптимизированным и быстрым.
Ура! Новая видео
😉
превью это жиза просто. с каждым днём всё лучше, удачи тебе!)
Благодарю, стараюсь 😉
Шишбик,согл)
@@burgershot777 :3
Еще быстрее код -
def cycle_example():
n = 999999
total_sum = (n * (n + 1)) // 2
print(total_sum)
кем ты работаешь? Что за сборка линукса?
ZorinOS
успехов тебе, желаю в этом году набрать 1кк подписчиков)
Спасибо, постараюсь набрать :D
Вот очень интересно, зачем такой масштаб? Вы с телефона кодите или что.
Потому что есть люди которые смотрят с телефонов, и там ничего не видно с мелким шрифтом
Лист-комп - это генератор обернутый в класс лист с помощью [ ]
Лист-комп не может быть быстрее генератора, ибо лист-комп = отработавший до конца генератор + создание объекта класса лист с результатами данных полученных от генератора
В данном случае листкомп быстрее ибо он сравнивается не с генератором, а с генератором обернутым в ( )
То есть по сути тут сравнивается list comprehension и tuple comprehension
Эээ, нет никакого tuple comprehension в природе. Если только явно обернуть в вызов tuple генератор, тогда да, но тут же нет такого, тут обычные генераторные выражения.
@@CrazyElf1971 Вы правы
Время затрачиваемое на работу зависит от кучи переменных, тестить нужно не 1 раз, а запускать код раз 100 и считать среднюю, а если требуется сложная логика, то генераторами с ума сойдёшь её программировать.
Фига питонисты узнали про функциональное программирование)))
Да :D
лайк от профи по патону - от СЕООНЛИ
Спасибо
3:50
lst = [choice(ascii_letters + digits + punctuation) for i in range(100_000_000)]
t1 = time()
for i in range(len(lst)):
a = lst[i]
print("Обращение по индексу и range:", time() - t1)
t1 = time()
for i, val in enumerate(lst):
a = val
print("Enumerate", time() - t1)
не знаю почему, но у меня выдает следующие значения:
Обращение по индексу и range: 4.414041996002197
Enumerate 5.103984832763672
То есть enumerate медленнее, а в видео наоборот, может из-за того что я пишу на pycharme, а вы в vs code?
идешки вроде не влияют на скорость выполнения кода
В первом примере слишком большое влияние оказывает наличие инициализации numbers в функции. Вот что будет, если этот список передавать аргументом:
499999500000
time: 0.00261
499999500000
time: 0.02129
Результаты не в том порядке, что в видео (сперва вариант через sum, потом через цикл).
d = {1: 'foo', True: 'bar'}
print(d) # {1: 'bar'}
for key in dict работает быстрее чем for key in dict.keys(). Как я понял это потому что первое выражение использует кэшированные данные, когда как метод keys() возвращает генератор.
Я думал enumerate наоборот затратнее и медленнее, поэтому редко использовал его.
1-ый пример совершенно притянутый за уши, я такого в реальной жизни никогда не видел. Давайте возьмем какую-нибудь вложенную структуру, в ней что-то найдём (причём найдём как-то не так чтобы очень просто, с переходом в другие процедуры) и оттуда уже суммируем данные по циклу. Впрочем, 4-й пример именно об этом.
2-ой, да согласен, это так и есть.
3-й, согласен, zip -- сила. )))
4-й, тоже согласен.
Разница как бы не такая большая, если процедура выполняется один раз, но если оно балалайкой повторяется миллион раз в цикле... )))
Есть классная книжка по такого рода приколам питона, называется Python Cookbook, автор Дэвид Бизли. Там прям рецепты решения всяких задач описаны. Один из советов, изучить встроенную либу collections,
Почему не используешь python 11?
лень обновлять :D
А какая операционка у вас?
Zorin OS
@@zproger не хотели бы сделать на нее обзор с упором именно на ваши задачи?
зачем использовать enumerate, когда нужны одни значения?
обычный итератор по значениям будет быстрее: for v in numbers: temp += v
ну а лучше вообще так: for v in range(1_000_000): temp += v
а еще лучше так: sum(range(1_000_000))
в последнем примере быстрее всего будет вообще так (кстати, используется генератор):
print(sum(value for action, value in user_buy if action == "confirmed"))
и это быстрее, чем использование списка (я уж молчу про использование памяти):
print(sum([value for action, value in user_buy if action == "confirmed"]))
Оказывается для получения суммы элементов не надо писать цикл - афигеть! Вот это новость!
Не забывайте о новичках
да можно вообще через reduce + lambda на сумму захерачить )))
2:57 написано for i in number, а массив называется numbers
давай 4 файл на map/reduce
молодцом, парень, толково объясняешь
Благодарю
Используйте встроенные функции, а в коде [num for num in range(1000)] вместо list(range(1000)), причём это заполнение ещё и в замер попадает.
И вообще, сумма рейнджа решается формулой.
можно пример формулы?
@@Lokamp_ищи "сумма членов арифметической прогрессии"
Давай видос про всю мощь much case и её отличие от простых if elif else.
Спасибо, сделаю
олень безграмотный, "much case" аахахха
>байтойобить
>байтойобить на питоне
:)
Круто круто круто
а зачем писать num for num in range() если то же самое возвращает просто функция range()
[num for num in range(10_000)] медленнее, больше по размеру, больше по байт коду, чем просто [*range(10_000)] или list(range(10_000))
Кстати, да. Всегда когда пишется код "[x for x in ..." его обычно можно заменить на более оптимальный код.
Лучший!
Спасибо 😉
разница в скорости не стоит потраченного времени на это видео)
"Я добавляю всем известный перф каунтер.." Да, да... Всем известный...
Норм
Спасибо =)
Есть ощущение, что вся разница вовсе не от записи.
При запуске, вы не можете гарантировать, что код получил процессорное время в момент старта, и на всё время выполнения, без пауз.
Вспомните про gil, и как ос раздает время процессора.
Итого. На 10⁶+ запусков цикла можно посмотреть среднее.
Чтобы понять, есть ли смысл в этой оптимизации.
И уже тогда думать, как городить тесты "в вакууме", для корректного сравнения.
Нда, огромная разница при использовании enumerate и zip, аж целых 10% !! Питон превращается в Си++ можно сказать
Декоратор лучше использовать для замера времени выполнения
Пример можете написать ?
чем лучше?
@@Keefear def time_counter(func):
def inner(*args, **kwargs):
start_t = time.time_ns()
func(*args, **kwargs)
end_t = time.time_ns()
res = end_t - start_t
print(f'execution time of {func.__name__}: {res} ns')
return inner
@@justkrybik Да просто удобнее и меньше кода нужно
Думаю, народ запускает цикл на суммированием когда нужно использовать оператоh if , разбери пример использования sum map lambda filter
чтобы записи были быстрее надо сменить раскладку с "py" на "c++", и тогда уже можно обложиться костылями и летать всё будет как боинг
Сделай пожалуйста видео про оптимизацию, с использованием всех практик которые ты раньше показывал, какого-нибудь реального веб приложения, допустим на том же фастапи. И сравни результаты до оптимизаций и после, прям хочется посмотреть насколько можно выиграть если сильно заморочится
Спасибо за идею.
Возможно сделаю если будет на это время.
Пишу промышленные микросервисы на фастапи. В меня ходит сервис с таймаутом 1.5 секунды. Из них передача данных (жсон с парой десятков полей и здоровенной матрицей, общий вес около 3 МБ) занимает порядка 700-800мс. Работа с данными занимает порядка 200-300мс. Причем алгоритм работы с матрицей оптимизирован только использованием нампай и нумбы, но при этом есть вложенные циклы (алгоритм писали аналитики, решили не лезть туда с оптимизациями, чтоб не поломать). Таким образом, если оптимизировать всю обработку данных с 300 до 100мс - передача данных все равно будет занимать гораздо большую часть общего времени. Так что не всегда миллисекунды в алгоритмах так важны )))