TGTGInsighttelegram intelligenceLIVE / telegram public index
Назад кон каналите
Python Заметки avatar

TGINSIGHT CHAT

Python Заметки

@pythonotes

Education

Интересные заметки и обучающие материалы по Python Контакт: @paulwinex ⚠️ Рекламу на канале не делаю!⚠️ Хештеги для поиска: #tricks #libs #pep #basic #regex #qt #django #2to3 #source #offtop

Претплатници2,220Тековни претплатници
Следени објави384Број на индексирани објави
Неодамнешен опфат15,390Збир на неодамнешни прегледи
Неодамнешни објави

Неодамнешни објави

Ознака: #tricks · 178 објави

当前筛选 #tricks清除筛选

Објавено 25 дек.

Как в простом скрипте выполнить отложенное действие или даже несколько? Например, нам требуется удалить файл через 10 секунд после его создания, но при этом основной поток не должен просто остановиться на 10 секунд с помощью time.sleep(). Допустим, у нас также нет никаких асинхронных очередей задач типа rq или celery. Самый простой способ это класс threading.Timer. Это удобный способ выполнить отложенное действие, которое не блокирует основной поток. А так же это пример как писать многопоточный код на базе класса threading.Thread. Всё что делает этот класс, это создаёт отдельный поток, в котором и будет запущена функция ожидания. from threading import Timer import os filename = '/home/user/data.txt' t = Timer(10, lambda: os.remove(filename)) t.start() Можно проверить выполнилось ли действие с помощью неблокирующго метода ивента is_set() if t.finished.is_set(): # do something или отменить выполнение t.cancel() А что будет если процесс интерпретатора завершится ДО завершения таймаута? Тут у нас два варианта: 🔸 Если поток запущен как Daemon то он завершится вместе с программой, тем самым отменив выполнение колбека. 🔸 В противном случае программа не завершится пока поток не завершится. По умолчанию режим Daemon отключен, то есть программа в любом случае дождётся исполнения колбека и только потом завершится. Переключение режима следует делать ДО запуска потока! t = Timer(...) t.setDaemon(True) t.start() #tricks

1,670 views

Hashtags

Објавено 11 дек.

Еще раз про JSON Для создания кастомной сериализации объектов в JSON не обязательно создавать класс-сериализатор. Достаточно указать функцию default() которую в свою очередь подать в виде лямбды. За читаемость примера не ручаюсь но выйдет что-то вроде такого: json.dumps(my_data, default=lambda obj: { 'type': f'{obj.__class__.__module__}.{obj.__class__.__name__}', 'data': getattr(obj, '__dict__', repr(obj))}) В примере я добавил полный путь к классу включая имя модуля. ⚠️ Повторяю! В ситуации, когда данные неизвестны, такой подход может привести к непредсказуемому поведению! Лично я использовал его только для дебага, когда требовалось получить хоть что-то в виде JOSN а не ошибку. Явное лучше чем неявное 😉 Кстати, обычная функция часто лучше чем лямбда 😬 Так что вам эта же функция в нормальном виде def default_hook(obj): return { 'type': f'{obj.__class__.__module__}.' f'{obj.__class__.__name__}', 'data': getattr(obj, '__dict__', repr(obj)) } json.dumps(my_data, default=default_hook) Почему бы не организовать поддержку всех стандартных объектов Python в стандартном JSON-энкодере? Я думаю дело в неочевидности этого процесса. Как можно сериализовать float? Тут вполне очевидно. А как сериализовать datetime? Вот тут тысяча и один вариант как можно форматировать дату. Поэтому данный этап отадётся на откуп разработчику. Меня устроило бы добавление в спецификацию класса метода __json__ по аналогии с __fspath__, который использовался бы стандартным энкодером. Метод возвращал бы поддерживаемый для JSON объект. Тогда не требуется что-то указывать в функции dump() и наш класс может использоваться в других модулях, где код уже записан и вставить что-то в default м не можем. Но пока этого нет (и будет ли?) мы по-прежнему добавляем в класс метод toJson() и вызываем его, отправляя в json.dump(). #tricks#libs

1,390 views

Hashtags

Објавено 9 дек.

В прошлом посте мы научили JSON понимать новый тип данных. Но что если придётся записывать в JSON много разных неподдерживаемых типов? Описывать для каждого отдельный if isinstance()? А если нам не известно что именно придётся сериализовать и нужно поддерживать в принципе всё что угодно? Ну хотя бы как-то записать объект чтобы не было ошибки и чтобы объект записался в JSON более менее информативно. В этом случае можно вызвать стандартные способы репрезентации объекта. Самое простое это функция repr(). И тут уж как повезёт с тем, как именно разработчик позаботился о таком поведении его класса. Более описательный метод, это закинуть объект в словарь с именем класса и данными инстанса. В общем, возвращаясь к примеру из прошлого поста, мы просто убираем проверку конкретного типа class MySerializer(json.JSONEncoder): def default(self, obj): return {'type': obj.__class__.__name__, 'data': vars(obj)} Функция vars() аналогична обращению к атрибуту __dict__ Теперь любой ранее неизвестный объект будет успешно сериализован... или нет? >>> json.dumps(datetime.datetime.now(), cls=MySerializer) AttributeError: 'datetime.datetime' object has no attribute '__dict__ Так уж вышло, что не любой объект имеет атрибут __dict__. В таком случае используем repr() class MySerializer(json.JSONEncoder): def default(self, obj): return {'type': obj.__class__.__name__, 'data': getattr(obj, '__dict__', repr(obj))} То есть мы пробуем забрать данные инстанса, и если не получается то вызываем стандартное строковое представление объекта в надежде что разработчики позаботились о нём. >>> json.dumps(datetime.now(), cls=MySerializer) '{"type": "datetime", "data": "datetime.datetime(...)"}' ⚠️ PS: Не могу сказать что решение идеально. Такую неявную сериализацию можно сравнить с замалчиванием ошибок. try: do_something() except: pass То есть в какой-то момент вы будете получать совершенно бесполезные данные и придётся искать где это происходит. Если не определён метод __repr__ то вы получите что-то вроде такого <__main__.MyClass object at 0x00000147CE48CBC8> Что с этим делать? Непонятно! 🤔 Скорее всего найдутся люди, которые осудят такой подход. Может выбрасывать ошибку в случае отсутствия метода __repr__ в классе? #libs#tricks

1,230 views

Hashtags

Објавено 30 ное.

Как получить список месяцев или дней недели на русском языке? Можно "нахардкодить" список прямо в своём коде😖. Но лучше воспользоваться стандартным модулем calendar установив нужную локализацию. >>> import calendar >>> import locale >>> locale.setlocale(category =locale.LC_ALL, locale="Russian") >>> print(list(calendar.month_name)[1:]) ['Январь', 'Февраль', 'Март', ... >>> print(list(calendar.day_name)) ['понедельник', 'вторник', 'среда', ... >>> print(list(calendar.day_abbr)) ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'] Модуль datetime тоже будет с переводом >>> import datetime >>> datetime.date.today().strftime('%B %Y года, %A') 'Ноябрь 2020 года, суббота' (кстати, именно так получается список month_name) А вот так можно распечатать календарь на год на русском прямо в терминал: print(calendar.LocaleTextCalendar(locale="Russian_Russia").formatyear(2021)) #libs#tricks

1,270 views

Hashtags

Објавено 27 ное.

Делал оптимизацию своей библиотеки кеширования ответов от API. Нужно было выяснить, какие функции заставляют обращаться в API минуя кеш. Мне нужно было видеть полный стек вызова функции запроса в API. Конечно же первое что приходит в голову это traceback.print_stack(). Но мне не нравится его многословное форматирование. Поэтому я решил сделать свой аналог. Делается это совсем не сложно, достаточно посмотреть как работает функция print_stack() и сделать аналогично. Все дополнительные функции уже имеются в модуле traceback. Первым делом достаём фрейм frame = sys._getframe().f_back Теперь преобразуем его в стек вызовов stack = traceback.extract_stack(frame) Теперь у нас есть список инстансов класса FrameSummary в порядке их вызова. Остаётся сделать вывод for item in stack: print(f"{os.path.basename(item.filename)}[{item.lineno}] -> {item.name}") Получается что-то вроде такого __main__.py[123] -> <module> module1.py[456] -> func1 module2.py[789] -> func2 Как можно улучшить? 🔸 Фильтр по имени файла. Удобно при работе через дебагер, например, в Pycharm. Пропускаются ненужные вызовы. 🔸 Выводить передаваемые аргументы. Придется сделать свой extract_stack. 🔸 Добавить цвета Пример реализации здесь. Просто вызовите эту функцию в том месте где требуется увидеть цепочку вызовов. PS. Да, я знаю на сколько это странный способ дебага. Но такой инструмент потребовался под конкретную ситуацию и он справился хорошо. PPS: А теперь суровый хардкод! Всё в одну строку плюс некоторые дополнения в форматировании! (не повторяйте это дома!) print_stack_simple = lambda: print(*[f"{' ' * i}{os.path.basename(item.filename)}[{item.lineno}] -> {item.name}{'()' if not '>' in item.name else ''}" for i, item in enumerate(traceback.extract_stack(sys._getframe().f_back)[:-1])], sep='\n') #tricks

1,260 views

Hashtags

Објавено 13 ное.

Почему рекомендуют каждый импорт делать на новой строке? Просто так написано в PEP8, скажете вы. Да, это действительно так. Но PEP8 это стилистические рекомендации. Какая практическая польза от такой записи? Несколько раз мои студенты спрашивали зачем писать длинней когда можно короче? Не это ли один из основных принципов в Python? Как мы хотим писать: import os, sys, subprocess Как рекомендуют import os import sys import subprocess Если "нельзя" писать в одну строку, то зачем добавили такую возможность - перечислять имена импорта? Объясняю в два этапа: 🔸Перечисление добавлено для возможности импорта через from, когда из модуля требуется импортнуть несколько имён from os.path import join, expanduser, sep В PEP328 добавили возможность перечисление вставлять в скобки чтобы переносить на новую строку без экранирования символа новой строки Было from os.path import join, expanduser, \ sep, basename, exists Стало from os.path import (join, expanduser, sep, basename, exists) 🔸 Практическая польза от импортов на разных строках заметна когда вы делаете слияние разных веток кода с конфликтами или просто с изменениями. Допустим, есть два варианта кода # file1.py import fnmatch import time import json import uuid #file2.py import fnmatch, time, json, uuid Зачем-то нам потребовалось изменить импорт, вместо uuid импортим sys. Что покажет нам diff? # file1.py -import uuid +import sys # file2.py -import fnmatch, time, json, uuid +import fnmatch, time, json, sys Заметили разницу? Пусть даже для GIT это совершенно не проблема, кодревью будет происходить удобней с точки зрения человека. Да, это объяснение человека, который сам делает кодревью (и регулярно сам же нарушает эту рекомендацию, о чем потом жалеет😭) Есть ли у вас еще доводы в пользу каждого импорта на новой строке? #tricks#pep

1,340 views

Hashtags

Објавено 6 ное.

Как проверить является ли число чётным или нечётным? Конечно же оператор "остаток от деления" is_odd = bool(x%2) Если остаток от деления равен 0 то это чётное число, если 1 то нечётное. Но есть еще альтернативный способ, это битовый оператор AND is_odd = bool(x&1) Как это работает? Распечатайте ряд целых чисел в двоичном формате >>> for i in range(0, 7): >>> print(bin(i)) 0b0 0b1 0b10 0b11 0b100 0b101 0b110 Можно заметить, что последние биты чередуются между 0 и 1. А что делает оператор AND (&)? Он возвращает 1 если оба бита числа имеют значение 1. Учитывая, что у числа 1 в двоичном представлении только один бит равен 1, и он последний, то все остальные биты от любого числа всегда будут возвращать 0. Таким образом, делая операцию AND любого числа с числом 1 мы всегда получим либо число 0 либо число 1. Это будет зависеть от последнего бита другого операнда. Выглядит это примерно так >>> 38 & 1 0 00100110 & 00000001 = 00000000 >>> 227 & 1 1 11100011 & 00000001 = 00000001 #tricks

1,250 views

Hashtags

Објавено 2 ное.

Модуль Qt․py это не просто текстовый модуль, его компоненты генерируются на лету в зависимости от ситуации. Поэтому ваша любимая IDE не сможет качественно сообразить автокомплиты под этот модуль. Решение здесь более чем очевидно, надо сделать stubs-файлы. Это файлы с расширением .pyi, описывающие содержимое модуля но не имеющие рабочего кода. Ну что, готовы потратить пару месяцев своей жизни чтобы описать все классы Qt и их методы? 😭 Расслабьтесь, за вас это уже сделали добрые люди. Спасибо Fredrik Averpil ! Качаем здесь ⬇️ https://github.com/fredrikaverpil/Qt.py/tree/stubs/stubs/Qt Не думаю что стоит устанавливать Qt․py из этого репозитория. Он там не обновляется. Так что забираем только файлы .pyi. За актуальность этих файлов тоже не ручаюсь, но большинство методов там имеются. Установка: 🔸 Вариант 1: - находим куда установлен сам модуль Qt․py, это будет одинокий файл который так и называется Qt․py - кидаем директорию рядом с ним (если есть доступ на запись). Должно получиться так: 📁 site-packages\ 📄 Qt.py 📁 Qt\ ... 🔸 Вариант 2 - копируем директорию Qt куда угодно - пробиваем путь к ней в настройках энвайронмента в переменную PATH так, чтобы путь был ДО директории Qt. Закинуть можно и в свою домашнюю директорию. Если скопируете сюда: ~/stubs/Qt То переменную пишем так export PATH=~/stubs:${PATH} После этого IDE должна распарсить stubs-файлы и автокомплиты появятся 😎 #qt#libs#tricks

1,210 views

Објавено 30 окт.

В прошлом посте говоря "Все вызовы теперь одинаковы" я несколько слукавил. Всё-таки есть в этом зоопарке версий некоторая несовместимость вызов которой просто так не унифицировать. Эти моменты вынесены в отдельный модуль QtCompat (compatibility). Там не так много функций но они довольно полезны. Этот модуль содержит унификаци модуля shiboken2, функций loadUi, translate и несколько переименованных функций классов или изменённую сигнатуру аргументов и возвращаемых значений. Это единственное исключение из правила когда вам потребуется где-то изменить свой код кроме импортов и этот код не похож на обычный код PySide2. Например, в PyQt4 и PySide есть метод QHeaderView.setResizeMode Для PyQt5 и PySide2 они были благополучно переименованы в QHeaderView.setSectionResizeMode Чтобы применить этот метод следует использовать такой код from Qt import QtCompath header = self.horizontalHeader() QtCompat.QHeaderView.setSectionResizeMode(header, QtWidgets.QHeaderView.Fixed) Унификация загрузки UI файлов: # PySide2 from PySide2.QtUiTools import QUiLoader loader = QUiLoader() widget = loader.load(ui_file) # PyQt5 from PyQt5 import uic widget = uic.loadUi(ui_file) # Qt.py from Qt import QtCompat widget = QtCompat.loadUi(ui_file) Хорошо что таких моментов не много и их легко запомнить. Полный список можно посмотреть в таблице. #qt#tricks

1,150 views

Hashtags

Објавено 21 окт.

А знаете ли вы, что в Bash есть особая переменная "$_" ? Из описания можно узнать, что в переменную "$_" помещается последний аргумент последней команды. Что-то знакомое? Конечно, в Python есть примерно тоже самое. Мы знаем, что дата первого релиза Bash (8 июня 1989) несколько раньше чем первый релиз Python (20 февраля 1991). Если учесть, что по задумке автора Python это агрегация самых успешных практик отовсюду, можно ли сказать что именно эта фишка к нему пришла из Bash? Ни подтверждения ни опровержения я пока не нашел. Давайте просто считать это совпадением 😉 А как это может быть полезно в Bash? Приведу простой пример, который оценят те, кто часто работает в терминале. Допустим, нам надо создать директорию и зайти в неё. Что обычно делаем? :~$ mkdir foldername :~$ cd foldername :~/foldername$ Как это сделать в одну команду? :~$ mkdir foldername && cd foldername :~/foldername$ Уже лучше, но хочется короче, без повторов. Вот тут и пригодится наша переменная. Напомню, что в неё сохраняется последний аргумент предыдущей команды. :~$ mkdir foldername && cd $_ :~/foldername$ _________________ PS. Символы "&&"" разделяют команды и означают, что требуется выполнить вторую команду только если первая завершилась успешно. #linux#tricks

1,220 views

Објавено 19 окт.

Давайте немного прокачаем стандартный словарь. Зачем? А потому что Python это позволяет))) Добавим возможность работать с ключами словаря как с атрибутами объекта. Для этого нам потребуется определить три метода: __getattr__ - получение атрибута __setattr__ - изменение атрибута __delattr__ - удаление атрибута Наследоваться будем от стандартного словаря class _AttribDict(dict): def __getattr__(self, name): return self[name] def __setattr__(self, name, value): self[name] = value def __delattr__(self, name): del self[name] Подменяем стандартный словарь в одну строку __import__('builtins').__dict__['dict'] = _AttribDict Всё, можно тестить 😬 >>> obj = dict(name='Object1', id=14) >>> print(obj) {'name': 'Object1', 'id': 14} Выглядит как обычный словарь >>> obj['name'] = 'Thing 1' >>> print(obj['name']) Thing 1 Изменение ключей работает. То есть стандартное поведение не сломали. А теперь попробуем обратиться к атрибутам >>> print(obj.name) Thing 1 Пробуем изменить ключ через атрибут >>> obj.name = 'Thing 2' >>> print(obj.name) Thing 2 И удаляем ключ через атрибут del obj.name print(obj) {'id': 14} Теперь с ключами словаря можно работать и обычным способом: dict['key'] и как с атрибутами: dict.key Выглядит интересно. Но помните , если Python позволяет это делать, этоне значит что это можно делать👮‍♂️! Подобные эксперименты лучше оставить для развлечения и не допускать в прод. Хорошая разминка, не более того. #tricks

1,170 views

Hashtags

Објавено 14 окт.

🙄Разминка для ума! Треугольник Серпинского, интересная фигура которую построить достаточно просто. Алгоритм такой: 1. создаём любые 3 точки на плоскости 2. из этих точек случайно выбираем любую, как начальную 3. случайно выбираем любую точку из этих же трёх точек как цель 4. перемещаемся в сторону цели на половину расстояния 5. повторяем бесконечно с пункта 3 Если сделать достаточно много итераций то вырисовывается интересная фигура. Треугольник, в который вписаны более мелкие треугольники. Это самый настоящий фрактал! Я собрал пример построения такой фигуры на базе Qt. 🌎 Код можно посмотреть здесь. С помощью paintEvent я рисую точки по озвученному алгоритму. Каждые 10 секунд либо по клику на виджете строится следующий треугольник. Особенности примера: 🔸 Атрибут Qt.WA_OpaquePaintEvent позволяет сохранить то, что было нарисовано в прошлой итерации. Таким образом мы видим постепенное наполнение точек а не мелькающую одну точку. 🔸QTimer позволяет создавать отложенные вызовы один раз или с повторением через интервал. 🔸QColor.fromHsv() позволяет создать рандомный но предсказуемый цвет с помощью HSV схемы. Не слишком светлый и не слишком тёмный но всегда с разный. Рандомизации подвергается только смещение по цветовому кругу (Hue), яркость (Value) и насыщенность (Saturation) можно контролировать отдельно в своих пределах или оставить статичными. Обычный рандом цвета по RGB не даёт такой предсказуемый результат. 🔸 Каждый новый цикл с новым треугольником предварительно затемняет предыдущие через этот вызов painter.fillRect(rec, QColor(0, 0, 0, 100)) То есть полупрозрачный цвет. Таким образом, чем старше треугольник, тем он темней. Если сделать виджет фулскрин, то у нас получится некий ScreenSaver))) 🔸 Да, я знаю, что рисование в Qt не самый лучший способ сделать этот пример) Скорее всего самый НЕподходящий. Попробуйте сделать тоже самое но другими средствами. #qt#source#tricks

1,230 views
12•••678910•••1415
ПретходнаСтраница 8 од 15Следна