TGINSIGHT CHAT
Python Заметки
@pythonotes
EducationИнтересные заметки и обучающие материалы по Python Контакт: @paulwinex ⚠️ Рекламу на канале не делаю!⚠️ Хештеги для поиска: #tricks #libs #pep #basic #regex #qt #django #2to3 #source #offtop
Неодамнешни објави
Страница 27 од 32 · 384 објави
Објавено 1 мај
В прошлом посте мы получали полное имя метода. В атрибут ˍˍqualnameˍˍ записано имя только до класса, к которому метод принадлежит. Как быть если нужно полное имя с модулем, а мы имеем только объект метода без инстанса или ссылки на класс? func = get_my_method() В результате этого псведо-выражения мы имеем только сам метод класса. Есть ли в нём информация к какому классу он принадлежит? Конечно есть, это атрибут ˍˍselfˍˍ. Он ссылается на инстанс. А оттуда, как не трудно догадаться, можно получить и ссылку на класс через атрибут ˍˍclassˍˍ: cls = func.__self__.__class__ итого полное имя будет выглядеть так full_name = '.'.join([ func.__self__.__class__.__module__, func.__qualname__] ) #tricks
Hashtags
Објавено 29 апр.
Каждый модуль в Python имеет атрибут ˍˍnameˍˍ. В него записывается строка, содержащая полное имя модуля. Например: >>> 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' #pep
Hashtags
Објавено 27 апр.
Ранее я делал серию постов про битовые операторы. Вот вам ещё один наглядный пример как это используется в 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.me/pythonotes/45 В общем, это пример применения побитовых операций в самом Python. Теперь вы знаете Python еще немного лучше) #tricks#regex#libs
Објавено 25 апр.
Хотите оформить свои CLI скрипты красивым прогрессбаром? Стоит посмотреть на библиотеку 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
Објавено 23 апр.
20 апреля 2020 года вышел последний релиз Python версии 2. Им стал билд 2.7.18. На этом точно всё, официально версия закрыта. Развитие будет только за счёт сторонних разработчиков в контексте их проектов, где 2й Python никак не обойти.
Објавено 21 апр.
Регулярные выражения иногда могут быть просто монструозными. Выглядеть это может крайне запутанно. Сами регэкспы и без того история непростая, а когда это длинный паттерн на несколько десятков знаков, разобрать там что-либо становится не просто. Но на помощь приходит Python и его стремление сделать нашу жизнь проще! В функциях регулярок можно после паттерна указывать флаги, один из которых позволяет писать паттерны более свободно. А именно, добавлять пробелы и переносы, которые будут игнорированы. В результате мы можем разбить паттерн на строки и добавить комментов. Чтобы это сработало нужно добавить флаг re.VERBOSE. Пробелы в паттерне теперь следует указывать явно спец символами. Согласитесь, что даже с именованными группами а таком виде регэкспа выглядит вполне сносно 😉. #tricks#regex
Објавено 19 апр.
Стандартный модуль json имеет command line интерфейс. Он умеет делать валидацию JSON-данных и переводить однострочный вариант в форматированный. Изменяем форматирование $ 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
Hashtags
Објавено 17 апр.
Помните пример с неудачной вставкой комментария? x = 1 + \ # comment 2 x = 1 + \ # comment 2 Я говорил что так делать не стоит, так как вызывает ошибку. Но я же скажу как эту ошибку поправить! И это просто (((СКОБОЧКИ))) 😎! x = (1 + # comment 2) x = (1 + # comment 2) Теперь интерпретатор сообразит что к чему. Кстати, они же помогают избежать символа "\" в других случаях Без скобок from Qt.QtWidgets import QPushButton, QLabel, \ QTextEdit, QListWidget Со скобками from Qt.QtWidgets import (QPushButton, QLabel, # здесь можно вставить комментарий QTextEdit, QListWidget) Без скобок if a > 0 \ and b > 0 \ and c < 10: pass Со скобками if (a > 0 # комент and b > 0 # комент and c < 10): pass Но прошу заметить, как порой страшно выглядят данные конструкции. Применяйте осторожно! Да и лишние скобки в Python смотрятся не всегда уместно) #tricks
Hashtags
Објавено 15 апр.
В моих триках вы часто можете видеть способы сокращения кода. Когда длинный код заменяется на однострочный. Скорее всего, я продолжу эту практику, но хочу напомнить о важной вещи! ⚠️Понятное лучше чем запутанное⚠️ Да, иногда эти трики помогают сделать код чище, когда они применяются в виде "синтаксического сахара". Но если не следить за собой то эта привычка может создавать совершенно безумные вещи! Если это можно сделать в Python, то это не значит что это нужно сделать. Смотрим реальный пример из моей практики, когда я увлёкся и загнал все сравнения в один оператор if >>> if any([fnmatch(rel, pat) for pat in include_files]) and not any([fnmatch(rel, pat) for pat in ignore_files]) and not any([any([fnmatch(name, pat) for pat in ignore_files]) for name in Path(rel).parts]): >>> do_comething() Да, это всего две строки))) Страшная мешанина! Почему это плохо? 🔸 Это круто когда "заработало" но не круто когда через время смотришь на код и не поймешь что он делает. 🔸 Когда через время попробуешь что-то поправить, становится проще переписать заново (мой случай 😉) 🔸 Если работаете в команде, можно получить знатных [подставить страшное слово]лей. Такой код просто невозможно поддерживать. Можно получить знатной брани на свою голову от себя же, пока не вспомнишь что это ты и писал) 🔸Если вы решились немного сгладить вину и расставить комментарии, то в однострочном выражении это не всегда получится. Символ игнора перехода на новую строку не позволит вам за ним писать комментарий. Оба выражения ниже вызовут ошибку синтаксиса x = 1 + \ # comment 2 x = 1 + \ # comment 2 Делайте код проще, избегайте сокращений, которые снижают читабельность кода. ⚠️Простое лучше сложного⚠️ А тем, кто еще не понял: import this
Објавено 13 апр.
В предыдущем посте мы запускали нужную нам функцию внутри генератора списка. Выглядит странно, когда мы не сохраняем результат этого генератора, так как обычно нужен именно он. Обычно, в таких случаях более наглядно смотрится функция map(): array = [1, 2, 3, 4, 5, 6, 7, 8, 9] more, less = [], [] map(lambda x: [less.append, more.append][x>5](x), array) Проверяем результат >>> print(more, less) [] [] Хм, не сработало! Что произошло? Почему итерация не запустилась? Дело в том, что в Python3 многие итеративные функции (если не все) стали генераторами. А что такое генератор? Это не тот генератор списка который List Comprehensions, а именно Generator. Объект, который внутри себя имеет алгоритм получения следующего элемента и он не станет запускать итерацию пока мы не начнём запрашивать эти элементы. Что возвращает функция map()? >>> print(map(...)) <map object at 0x0000023114ADE3C8> Тоже самое произошло и с функцией range >>> print(range(10)) range(0, 10) Значит всё что остаётся, это запустить итерацию по элементам генератора. Как это сделать максимально коротко? Можно просто в цикле for for _ in map(...): pass Либо конвертнуть в список list(map(...)) Или оператором * [*map(...)] Ну а полностью будет выглядеть так [*map(lambda x: [less.append, more.append][x>5](x), array)] Теперь генератор сразу вычисляется. Хотя, как по мне, такая запись менее "читабельна") #tricks
Hashtags
Објавено 10 апр.
Как разделить один список на два по определённому признаку элементов? Самый очевидный способ выглядит так: array = [1, 2, 3, 4, 5, 6, 7, 8, 9] even, odd = [], [] for item in array: if item%2: odd.append(item) else: even.append(item) Смотрим что получается >>> odd [1, 3, 5, 7, 9] >>> even [2, 4, 6, 8] А теперь способ в одну строку! (ну кто бы сомневался😂) _ = [[even.append, odd.append][x%2](x) for x in array] Я специально сохранил результат в переменную "_", так как этот результат нам не нужен. Вся магия происходит во время выполнения генератора списка. Выражение x%2 возвращает нам 1 или 0, что является индексом списка [even.append, odd.append]. Таким образом происходит выбор метода append() для нужного списка и сразу после этого мы вызываем его, отправляя очередной элемент в выбранный список. Точно так же в выражение можно ставить сравнение, которое вернёт bool. Ведь вы помните, что bool это производный тип от int, а значит True это 1 а False это 0. >>> arr = [100, 200] >>> print(arr[True]) 200 >>> print(arr[False]) 100 В нашем случае можно сделать так more, less = [], [] [[less.append, more.append][x>5](x) for x in array] Можно даже никуда не сохранять результат, всё равно то что нам нужно произойдет. >>> print(more, less) [6, 7, 8, 9], [1, 2, 3, 4, 5] #tricks
Hashtags
Објавено 8 апр.
Небольшой трик с регулярными выражениями который редко вижу в чужом коде. Допустим, вам нужно распарсить простой текст и вытащить оттуда пары имя+телефон. Вернуть всё это надо в виде списка словарей. Возьмем очень простой пример текста. >>> text = ''' >>> Alex:8999123456 >>> Mike:+799987654 >>> Oleg:+344456789 >>> ''' Соответственно, для выделения нужных элементов будем использовать группы. Получится такой паттерн: (\w+):([\d+]+) Как мы будем формировать словарь из найденных групп? >>> import re >>> results = [] >>> for match in re.finditer(r"(\w+):([\d+]+)", text): >>> results.append({ >>> "name": match.group(1), >>> "phone": match.group(2) >>> }) >>> print(results) [{'name': 'Alex', 'phone': '8999123456'}, ...] Можно немного сократить запись используя zip >>> results = [] >>> for match in re.finditer(r"(\w+):([\d+]+)", text): >>> results.append(dict(zip(['name', 'phone'], match.groups()))) Но есть способ лучше! Это именованные группы в regex. Можно в паттерне указать имя группы и результат сразу забрать в виде словаря. >>> for match in re.finditer(r"(?P<name>\w+):(?P<phone>[\d+]+)", text): >>> results.append(match.groupdict()) То есть всё что я сделал, это добавил в начале группы (внутри сбокочек) такую запись: (?P<group-name>...) Теперь найденная группа имеет имя и можно обратиться к ней как к элементу списка >>> name = match['name'] Либо забрать сразу весь словарь методом groupdict() >>> match.groupdict() #tricks#regex