Стандартный модуль json имеет command line интерфейс. Он умеет делать валидацию JSON-данных и переводить однострочный вариант в форматированный.
Изменяем форматирование
Как происходит валидация? Да просто команда завершится с ошибкой если формат данных неверный (exit code 1). В stderr распечатается информация о том где произошёл сбой.
#libs
Изменяем форматирование
$ echo '{"key1": "value1", "key2": "value2"}' | python3 -m json.tool
{
"key1": "value1",
"key2": "value2"
}
Заменяем прямой ввод данных на данные из файловpython3 -m json.tool < single.json > pretty.json
Вместо stdin и stdout просто передаём путь к файлам, результат тот же.python3 -m json.tool single.json pretty.json
Как происходит валидация? Да просто команда завершится с ошибкой если формат данных неверный (exit code 1). В stderr распечатается информация о том где произошёл сбой.
#libs
Регулярные выражения иногда могут быть просто монструозными. Выглядеть это может крайне запутанно. Сами регэкспы и без того история непростая, а когда это длинный паттерн на несколько десятков знаков, разобрать там что-либо становится не просто.
Но на помощь приходит Python и его стремление сделать нашу жизнь проще!
В функциях регулярок можно после паттерна указывать флаги, один из которых позволяет писать паттерны более свободно. А именно, добавлять пробелы и переносы, которые будут игнорированы. В результате мы можем разбить паттерн на строки и добавить комментов.
Чтобы это сработало нужно добавить флаг
Согласитесь, что даже с именованными группами а таком виде регэкспа выглядит вполне сносно 😉.
#tricks #regex
Но на помощь приходит Python и его стремление сделать нашу жизнь проще!
В функциях регулярок можно после паттерна указывать флаги, один из которых позволяет писать паттерны более свободно. А именно, добавлять пробелы и переносы, которые будут игнорированы. В результате мы можем разбить паттерн на строки и добавить комментов.
Чтобы это сработало нужно добавить флаг
re.VERBOSE. Пробелы в паттерне теперь следует указывать явно спец символами.Согласитесь, что даже с именованными группами а таком виде регэкспа выглядит вполне сносно 😉.
#tricks #regex
Хотите оформить свои CLI скрипты красивым прогрессбаром?
Стоит посмотреть на библиотеку progress.
Она даёт возможность создавать красивые прогрессбары легко и быстро.
Но что если не хочется добавлять лишнюю зависимость только ради одной бегущей полоски?
Создать свою функцию с подобным функционалом — не проблема!
Вот базовый набросок:
Вся хитрость в отсутствии переноса на новую строку в конце строки и в символе
Главное в самом конце не забыть обычный print() чтобы перейти на новую строку.
Расширенный вариант в виде контекст менеджера. 🌎
#libs #tricks
Стоит посмотреть на библиотеку progress.
Она даёт возможность создавать красивые прогрессбары легко и быстро.
Но что если не хочется добавлять лишнюю зависимость только ради одной бегущей полоски?
Создать свою функцию с подобным функционалом — не проблема!
Вот базовый набросок:
import time
for i in range(0, 100+1, 4):
time.sleep(0.1)
print('\r{:>3}% [{}{}]'.format(i, "#"*i, '-'*(100-i)), end='')
print()
Запустите код в интерактиве и увидите что работает не хуже.Вся хитрость в отсутствии переноса на новую строку в конце строки и в символе
"\r" — возврат "каретки" (читай "курсора") в начало строки. После чего мы перезаписываем предыдущую строку.Главное в самом конце не забыть обычный print() чтобы перейти на новую строку.
Расширенный вариант в виде контекст менеджера. 🌎
#libs #tricks
Ранее я делал серию постов про битовые операторы.
Вот вам ещё один наглядный пример как это используется в Python в модуле
Чтобы указать флаг для компилятора нам надо указать его после передаваемой строки. Например, добавляем флаг для игнорирования переноса строки.
Не удивительно, степени двойки. Почему? Потому что каждое следующее значение это сдвиг единицы влево.
В общем, это пример применения побитовых операций в самом Python.
Теперь вы знаете Python еще немного лучше)
#tricks #regex #libs
Вот вам ещё один наглядный пример как это используется в Python в модуле
re.Чтобы указать флаг для компилятора нам надо указать его после передаваемой строки. Например, добавляем флаг для игнорирования переноса строки.
pattern = re.compile(r"(\w+)+")
words = pattern.search(text, re.DOTALL)
А как указать несколько флагов? Ведь явно будут ситуации когда нам потребуется больше одного. Кто читал посты по битовые операторы уже понял как.pattern.search(text, re.DOTALL | re.VERBOSE)
А теперь смотрим исходники, что находится в этих атрибутах?Не удивительно, степени двойки. Почему? Потому что каждое следующее значение это сдвиг единицы влево.
>>> for n in [1, 2, 4, 8, 16, 32, 64, 128, 256]:
>>>
print(bin(n))
0b1
0b10
0b100
0b1000
0b10000
0b100000
0b1000000
0b10000000
0b100000000
Чтобы было понятней, давайте напишем тоже самое но иначе, добавим ведущие нули:000000001
000000010
000000100
000001000
000010000
000100000
001000000
010000000
100000000
Не понятно что тут происходит? Читай три поста про битовые операторы начиная с этого ➡️ https://t.iss.one/pythonotes/45В общем, это пример применения побитовых операций в самом Python.
Теперь вы знаете Python еще немного лучше)
#tricks #regex #libs
GitHub
cpython/sre_constants.py at main · python/cpython
The Python programming language. Contribute to python/cpython development by creating an account on GitHub.
Каждый модуль в Python имеет атрибут ˍˍnameˍˍ. В него записывается строка, содержащая полное имя модуля. Например:
Но однажды мне потребовалось создать логгер не для модуля в целом, а для отдельной функции. Что в этом случае можно сделать?
Такой объект как функция тоже имеет атрибут ˍˍnameˍˍ
А что если функция не из этого класса а унаследована и вы работаете с внешней библиотекой?
На помощь приходит PEP3155 и его имплементация "Qualified name".
Всё очень просто, начиная с Python 3.3 классы и функции имеют атрибут ˍˍqualnameˍˍ, который и содержит полное или "честное" или "квалифицированное" имя объекта. Именно в него уже записан готовый полный адрес объекта. И теперь даже такая конструкция сработает правильно:
>>> from my_package import my_module
>>> print(my_module.__name__)
'my_package.my_module'
Очень удобно для логгинга когда в имя логгера требуется записать имя модуля, в котором происходят события лога.Но однажды мне потребовалось создать логгер не для модуля в целом, а для отдельной функции. Что в этом случае можно сделать?
Такой объект как функция тоже имеет атрибут ˍˍnameˍˍ
>>> class MyClass:
>>> def func(self):
>>> pass
>>> print(MyClass.func.__name__)
'func'
Итого нам следует собрать полное имя таким образом:>>> full_name = '.'.join([__name__, MyClass.__name__, MyClass.func.__name__])
>>> print(full_name)
'my_package.my_module.MyClass.func'
А что если класс вложен в другой класс?>>> class MyClass:
>>> class SubClass:
>>> def func(self):
>>> pass
>>> full_name = '.'.join([__name__, MyClass.__name__, MyClass.SubClass.__name__, MyClass.SubClass.func.__name__])
>>> print(full_name)
'my_package.my_module.MyClass.SubClass.func'
А что если вложений несколько?>>> class MyClass:
>>> class SubClass1:
>>> class SubClass2:
>>> def func(self):
>>> pass
Даже не буду писать пример получения имени 😭.А что если функция не из этого класса а унаследована и вы работаете с внешней библиотекой?
>>> from somewhere.something import OtherClass
>>> class MyClass2(OtherClass):
>>> pass
Cтрашно подумать как нам достать настоящее "полное имя" функции! А если там динамическое определение наследования или метаклассы? Вообще можно забить на решение и придумать другой способ 😵На помощь приходит PEP3155 и его имплементация "Qualified name".
Всё очень просто, начиная с Python 3.3 классы и функции имеют атрибут ˍˍqualnameˍˍ, который и содержит полное или "честное" или "квалифицированное" имя объекта. Именно в него уже записан готовый полный адрес объекта. И теперь даже такая конструкция сработает правильно:
>>> # some_module.py
>>>
>>> class OtherClass:
>>> class SubClass1:
>>> class SubClass2:
>>> def func(self):
>>> pass
>>>
>>> # my_module.py
>>> from some_module import OtherClass
>>> class FinalClass(OtherClass.SubClass1.SubClass2):
>>> pass
>>>
>>> full_name = '.'.join([__name__, FinalClass.func.__qualname__])
'my_package.my_module.OtherClass.SubClass1.SubClass2.func'
Тут стоит заметить, что если функция находится не в текущем модуле а откуда-то импортирована (как в моём примере) то собирать имя с локальным атрибутом ˍˍnameˍˍ уже не имеет особого смысла. Тогда лучше использовать имя модуля исходного класса:>>> full_name = '.'.join([FinalClass.__module__, FinalClass.func.__qualname__])
'some_module.OtherClass.SubClass1.SubClass2.func'
#pepPython Enhancement Proposals (PEPs)
PEP 3155 – Qualified name for classes and functions | peps.python.org
В прошлом посте мы получали полное имя метода. В атрибут ˍˍqualnameˍˍ записано имя только до класса, к которому метод принадлежит. Как быть если нужно полное имя с модулем, а мы имеем только объект метода без инстанса или ссылки на класс?
func = get_my_method()
В результате этого псведо-выражения мы имеем только сам метод класса. Есть ли в нём информация к какому классу он принадлежит? Конечно есть, это атрибут ˍˍselfˍˍ. Он ссылается на инстанс. А оттуда, как не трудно догадаться, можно получить и ссылку на класс через атрибут ˍˍclassˍˍ:cls = func.__self__.__class__
итого полное имя будет выглядеть такfull_name = '.'.join([
func.__self__.__class__.__module__,
func.__qualname__]
)
#tricksНаверняка вы часто используете генераторы списков (List Comprehension). Я тоже, даже порой злоупотребляю ими) Но что поделать, если они такие удобные!
Давайте разберёмся как работают составные итерации в генераторе списка.
Для начала посмотрим простой вид.
В целом, я бы советовал на этом и остановиться. Не нужно усложнять простое!
Мы можем итерировать в генераторе сразу два списка так, чтобы каждый элемент одного списка повстречался с каждым элементом из другого.
Можем ли мы использовать больше двух итераций? Да хоть десять!
Можно еще сложней? Конечно, Python довольно многое позволяет делать.
Но, как говорят про Python: если ЭТО можно сделать то еще не значит что ЭТО нужно делать.
#tricks
Давайте разберёмся как работают составные итерации в генераторе списка.
Для начала посмотрим простой вид.
>>> 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
Срез это довольно удобная штука для получения определённого диапазона элементов из списка.
Но срезы не так просты как кажется, их можно использовать и для изменения оригинального списка так же как мы это делаем просто по индексу.
Для примера возьмем простой список
Воспользуемся оператором присвоения:
Кажется мы "потратили" весь наш список)))
Сделаем новый и продолжим.
И напоследок. Следующие два действия равнозначны и дадут одинаковый результат — полная замена списка.
Но срезы не так просты как кажется, их можно использовать и для изменения оригинального списка так же как мы это делаем просто по индексу.
Для примера возьмем простой список
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]
#tricksTelegram
Python Заметки
Все мы знаем, что в Python всё является объектом. Это значит, что всё можно сохранить в переменную, передать аргументом или вернуть из функции через return.
Но известно ли вам, что объектом можно сделать даже срез списка?! То есть сохранить в переменную алгоритм…
Но известно ли вам, что объектом можно сделать даже срез списка?! То есть сохранить в переменную алгоритм…
В Python всё является объектами.
Это значит что у каждой сущности есть тип и какие-либо методы.
Мы знаем что есть методы у строк
Например, возьмём простой int
Проверим float
У int тоже есть такой метод (Python3.8+), но он работает "хитро". Целое число всегда равно дроби где в числителе это же число а в знаменателе 1. Поэтому данный метод у int всегда возвращает (x, 1). 😕
Кстати, чтобы обойтись без переменной просто возьмите число в скобки
Это значит что у каждой сущности есть тип и какие-либо методы.
Мы знаем что есть методы у строк
>>> '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У строки в Python есть два очень похожих метода. На столько похожих что кажется они делают одно и тоже.
Это метод isdigit() и isnumeric()
Давайте посмотрим зачем нам два одинаковых метода? И так ли они одинаковы?
Очевидно что isdigit() говорит нам, состоит ли строка только из чисел 0-9
В свою очередь isnumeric() включает все дополнительные символы юникода которые имеют отношения к числовым и цифровым представлениям.
Ну и пара примеров в которых в обоих случаях символ не является числом, это эмодзи.
Он нам сообщает, можно ли из указанного символа сделать простую десятичную цифру. То есть сработает ли метод int(x)
🔸 При определении цифры в строке isdigit() подходит лучше чем isnumeric(), но оба не гарантируют успешную конвертацию в int
🔸 Для однозначного определения возможности преобразования строки в int лучше подходит метод isdecimal()
🔸 Для однозначного определения символов 0...9 лучше использовать regex
Полный список символов юникода которые определяются как numeric
#basic
Это метод 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
В продолжение прошлого поста про цифры в мире строк.
Почему методы isdigit() и isnumeric() не определяют в строке float и отрицательные значения?
Дело в том, что эти методы работают с ЦИФРАМИ, то есть с единичным символом. А строка "-2" или "3.4" это уже ЧИСЛО. То есть не символ а значение, записанное несколькими символами.
Все озвученные методы проходятся по каждому символу строки и проверяют их индивидуально.
В юникоде есть символы цифр с точками "🄀⒈⒉⒊⒋⒌⒍⒎⒏⒐"
Каждая из них это ОДИН СИМВОЛ, поэтому он будет считаться цифрой
#basic
Почему методы isdigit() и isnumeric() не определяют в строке float и отрицательные значения?
Дело в том, что эти методы работают с ЦИФРАМИ, то есть с единичным символом. А строка "-2" или "3.4" это уже ЧИСЛО. То есть не символ а значение, записанное несколькими символами.
Все озвученные методы проходятся по каждому символу строки и проверяют их индивидуально.
В юникоде есть символы цифр с точками "🄀⒈⒉⒊⒋⒌⒍⒎⒏⒐"
Каждая из них это ОДИН СИМВОЛ, поэтому он будет считаться цифрой
>>> '⒌'.isdigit(), '⒌'.isnumeric()Но когда мы пишем это выражение в два символа ( 5+точка), то это не работает.
True, True
>>> '5.'.isdigit(), '5.'.isnumeric()А еще есть такие символы
False, False
>>> '⑴⑵⑶⑷⑸'.isdigit()Но они не преобразуются в десятичные цифры
True
>>> '🄁🄂🄃'.isdigit()
True
>>> '⒈'.isdecimal()
False
>>> '🄃'.isdecimal()
False
>>> '⑶'.isdecimal()
False#basic
"Ну и как же нам перекидывать строки ви числа?" спросите вы. Проверять каждый символ, очистив строку от лишних знаков и точек. Потом конвертить допустимые символы в числа и восстанавливать знак, дробную чусть и тд???
Самый быстрый способ это просто "попробовать" 😜
#basic
Самый быстрый способ это просто "попробовать" 😜
text = "-0.3"Всё остальное это уже парсинг и разбор символов для иных целей.
try:
num = float(text)
except ValueError:
print('Dough!')
#basic
Допустим, имеется у нас задача: разделить одно число на другое и получить отдельно целое число и остаток от деления.
Например, исходные числа 15 и 2. В результате должны получить 7 и 1. То есть 7 раз двойка входит в состав 15 целиком и потом остаётся еще 1.
Как будем действовать?
Очевидно же, целое число вхождений получаем через floor division
Например, исходные числа 15 и 2. В результате должны получить 7 и 1. То есть 7 раз двойка входит в состав 15 целиком и потом остаётся еще 1.
Как будем действовать?
Очевидно же, целое число вхождений получаем через floor division
>>> 15//2
7
Остаток через деление по модулю>>> 15%2
1
Но можно сделать проще (ох, ну куда уж проще то 😄). В Python есть builtin функция divmod которая делает эти два действия в одно. >>> divmod(15, 2)
(7, 1)
#tricksPython 3.9 готовит нам приятный сюрприз в PEP584.
Ранее мы обсуждали как можно удобно сложить вместе два словаря получив новый словарь. Было много вариатов, но ни одного идеального. Наконец-то в Python добавили оператор для слияния словарей!!! Это оператор "|".
А это значит, что начиная с 3.9 соединить словари можно таким синтаксисом:
#pep
Ранее мы обсуждали как можно удобно сложить вместе два словаря получив новый словарь. Было много вариатов, но ни одного идеального. Наконец-то в Python добавили оператор для слияния словарей!!! Это оператор "|".
А это значит, что начиная с 3.9 соединить словари можно таким синтаксисом:
dct3 = dct1 | dct2
Чтобы обновить словарь, можно использовать такой синтаксисdct1 |= dct2
Данный функционал уже можно опробовать, установив первые релизы.#pep
Python Enhancement Proposals (PEPs)
PEP 584 – Add Union Operators To dict | peps.python.org
This PEP proposes adding merge (|) and update (|=) operators to the built-in dict class.
В Python есть стандартный модуль sched для синхронного планировщика задач. Что??? Синхронных??? В наш-то век "асинхронщины" и "параллельщины"!
Спокойно, сначала смотрим код, потом разбираемся.
Работает это так:
- создаём планировщик
- добавляем задачи в очередь с таймаутом и приоритетом
- запускаем и ждём пока завершится вся очередь
Смотрим пример:
Вывод получаем в соответствии со временем задержки:
Время указывается с момента старта очереди. Если время совпадает то сортировка идёт по приоритету.
Что значит синхронный планировщик? Это значит что задачи будут выполняться строго по очереди в одном потоке. Никаких мультипотоков и мультипроцессов, модуль прост как бревно!
Если у первой задачи стоит задержка 0сек а у второй 1сек, и при этом первая задача выполняется 3сек, то вторая задача выполнится только через 3 сек. Никакого параллельного запуска не будет.
Параметр delay следует понимать не как "запусти через N сек" а как "запусти не раньше чем через N сек". Время запуска следующей зависит от выполнения предыдущих задач.
Где это может пригодиться? Очередь задач, между которыми должен быть промежуток времени по какой-либо причине.
🔸 несколько синхронных задач, которые должны выполняться друг за другом но требующие ожидания обновления какой-либо инфраструктуры (тормозная сеть?)
🔸 сетевой API который имеет лимит на количество команд в единицу времени
🔸 фейковая задержка для генерации тестов или имитация поведения юзера.
Конечно, всё это можно решить банальным time.sleep() в нужном месте, но sched даёт несколько более удобный интерфейс управления задачами.
#libs
Спокойно, сначала смотрим код, потом разбираемся.
Работает это так:
- создаём планировщик
- добавляем задачи в очередь с таймаутом и приоритетом
- запускаем и ждём пока завершится вся очередь
Смотрим пример:
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
В прошлом посте мы рассмотрели синхронный планировщик задач sched. Функционал вроде не плох, но синхронное выполнение с блокировкой всё портит☹️. Можем ли мы как-то поправить ситуацию?
На самом деле можем (помимо отправки всего в subprocess).
Функция run() принимает аргумент blocking, который по умолчанию True.
То есть, если мы укажем
Если мы делаем неблокирующий запуск, то после вызова метода
Получается, что вместо ожидания таймаута без полезной нагрузки планировщик освобождает поток и сообщает через сколько ему пора будет продолжить работу. А что делать с этой информацией, решаете сами.
Выполнение следующих задач произойдет после следующего вызова run() и если пришло их время выполниться.
Например, вместо ожидания следующей задачи будем делать что-то полезное:
___________________
PPS. Да, в этих примерах я нагло использую глобальные переменные))) Не делайте так на реальных проектах
На самом деле можем (помимо отправки всего в 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. Да, в этих примерах я нагло использую глобальные переменные))) Не делайте так на реальных проектах
Telegram
Python Заметки
В Python есть стандартный модуль sched для синхронного планировщика задач. Что??? Синхронных??? В наш-то век "асинхронщины" и "параллельщины"!
Спокойно, сначала смотрим код, потом разбираемся.
Работает это так:
- создаём планировщик
- добавляем задачи…
Спокойно, сначала смотрим код, потом разбираемся.
Работает это так:
- создаём планировщик
- добавляем задачи…
Вы всё еще проверяете секретные данные оператором сравнения?
Чтобы сделать безопасное сравнение используйте метод secrets.compare_digest(). Он защитит операцию проверки от подобных атак.
>>> if password == user_password:Это небезопасный способ сравнения для стендалон приложений. Он уязвим к такому типу атаки как timing attack, позволяющий делать выводы и угадывать пароль на основании времени проверки.
>>> ...
Чтобы сделать безопасное сравнение используйте метод secrets.compare_digest(). Он защитит операцию проверки от подобных атак.
>>> import secretsВозможно вы ранее слышали про метод hmac.compare_digest(). Он не только делает то же самое, это один и тот же метод!
>>> if secrets.compare_digest(password, user_password):
>>> ...
>>> import secrets#libs
>>> import hmac
>>> hmac.compare_digest is secrets.compare_digest
True
Почему не стоит в коде использовать assert для проверки данных?
Действительно, команда очень удобна для быстрой проверки правдивости какого-либо факта.
Есть такая builtin константа
Стоит запустить интерпретатор в режиме оптимизации (флаг -O), константа
Действительно, команда очень удобна для быстрой проверки правдивости какого-либо факта.
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В стандартной поставке Python есть один полезный инструмент в библиотеке collections, это класс deque.
он очень похож на простой список но он намного быстрее работает в некоторых случаях.
Например для обработки элементов в начале списка у него есть дополнительные методы: extendleft(), appendleft() и popleft().
Запустим пару тестов!!! 🚀
Тестим функцию pop() (опустим код теста для краткости)
Почему так быстро? Дело в том что deque это некий аналог такого типа данных как "linked list data structure", в Python это занывается "двусвязные списки" (doubly-linked lists). Это список, но не в привычном представлении, а с особой оптимизированной структурой. При создании такого массива данные никуда не переносятся а только линкуются оттуда где были.
Да, это похоже на Python-лист, но линковка происходит иначе. Вместо того чтобы собирать некий стек ссылок и назвать его списком, в doubly-linked list каждый элемент просто ссылается на следующий.
Такая структура позволяет значительно ускорить, создание списка, изменение с любой стороны.
Где это может пригодиться? Конечно же в двусторонних очередях (double-ended queue). То есть когда мы добавляем элементы в начало а забираем с конца, или наоборот.
Минус такого подхода в просадке производительности для произвольного доступа к элементам в середине очереди.
Полный код тестов 🌎
______________
Реальное описание несколько сложней, я постарался передать основную суть.
#libs #tricks
он очень похож на простой список но он намного быстрее работает в некоторых случаях.
Например для обработки элементов в начале списка у него есть дополнительные методы: extendleft(), appendleft() и popleft().
Запустим пару тестов!!! 🚀
>>> from collections import dequeДобавление в конец списка работает примерно одинаково. Теперь попробуем вставлять элемент в начало массива.
>>> import time
>>>
>>> st = time.perf_counter()
>>> for _ in range(1000):
>>> l1 = deque()
>>> for i in range(5000):
>>> l1.append(i)
>>> en = round(time.perf_counter()-st, 3)
>>> print(f'Test deque: {en}sec')
>>>
>>> st = time.perf_counter()
>>> for _ in range(1000):
>>> l2 = list()
>>> for i in range(5000):
>>> l2.append(i)
>>> en = round(time.perf_counter()-st, 3)
>>> print(f'Test list: {en}sec')
Test deque: 0.452sec
Test list: 0.436sec
>>> st = time.perf_counter()Прирост производительности почти в 15 раз! 😲
>>> for _ in range(1000):
>>> l1 = deque()
>>> for i in range(5000):
>>> l1.appendleft(i)
>>> en = round(time.perf_counter()-st, 3)
>>> print(f'Test deque: {en}sec')
>>>
>>> st = time.perf_counter()
>>> for _ in range(1000):
>>> l2 = list()
>>> for i in range(5000):
>>> l2.insert(0, i)
>>> en = round(time.perf_counter()-st, 3)
>>> print(f'Test list: {en}sec')
Test deque: 0.435sec
Test list: 6.347sec
Тестим функцию pop() (опустим код теста для краткости)
Test deque: 0.48secТеперь pop(0) для list и popleft() для deque
Test list: 0.529sec
Test deque: 0.476secБыстрей примерно в 6.5 раз.
Test list: 3.101sec
Почему так быстро? Дело в том что deque это некий аналог такого типа данных как "linked list data structure", в Python это занывается "двусвязные списки" (doubly-linked lists). Это список, но не в привычном представлении, а с особой оптимизированной структурой. При создании такого массива данные никуда не переносятся а только линкуются оттуда где были.
Да, это похоже на Python-лист, но линковка происходит иначе. Вместо того чтобы собирать некий стек ссылок и назвать его списком, в doubly-linked list каждый элемент просто ссылается на следующий.
Такая структура позволяет значительно ускорить, создание списка, изменение с любой стороны.
Где это может пригодиться? Конечно же в двусторонних очередях (double-ended queue). То есть когда мы добавляем элементы в начало а забираем с конца, или наоборот.
Минус такого подхода в просадке производительности для произвольного доступа к элементам в середине очереди.
Полный код тестов 🌎
______________
Реальное описание несколько сложней, я постарался передать основную суть.
#libs #tricks
Gist
list_vs_deque.py
GitHub Gist: instantly share code, notes, and snippets.