TGINSIGHT CHAT
Python Заметки
@pythonotes
EducationИнтересные заметки и обучающие материалы по Python Контакт: @paulwinex ⚠️ Рекламу на канале не делаю!⚠️ Хештеги для поиска: #tricks #libs #pep #basic #regex #qt #django #2to3 #source #offtop
Неодамнешни објави
Страница 26 од 32 · 384 објави
Објавено 27 мај
Почему не стоит в коде использовать assert для проверки данных? Действительно, команда очень удобна для быстрой проверки правдивости какого-либо факта. assert isinstance(value, int), "Value must be type int" Но почему советуют делать это только в тестах? Дело в том, что эта команда сделана именно для тестов и есть специальный режим когда она глобально отключается и не работает. Есть такая builtin константа__debug__, которая по умолчанию имеет значение True (и это не изменить в коде). Именно она указывает, будут ли работать ваши assert'ы. Стоит запустить интерпретатор в режиме оптимизации (флаг -O), константа __debug__ будет равна False, и все ваши проверки будут проигнорированы. python -O script.py Поэтому всегда используйте raise. if not isinstance(value, int): raise TypeError("Value must be type int") #tricks
Hashtags
Објавено 25 мај
Вы всё еще проверяете секретные данные оператором сравнения? >>> if password == user_password: >>> ... Это небезопасный способ сравнения для стендалон приложений. Он уязвим к такому типу атаки как timing attack, позволяющий делать выводы и угадывать пароль на основании времени проверки. Чтобы сделать безопасное сравнение используйте метод secrets.compare_digest(). Он защитит операцию проверки от подобных атак. >>> import secrets >>> if secrets.compare_digest(password, user_password): >>> ... Возможно вы ранее слышали про метод hmac.compare_digest(). Он не только делает то же самое, это один и тот же метод! >>> import secrets >>> import hmac >>> hmac.compare_digest is secrets.compare_digest True #libs
Hashtags
Објавено 23 мај
В прошлом посте мы рассмотрели синхронный планировщик задач sched. Функционал вроде не плох, но синхронное выполнение с блокировкой всё портит☹️. Можем ли мы как-то поправить ситуацию? На самом деле можем (помимо отправки всего в subprocess). Функция run() принимает аргумент blocking, который по умолчанию True. То есть, если мы укажем blocking=False то получим неблокирующее выполнение? Нет. Этот параметр работает иначе. Если мы делаем неблокирующий запуск, то после вызова метода run(blocking=False) планировщик выполнит все задачи, которым пришло время исполниться в обычном блокирующем синхронном режиме и вернёт время, через которое следует запуститься следующей задаче. То есть через какое время нужно запустить run(...) еще раз. Получается, что вместо ожидания таймаута без полезной нагрузки планировщик освобождает поток и сообщает через сколько ему пора будет продолжить работу. А что делать с этой информацией, решаете сами. Выполнение следующих задач произойдет после следующего вызова run() и если пришло их время выполниться. Например, вместо ожидания следующей задачи будем делать что-то полезное: import sched, time def func(name): # задача t = round(time.time()-start_time, 2) print(f"Execute {name} ({t}s)") start_time = time.time() # создаём планировщик s = sched.scheduler(time.time, time.sleep) s.enter(0, 1, func, argument=('ev1',)) s.enter(1, 1, func, argument=('ev2',)) s.enter(2, 1, func, argument=('ev3',)) delay = 0 last_run_time = time.time() while True: # выполнение заданий планировщика if last_run_time + delay < time.time(): delay = s.run(blocking=False) if delay is None: break last_run_time = time.time() # здесь делаем что-то полезное print('Делаем что-то полезное...') time.sleep(0.1) print('Complete all tasks') Планировщик остаётся по-прежнему синхронным, но теперь вместо бесполезного ожидания мы можем запустить другой код на исполнение и знаем когда следует вернуться к планировщику. ___________________ PPS. Да, в этих примерах я нагло использую глобальные переменные))) Не делайте так на реальных проектах
Објавено 21 мај
В Python есть стандартный модуль sched для синхронного планировщика задач. Что??? Синхронных??? В наш-то век "асинхронщины" и "параллельщины"! Спокойно, сначала смотрим код, потом разбираемся. Работает это так: - создаём планировщик - добавляем задачи в очередь с таймаутом и приоритетом - запускаем и ждём пока завершится вся очередь Смотрим пример: import sched, time def func(name): t = round(time.time()-start_time, 2) print(f"Execute {name} ({t}s)") # просто отметка времени старта start_time = time.time() # создаём планировщик s = sched.scheduler( time.time, # функция замера времени time.sleep # функция ожидания ) # добавляем задачи s.enter(0, 1, func, argument=('ev1',)) s.enter(2, 1, func, argument=('ev2',)) s.enter(1, 1, func, argument=('ev3',)) # запускаем очередь на исполнение s.run() Описание аргументов Вывод получаем в соответствии со временем задержки: Execute ev1 (0.0s) Execute ev3 (1.0s) Execute ev2 (2.0s) Функция run() запускает планировщик и начинается выполнение задач в порядке очереди по времени и приоритету. Время указывается с момента старта очереди. Если время совпадает то сортировка идёт по приоритету. Что значит синхронный планировщик? Это значит что задачи будут выполняться строго по очереди в одном потоке. Никаких мультипотоков и мультипроцессов, модуль прост как бревно! Если у первой задачи стоит задержка 0сек а у второй 1сек, и при этом первая задача выполняется 3сек, то вторая задача выполнится только через 3 сек. Никакого параллельного запуска не будет. Параметр delay следует понимать не как "запусти через N сек" а как "запусти не раньше чем через N сек". Время запуска следующей зависит от выполнения предыдущих задач. Где это может пригодиться? Очередь задач, между которыми должен быть промежуток времени по какой-либо причине. 🔸 несколько синхронных задач, которые должны выполняться друг за другом но требующие ожидания обновления какой-либо инфраструктуры (тормозная сеть?) 🔸 сетевой API который имеет лимит на количество команд в единицу времени 🔸 фейковая задержка для генерации тестов или имитация поведения юзера. Конечно, всё это можно решить банальным time.sleep() в нужном месте, но sched даёт несколько более удобный интерфейс управления задачами. #libs
Hashtags
Објавено 19 мај
Python 3.9 готовит нам приятный сюрприз в PEP584. Ранее мы обсуждали как можно удобно сложить вместе два словаря получив новый словарь. Было много вариатов, но ни одного идеального. Наконец-то в Python добавили оператор для слияния словарей!!! Это оператор "|". А это значит, что начиная с 3.9 соединить словари можно таким синтаксисом: dct3 = dct1 | dct2 Чтобы обновить словарь, можно использовать такой синтаксис dct1 |= dct2 Данный функционал уже можно опробовать, установив первые релизы. #pep
Hashtags
Објавено 17 мај
Допустим, имеется у нас задача: разделить одно число на другое и получить отдельно целое число и остаток от деления. Например, исходные числа 15 и 2. В результате должны получить 7 и 1. То есть 7 раз двойка входит в состав 15 целиком и потом остаётся еще 1. Как будем действовать? Очевидно же, целое число вхождений получаем через floor division >>> 15//2 7 Остаток через деление по модулю >>> 15%2 1 Но можно сделать проще (ох, ну куда уж проще то 😄). В Python есть builtin функция divmod которая делает эти два действия в одно. >>> divmod(15, 2) (7, 1) #tricks
Hashtags
Објавено 15 мај
"Ну и как же нам перекидывать строки ви числа?" спросите вы. Проверять каждый символ, очистив строку от лишних знаков и точек. Потом конвертить допустимые символы в числа и восстанавливать знак, дробную чусть и тд??? Самый быстрый способ это просто "попробовать" 😜 text = "-0.3" try: num = float(text) except ValueError: print('Dough!') Всё остальное это уже парсинг и разбор символов для иных целей. #basic
Hashtags
Објавено 13 мај
В продолжение прошлого поста про цифры в мире строк. Почему методы isdigit() и isnumeric() не определяют в строке float и отрицательные значения? Дело в том, что эти методы работают с ЦИФРАМИ, то есть с единичным символом. А строка "-2" или "3.4" это уже ЧИСЛО. То есть не символ а значение, записанное несколькими символами. Все озвученные методы проходятся по каждому символу строки и проверяют их индивидуально. В юникоде есть символы цифр с точками "🄀⒈⒉⒊⒋⒌⒍⒎⒏⒐" Каждая из них это ОДИН СИМВОЛ, поэтому он будет считаться цифрой >>> '⒌'.isdigit(), '⒌'.isnumeric() True, True Но когда мы пишем это выражение в два символа ( 5+точка), то это не работает. >>> '5.'.isdigit(), '5.'.isnumeric() False, False А еще есть такие символы >>> '⑴⑵⑶⑷⑸'.isdigit() True >>> '🄁🄂🄃'.isdigit() True Но они не преобразуются в десятичные цифры >>> '⒈'.isdecimal() False >>> '🄃'.isdecimal() False >>> '⑶'.isdecimal() False #basic
Hashtags
Објавено 11 мај
У строки в Python есть два очень похожих метода. На столько похожих что кажется они делают одно и тоже. Это метод isdigit() и isnumeric() Давайте посмотрим зачем нам два одинаковых метода? И так ли они одинаковы? Очевидно что isdigit() говорит нам, состоит ли строка только из чисел 0-9 >>> '12'.isdigit() True >>> '12x'.isdigit() False >>> '-12'.isdigit() False >>> '12.5'.isdigit() False Можно предположить что isnumeric() делает более глубокий анализ и распознаёт в строке float или отрицательное число. >>> '15'.isnumeric() True >>> '-15'.isnumeric() False >>> '15.2'.isnumeric() False Нет, всё так же как и с другим методом. В чем же тогда разница? Для начала посмотрим следующие примеры: >>> '5'.isdigit(), '5'.isnumeric() # Обычная цифра 5 # True, True >>> '꧕'.isdigit(), '꧕'.isnumeric() # Яванская 5 # True, True >>> '෩'.isdigit(), '෩'.isnumeric() # Синхала 3 # True, True >>> '৩'.isdigit(), '৩'.isnumeric() # Бенгальская 3 # True, True >>> '༣'.isdigit(), '༣'.isnumeric() # Тибетская 3 # True, True >>> '³'.isdigit(), '³'.isnumeric() # 3 верхний индекс (степень) # True, True >>> '𝟝'.isdigit(), '𝟝'.isnumeric() # Математическая двойная 5 # True, True >>> '๔'.isdigit(), '๔'.isnumeric() # Тайская 4 # True, True >>> '➑'.isdigit(), '➑'.isnumeric() # 8 в круге # True, True А теперь примеры в которых, по мнению Python, результаты не равны >>> '¾'.isdigit(), '¾'.isnumeric() # дробь три четверти # False, True >>> '⅕'.isdigit(), '⅕'.isnumeric() # дробь одна пятая # False, True >>> '𒐶'.isdigit(), '𒐶'.isnumeric() # клинопись 3 # False, True >>> '三'.isdigit(), '三'.isnumeric() # 3 из унифицированной идеограммы # False, True >>> '⑩'.isdigit(), '⑩'.isnumeric() # цифра 10 в круге # False, True >>> 'Ⅳ'.isdigit(), 'Ⅳ'.isnumeric() # Римская 4 # False, True >>> '𑇪'.isdigit(), '𑇪'.isnumeric() # Сенегальская архаическая 10 # False, True >>> '𐌢'.isdigit(), '𐌢'.isnumeric() # Этрусская цифра 10 # False, True >>> 'ↂ'.isdigit(), 'ↂ'.isnumeric() # Римская цифра 10000 # False, True >>> '〇'.isdigit(), '〇'.isnumeric() # Символ ККЯ ноль # False, True Получается, что isdigit() говорит нам, является ли символ десятичной цифрой или спецсимволом, имеющим цифирное значение после преобразования. В свою очередь isnumeric() включает все дополнительные символы юникода которые имеют отношения к числовым и цифровым представлениям. Ну и пара примеров в которых в обоих случаях символ не является числом, это эмодзи. >>> '🕙'.isdigit(), '🕙'.isnumeric() # эмодзи 10 часов # False, False >>> '7️⃣'.isdigit(), '7️⃣'.isnumeric() # эмодзи 7 # False, False Также есть еще один дополнительный и весьма полезный метод isdecimal(). Он нам сообщает, можно ли из указанного символа сделать простую десятичную цифру. То есть сработает ли метод int(x) >>> '෩'.isdecimal(), int('෩') # Синхала 3 # True, 3 >>> '➑'.isdecimal(), int('➑') # 8 в круге # False, ValueError Какие выводы? 🔸 При определении цифры в строке isdigit() подходит лучше чем isnumeric(), но оба не гарантируют успешную конвертацию в int 🔸 Для однозначного определения возможности преобразования строки в int лучше подходит метод isdecimal() 🔸 Для однозначного определения символов 0...9 лучше использовать regex Полный список символов юникода которые определяются как numeric #basic
Hashtags
Објавено 8 мај
В Python всё является объектами. Это значит что у каждой сущности есть тип и какие-либо методы. Мы знаем что есть методы у строк >>> 'string'.upper() у списков >>> [1,2,3].count(2) у словарей >>> {"key": 123}.items() А есть ли какие-то методы у простых чисел? Не много, но есть! Например, возьмём простой int >>> a = 22 Метод bit_length() покажет сколько потребуется бит для отображения данного числа в двоичном представлении, исключая ведущие нули. >>> a.bit_length() 5 Проверяем >>> bin(a).lstrip('-0b') '10110' Всё верно. Проверим float >>> b = 10.5 Мы можем проверить есть ли у числа дробная часть >>> b.is_integer() False Получить наш float в виде простой десятичной дроби >>> b.as_integer_ratio() (21, 2) Конечно же Python не имеет типа "десятичная дробь", поэтому мы просто получаем кортеж из двух элементов: числитель и знаменатель. У int тоже есть такой метод (Python3.8+), но он работает "хитро". Целое число всегда равно дроби где в числителе это же число а в знаменателе 1. Поэтому данный метод у int всегда возвращает (x, 1). 😕 Кстати, чтобы обойтись без переменной просто возьмите число в скобки >>> (10.0).is_integer() True #tricks
Hashtags
Објавено 6 мај
Срез это довольно удобная штука для получения определённого диапазона элементов из списка. Но срезы не так просты как кажется, их можно использовать и для изменения оригинального списка так же как мы это делаем просто по индексу. Для примера возьмем простой список ls = [1, 2, 3, 4, 6] И поехали! >>> ls[-1] = 5 [1, 2, 3, 4, 5] Обычное изменение по индексу. >>> ls[2:4] [3, 4] Обычный срез который создаёт новый список на основе старого. >>> ls[::-1] [5, 4, 3, 2, 1] Реверс списка, тоже создаёт новый. Оригинальный список остался без изменений. Воспользуемся оператором присвоения: >>> ls[2:4] = [7, 8] [1, 2, 7, 8, 5] Заменили диапазон элементов в оригинальном списке. >>> ls[4:] = [0, 0, 0, 0] [1, 2, 7, 8, 0, 0, 0, 0] Указав диапазон сверх имеющегося мы расширили список по аналогии с методом extend(), но при этом еще и немного захватили конец списка. Всё это в одно действие! >>> ls[:0] = [9, 8, 7] [9, 8, 7, 1, 2, 7, 8, 0, 0, 0, 0] Добавили элементы в начало >>> del ls[-4:] [9, 8, 7, 1, 2, 7, 8] Удалили часть элементов списка >>> ls[1:3] = [] [9, 1, 2, 7, 8] Еще один способ удалить элементы >>> ls[:] = [] А этим способом можно пользоваться для очистки списка в Python2, в котором еще не было метода clear(). Кажется мы "потратили" весь наш список))) Сделаем новый и продолжим. >>> ls = [1, 2, 3, 4, 5, 6] >>> ls[::2] = [7,8,9] [7, 2, 8, 4, 9, 6] И конечно же мы можем использовать шаг в срезе, но тут требуется соблюдать количество подаваемых элементов. Заменять элементы с каким-то шагом можно, а добавлять нельзя. s = slice(3, 4) Как было показано ранее, срез можно сохранять как переменную и использовать в дальнейших манипуляциях >>> ls[s] = [0]*5 [7, 2, 8, 0, 0, 0, 0, 0, 9, 6] Заменили один элемент на несколько элементов, расширив исходный список из центра. И напоследок. Следующие два действия равнозначны и дадут одинаковый результат — полная замена списка. >>> ls[:] = [1,2,3,4] >>> ls = [1,2,3,4] #tricks
Hashtags
Објавено 4 мај
Наверняка вы часто используете генераторы списков (List Comprehension). Я тоже, даже порой злоупотребляю ими) Но что поделать, если они такие удобные! Давайте разберёмся как работают составные итерации в генераторе списка. Для начала посмотрим простой вид. >>> array = [1, 2, 3, 4] >>> [x for x in array] Этот код просто делает копию списка. Добавляем некоторое выражение: >>> [x*2 for x in array] Этот генератор создаёт новый список элементы которого в 2 раза больше чем в оригинальном списке. Добавим условие: >>> [x*2 for x in array if x%2] Предыдущему примеру добавили фильтр, который проходят только нечётные числа. В целом, я бы советовал на этом и остановиться. Не нужно усложнять простое! Мы можем итерировать в генераторе сразу два списка так, чтобы каждый элемент одного списка повстречался с каждым элементом из другого. >>> array1 = [1, 2, 3] >>> array2 = [4, 5, 6] >>> print([f'{i}-{j}' for i in array1 for j in array2]) ['1-4', '1-5', '1-6', '2-4', '2-5', '2-6', '3-4', '3-5', '3-6'] Давайте обозначу каждый цикл условными скобками чтобы было понятней (синтаксически это неверно). [f'{i}-{j}' (for i in array1) (for j in array2)] Можем добавить условие и сюда? Можем! >>> print([f'{i}-{j}' (for i in array1 if i >= 2) (for j in array2 if j < 5)]) ['2-4', '3-4'] При этом во второй итерации мы можем использовать переменную из первой >>> array = [(1, 2, 3), (4, 5, 6)] >>> new = [y for x in array for y in x] Кстати, мы разложили вложенные кортежи в один плоский список. Можем ли мы использовать больше двух итераций? Да хоть десять! >>> a1 = [9, 2, 4, 5] >>> a2 = [4, 5, 9, 1] >>> a3 = [5, 9, 3, 0] >>> match = [x for x in a1 for y in a2 for z in a3 if x == y and x == z] [9, 5] Нашли числа которые встречаются во всех списках. Можно еще сложней? Конечно, Python довольно многое позволяет делать. Но, как говорят про Python: если ЭТО можно сделать то еще не значит что ЭТО нужно делать. #tricks
Hashtags