TGINSIGHT CHAT
Python Заметки
@pythonotes
EducationИнтересные заметки и обучающие материалы по Python Контакт: @paulwinex ⚠️ Рекламу на канале не делаю!⚠️ Хештеги для поиска: #tricks #libs #pep #basic #regex #qt #django #2to3 #source #offtop
Неодамнешни објави
Ознака: #tricks · 178 објави
Објавено 24 јун.
Как быстро распечатать красиво время имея в наличии число секунд ⏱? Конечно, можно посчитать сколько в этих секундах минут, часов и потом посчитать остаток, но есть способ быстрей! Это стандартный класс datetime.timedelta Просто создайте класс с указанием того что у вас есть и конвертните его в строку. Он всё посчитает за вас и покажет стандартный формат времени. >>> from datetime import timedelta >>> str(timedelta(seconds=1024)) '0:17:04' Можно просто распечатать если результат нужен в консоли >>> print(timedelta(minutes=128)) 2:08:18 Также поддерживаются нецелые значения. Например, нецелое число минут будет преобразовано в секунды. >>> print(timedelta(minutes=256.5)) 4:16:30 Вот так можно распечатать полтора часа >>> print(timedelta(hours=1.5)) 1:30:00 Можно выходить за пределы одних суток, появится количество дней >>> print(timedelta(hours=64.32)) 2 days, 16:19:12 >>> print(timedelta(weeks=20.32)) 142 days, 5:45:36 А еще они поддерживают математические операции >>> print(timedelta(minutes=5) + timedelta(hours=2)) 2:05:00 #libs#tricks
Објавено 19 јун.
Ещё немного про base64. Собрал пример со встроенной в код картинкой. Это иконка для окна на PySide2. Файл кодирован в base64 и просто сохранён в переменной. Для использования этих данных даже не пришлось сохранять их в новый файл. Иконка создаётся на лету с помощью метода QPixmap.loadFromData() ... raw_data = base64.decodebytes(ico_encoded) ico = QPixmap() ico.loadFromData(raw_data, "PNG") ... 🌎 Полный пример смотрите в gists. #libs#tricks#qt
Објавено 17 јун.
Как сохранить картинку непосредственно в Python-модуль? Для этого нам пригодится библиотека base64. Этот способ кодирование позволяет любые бинарные или текстовые данные закодировать с помощью 64 ASCII символов. То есть получится простая строка. Зачем это вообще? Это обратимое кодирование позволит любые бинарные данные сохранить в текстовом виде и отправить туда где для записи поддерживается только текст. Например: - встроить в URL в GET запрос как параметр - встроить в тело email - сохранить в Python-модуль как переменную - сохранить любой конфиг, например JSON - записать в базу данных - зашить в HTML (XML) или CSS Чаще всего так кодируют изображения в HTML и в CSS. Есть даже специальные сервисы для кодирование изображений. Давайте закодируем и декодируем картинку. Кодирование: >>> import base64 >>> src_path = 'image1.png' >>> with open(src_path, 'rb') as f: >>> raw_data = f.read() >>> image_encoded = base64.encodebytes(raw_data) >>> print(image_encoded) b'iVBORw...Jggg==\n' Теперь наша картинка это просто байты в переменной. Её можно сохранить непосредственно в модуле и использовать позже. Декодирование: >>> save_path = "image2.png" >>> raw_data = base64.decodebytes(image_encoded) >>> with open(save_path, 'wb') as f: >>> f.write(raw_data) Картинка восстановлена обратно в файл. Стоит помнить что: - это не шифрование, пароли так не стоит прятать. Строка легко декодируется в исходник. - размер данных после кодирования увеличивается примерно на четверть - не храните изображения в базе данных таким способом! #libs#tricks
Објавено 12 јун.
Расширенный вариант каунтера из прошлого примера. Оказалось ему есть куда еще развиваться и без унарных операторов))). Добавим обработку бинарных операторов "+" и "-". Сделаем возможность прибавлять к канутеру любое число обычным синтаксисом def __add__(self, other): self.val += int(other) def __sub__(self, other): self.val += int(other) Теперь можно делать так: >>> c = Counter(3) >>> c + 2 >>> print(c) Count: 5 Добавим обработку для случая когда наш каунтер справа def __radd__(self, other): self.__add__(other) def __rsub__(self, other): self.__sub__(other) >>> c = Counter(3) >>> 2 + с >>> print(c) Count: 5 Добавим во все методы возвращаемое значение и будем возвращать self: def __pos__(self, *args): self.val += 1 return self def __neg__(self): self.val -= 1 return self ... Тоже самое в остальных. Теперь такой синтаксис тоже допустим (и тоже антипаттерн!): >>> c = Counter() >>> print(c) Count: 0 >>> ++c Count: 2 >>> ++++c Count: 6 >>> ---c Count: 3 А так же теперь доступен такой вариант >>> c += 2 Count: 5 Следующие три записи идентичны по результату с += 2 с + 2 ++c Во всех случаях каунтер с увеличится на 2 Еще раз о главном! Все эти операции изменяют один из операндов, что не принято в Python. Так что дальше эксперементов не стоит уходить. Класс требует доработки чтобы не конфликтовать с принятыми нормами. 🌎Ссылка на полный листинг __________________________ PS. Если инкремент изменить на 0.5, то у нас получится синтаксис весьма похожий на C. То есть, чтобы прибавить 1 надо будет написать ++x 😎, но это совсем уже будет вне всяких приличий 🥴 #tricks
Hashtags
Објавено 10 јун.
В Python очень классной идеей является возможность переопределять взаимодействие объекта с любыми операторами, в том числе и унарные операторы. И это можно очень интересно применить! Долгое время меня смущало отсутствие возможности инкрементировать число на 1 с помощью синтаксиса С++. То есть вот так: val++ Эта команда просто прибавляет единицу к значению val. В Python аналогичная операция делается так: val += 1 Ну такой cебе ZEN😭. Давайте напишем класс, который сможет провернуть что-то подобное. А именно, сделаем чтобы величина значения увеличивалась на 1 с помощью такой записи: >>> c = Counter() >>> print(c) 0 >>> +c 1 Да, вместо x++ мы сделаем +x, чтобы не конфликтовать со стандартным синтаксисом. Но и это уже не плохо! Для взаимодействия с оператором "+" есть магический метод __add__, но для бинарного оператора. То есть когда операнда два. Для унарной версии оператора "+" есть метод __pos__, что означает positive. То есть как себя ведёт объект когда его пытаются сделать положительным. Вот его и используем: class Counter(object): def __init__(self, init=0): self.val = init def __pos__(self, *args): self.val += 1 def __repr__(self): return 'Count: {}'.format(self.val) Чтобы удобно было смотреть на результат, добавил метод __repr__. Проверяем что получится. >>> c = Counter() >>> print(c) Count: 0 >>> +c Count: 1 >>> +c Count: 2 Отлично, сделали счётчик с необычным синтаксисом! Добавим аналогичный метод для оператора "-" __neg__, что означает negate и описывает реакцию на попытку сделать число отрицательным. Теперь можем двигать значение в обе стороны: >>> class Counter(object): ... >>> def __neg__(self): >>> self.val -= 1 >>> c = Counter(5) >>> print(c) Count: 5 >>> +c Count: 6 >>> -c Count: 5 Стоит учесть, что данная реализация методов изменяет сам объект а не возвращает новый изменённый, как это принято в Python. Так что весь пример не очень Pythonic-way). Но такое решение реализует необходимый нам минималистичный синтаксис. Не буду утверждать, что пример получился полезным с точки зрения использования в реальной работе. Но для практики изучения вышло вполне интересно. #tricks
Hashtags
Објавено 8 јун.
Есть забавный трик с defaultdict, это бесконечно-рекурсивное дерево. Самое забавное что создать его можно в одну короткую строку. def tree(): return defaultdict(tree) Теперь можете создавать вложенные словари любой глубины! levels = tree() levels['lvl1']['lvl2']['lvl3'] = 'item' И таким образом строить любую иерархическую систему. Примеры и описание на странице автора: 🌎https://gist.github.com/hrldcpr/2012250 #tricks
Hashtags
Објавено 5 јун.
Бывают задачи когда из большого массива объектов требуется отфильтровать эти объекты по категориям. В результате получаем словарь примерно такого вида: data = { "category1": [item1, item2, ...], "category2": [item1, item2, ...], ... } При этом заранее мы не знаем список категорий и их необходимо добавлять в процессе итерации. Как такой код будет выглядеть: items = [...] sorted_items = {} for item in items: cat = get_category(item) if cat not in sorted_items: sorted_items[cat] = [] sorted_items[cat].append(item) Значением ключа является не сам элемент а промежуточный объект это список. Потому нам следует сначала убедиться, что он есть, а если нету то создать. Альтернативный код делающий тоже самое: for item in items: cat = get_category(item) if cat not in sorted_items: sorted_items[cat] = [item] else: sorted_items[cat].append(item) Чтобы избежать этой проверки можно использовать тип defaultdict, это очень простой класс который сам за вас сделает проверку и добавит нужный тип если его нет. Просто укажите нужный тип в конструкторе. from collections import defauldict items = [...] sorted_items = defaultdict(list) for item in items: sorted_items[get_category(item)].append(item) Если указанного ключа нет в словаре, то он создаётся сразу со списком в значении и возвращается как будто он там и был. #tricks#libs
Објавено 3 јун.
Порой бывает необходимо работать с JSON файлами ручками, читая или изменяя данные. И очень не удобно, когда юникод в файле записан в виде кодированных символов. >>> import json >>> data = {'title': 'Привет Медвед!'} >>> print(json.dumps(data)) '{"title": "\\u041f\\u0440\\u0438\\u0432\\u0435\\u0442 \\u041c\\u0435\\u0434\\u0432\\u0435\\u0434!"}' Эх, безобразие! Ни прочитать нормально, ни поправить. Чтобы такое поведение изменить, достаточно добавить аргумент ensure_ascii=False >>> json.dumps(data, ensure_ascii=False) '{"title": "Привет Медвед!"}' Теперь символы не кодируются в Unicode. В файл запишется в таком же виде. ____________________ Для тех кто в танке (всё еще на Python 2🚂 ). Строку следует делать как unicode, и для записи в файл использовать модуль codecs. >>> import json, codecs >>> data = {'title': u'Привет Медвед!'} >>> with codecs.open(path, "w", encoding='utf-8') as f: >>> json.dump(data, f, ensure_ascii=False) #libs#tricks
Објавено 29 мај
В стандартной поставке Python есть один полезный инструмент в библиотеке collections, это класс deque. он очень похож на простой список но он намного быстрее работает в некоторых случаях. Например для обработки элементов в начале списка у него есть дополнительные методы: 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() >>> 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 Прирост производительности почти в 15 раз! 😲 Тестим функцию pop() (опустим код теста для краткости) Test deque: 0.48sec Test list: 0.529sec Теперь pop(0) для list и popleft() для deque Test deque: 0.476sec Test list: 3.101sec Быстрей примерно в 6.5 раз. Почему так быстро? Дело в том что deque это некий аналог такого типа данных как "linked list data structure", в Python это занывается "двусвязные списки" (doubly-linked lists). Это список, но не в привычном представлении, а с особой оптимизированной структурой. При создании такого массива данные никуда не переносятся а только линкуются оттуда где были. Да, это похоже на Python-лист, но линковка происходит иначе. Вместо того чтобы собирать некий стек ссылок и назвать его списком, в doubly-linked list каждый элемент просто ссылается на следующий. Такая структура позволяет значительно ускорить, создание списка, изменение с любой стороны. Где это может пригодиться? Конечно же в двусторонних очередях (double-ended queue). То есть когда мы добавляем элементы в начало а забираем с конца, или наоборот. Минус такого подхода в просадке производительности для произвольного доступа к элементам в середине очереди. Полный код тестов 🌎 ______________ Реальное описание несколько сложней, я постарался передать основную суть. #libs#tricks
Објавено 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
Објавено 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
Објавено 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