Вторая по частоте future-функция, которую я использовал, это абсолютный импорт
Изменения, которые вносит эта инъекция описаны в PEP328
Покажу простой пример.
Допустим, есть такой пакет:
1. модуль в моём пакете my_package.string
2. стандартный модуль string
И вот тут вступает в дело приоритет импортов. В Python2 порядок следующий: помимо иных источников, раньше ищется модуль внутри текущего пакета, а потом в стандартных библиотеках. Таким образом мы импортнём my_package.string.
Но в Python3 это поведение изменилось. Если мы указываем просто имя пакета, то ищется именно такой модуль, игнорируя имена в текущем пакете. Если мы хотим импортнуть именно подмодуль из нашего пакета то, мы должны теперь явно это указывать.
Подробней про импорты здесь:
https://docs.python.org/3/tutorial/modules.html
#2to3 #pep #basic
from __future__ import absolute_import
Что она делает? Изменения, которые вносит эта инъекция описаны в PEP328
Покажу простой пример.
Допустим, есть такой пакет:
/my_package
/__init__.py
/main.py
/string.py
Смотрим код в my_package/main.py# main.py
import string
Простой пример готов) Вопрос в том, какой модуль импортируется в данном случае? Есть два варианта:1. модуль в моём пакете my_package.string
2. стандартный модуль string
И вот тут вступает в дело приоритет импортов. В Python2 порядок следующий: помимо иных источников, раньше ищется модуль внутри текущего пакета, а потом в стандартных библиотеках. Таким образом мы импортнём my_package.string.
Но в Python3 это поведение изменилось. Если мы указываем просто имя пакета, то ищется именно такой модуль, игнорируя имена в текущем пакете. Если мы хотим импортнуть именно подмодуль из нашего пакета то, мы должны теперь явно это указывать.
from my_package import string
или относительный импорт, но с указанием пути относительно текущего модуля mainfrom . import string
Еще одной неоднозначностью меньше 😎Подробней про импорты здесь:
https://docs.python.org/3/tutorial/modules.html
#2to3 #pep #basic
Python Enhancement Proposals (PEPs)
PEP 328 – Imports: Multi-Line and Absolute/Relative | peps.python.org
The import statement has two problems:
Ранее я уже упоминал о другой фишке из ˍˍfutureˍˍ , это оператор деления.
Например:
Если нам требуется получить дробное значение при делении двух int то приходилось форсированно один из операндов конверировать в float.
То есть теперь деление int на int даёт float если результат не целое число.
В классах теперь доступны методы __floordiv__() и __truediv__() для определения поведения с этими операторами.
Данный переход описан в PEP238.
#pep #2to3 #basic
from __future__ import division
Суть проста. Раньше сложность типа данных результата поределялась типом самого сложного операнда.Например:
int/int => int
int/float => float
В первом случае оба операнда int, значит и результат будет int. Во втором float более сложный тип, поэтому результат будет float. Если нам требуется получить дробное значение при делении двух int то приходилось форсированно один из операндов конверировать в float.
12/float(5) => float
Но с новой "философией" это не требуется. В Python3 "floor division" заменили на "true division" а старый способ теперь работает через оператор "//". >>> 3/2
1.5
>>> 3//2
1То есть теперь деление int на int даёт float если результат не целое число.
В классах теперь доступны методы __floordiv__() и __truediv__() для определения поведения с этими операторами.
Данный переход описан в PEP238.
#pep #2to3 #basic
Telegram
Python Заметки
Если вы еще не начали переходить на Python3 то сейчас самое время!
Различий очень много. Как внутренние архитектурные решения, невидимые рядовому программисту, так и явные изменения, которые приходится использовать каждый день.
Для тех, кто только начинает…
Различий очень много. Как внутренние архитектурные решения, невидимые рядовому программисту, так и явные изменения, которые приходится использовать каждый день.
Для тех, кто только начинает…
Когда разрабатываете свой GUI с помощью PyQt для какого-либо софта бывает необходимо позаимствовать цвета из текущего стиля интерфейса. Например, чтобы правильно раскрасить свои виджеты, подогнав их по цвету. Ведь бывает, что ваш GUI используется в разных софтах. Причём некоторые со светлой темой а другие с тёмной.
По умолчанию стили наследуются, но если вы задаёте какую-либо раскраску для части виджета через свой styleSheet, то требуется ссылаться на цвета текущего стиля.
Как это сделать? Как получить нужный цвет из палитры имеющегося стиля? Это достаточно просто, нужно использовать класс QPalette и его роли.
Например, мне нужно достать цвет текста из одного виджета и применить его в другом как цвет фона (не важно зачем именно так, просто захотелось😊).
Получаем палитру виджета и сразу достаём нужный цвет, указав его роль.
На самом деле есть запись покороче, в одну строку и без лишних переменных. Не очень-то по правилам CSS, но Qt это понимает.
Зато он прекрасно сработает в файле .qss, то есть не придётся в коде прописывать раскраску отдельных элементов через ссылки на палитру, всё красиво сохранится в отдельном файле .qss!
#qt #tricks
По умолчанию стили наследуются, но если вы задаёте какую-либо раскраску для части виджета через свой styleSheet, то требуется ссылаться на цвета текущего стиля.
Как это сделать? Как получить нужный цвет из палитры имеющегося стиля? Это достаточно просто, нужно использовать класс QPalette и его роли.
Например, мне нужно достать цвет текста из одного виджета и применить его в другом как цвет фона (не важно зачем именно так, просто захотелось😊).
Получаем палитру виджета и сразу достаём нужный цвет, указав его роль.
from PySide2.QtGui import QPaletteтеперь можем использовать этот цвет в стилях
color = main_window.palette().color(QPalette.Text)
my_widget.setStyleSheet(f'background-color: {color.name()};')
Готово, мы динамически переопределили дефолтный стиль используя текущий стиль окна! На самом деле есть запись покороче, в одну строку и без лишних переменных. Не очень-то по правилам CSS, но Qt это понимает.
my_widget.setStyleSheet('background-color: palette(Text);')
Этот способ не подходит если вам нужно как-то модифицировать цвет перед применением в своих стилях. В этом случае потребуется первый способ. Зато он прекрасно сработает в файле .qss, то есть не придётся в коде прописывать раскраску отдельных элементов через ссылки на палитру, всё красиво сохранится в отдельном файле .qss!
QListView#my_widget::item:selected {
background: palette(Midlight);
}
Про имеющиеся роли можно почитать здесь 🌍#qt #tricks
Думаете ˍˍfutureˍˍ нужен только для Python2? Нет, это процесс постоянный. В Python3 тоже есть свои "future".
Что делает этот future?
Если вы активно используете аннотации типов в Python3 то знаете, как они обычно выглядят.
В более сложных случаях это будут инстансы объектов из модуля typing
Дело в том, что все эти инстансы типов создаются в момент определения функции когда модуль импортируется. Так же как и значения по умолчанию они должны исполниться и вернуть какой-то объект. В нашем случае это ссылка на тип int или инстанс класса typing.List.
И тут возникает две проблемы, описаные в PEP.
Если коротко то звучит это так:
1. Если в подсказке указывается тип, который еще не определён, это вызовет ошибку. Приходится описывать его просто строкой или менять порядок определения.
2. Определение ссылок на типы порой занимает много времени, так как требуется импорт модулей и создание инстансов.
Но если вы импортируете наш future, то активируется так называемое отложенное определение ссылок (Postponed Evaluation of Annotations).
В результате вместо создания инстансов и ссылок в ˍˍannotationsˍˍ просто записывается строка с этой подсказкой.
Для преобразования этих строк в привычный вид есть готовая функция
#pep
from __future__ import annotations
Похоже на бекпорт аннотаций типов из Python3 в Python2, но это не так. Это имплементация PEP 563 Postponed Evaluation of Annotations которая будет дефолтной только в Python4.Что делает этот future?
Если вы активно используете аннотации типов в Python3 то знаете, как они обычно выглядят.
def func(x: int) -> int:
return x * 2
При этом у объекта функции появляется атрибут ˍˍannotationsˍˍ>>> print(func.__annotations__)
{'x': <class 'int'>, 'return': <class 'int'>}
Видно, что в этой переменной находится словарь. Ключ словаря это имя аргумента функции, а значение это непосредственно ссылка на тип. А так же есть тип для return.В более сложных случаях это будут инстансы объектов из модуля typing
from typing import List
def func(x: int) -> List[int]:
return [x] * 10
>>> print(func.__annotations__)
{'x': <class 'int'>, 'return': typing.List[int]}
Что происходит если мы импортим annotations из ˍˍfutureˍˍ? Изменяется способ определения аннотаций. Дело в том, что все эти инстансы типов создаются в момент определения функции когда модуль импортируется. Так же как и значения по умолчанию они должны исполниться и вернуть какой-то объект. В нашем случае это ссылка на тип int или инстанс класса typing.List.
И тут возникает две проблемы, описаные в PEP.
Если коротко то звучит это так:
1. Если в подсказке указывается тип, который еще не определён, это вызовет ошибку. Приходится описывать его просто строкой или менять порядок определения.
2. Определение ссылок на типы порой занимает много времени, так как требуется импорт модулей и создание инстансов.
Но если вы импортируете наш future, то активируется так называемое отложенное определение ссылок (Postponed Evaluation of Annotations).
В результате вместо создания инстансов и ссылок в ˍˍannotationsˍˍ просто записывается строка с этой подсказкой.
from __future__ import annotations
def func(x: int) -> int:
return x * 2
>>> print(func.__annotations__)
{'x': 'int', 'return': 'int'}
В дальнейшем ваши IDE и ресолверы типов могут брать эти строки и исполнять самостоятельно где то на фоне, когда все необходимые типы уже определены.Для преобразования этих строк в привычный вид есть готовая функция
>>> import typing
>>> typing.get_type_hints(func)
{'x': <class 'int'>, 'return': <class 'int'>}
Но делать это можно уже только по необходимости.#pep
Python Enhancement Proposals (PEPs)
PEP 563 – Postponed Evaluation of Annotations | peps.python.org
PEP 3107 introduced syntax for function annotations, but the semantics were deliberately left undefined. PEP 484 introduced a standard meaning to annotations: type hints. PEP 526 defined variable annotations, explicitly tying them with the type hintin...
Небольшой трик с регулярными выражениями который редко вижу в чужом коде.
Допустим, вам нужно распарсить простой текст и вытащить оттуда пары имя+телефон. Вернуть всё это надо в виде списка словарей. Возьмем очень простой пример текста.
Допустим, вам нужно распарсить простой текст и вытащить оттуда пары имя+телефон. Вернуть всё это надо в виде списка словарей. Возьмем очень простой пример текста.
>>> 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Как разделить один список на два по определённому признаку элементов? Самый очевидный способ выглядит так:
(ну кто бы сомневался 😂)
Выражение
Точно так же в выражение можно ставить сравнение, которое вернёт bool. Ведь вы помните, что bool это производный тип от int, а значит True это 1 а False это 0.
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)#tricks
[6, 7, 8, 9], [1, 2, 3, 4, 5]
Telegram
Python Заметки
Перед вами простой словарик:
data = {1: 'value1', True: 'value2'}
С первого взгляда всё нормально. Давайте смотреть что у нас теперь есть в словаре
>>> data[1]
value2
>>> data[True]
value2
Кажется мы сломали питон) Но на самом деле нет. Это ошибка разработчика…
data = {1: 'value1', True: 'value2'}
С первого взгляда всё нормально. Давайте смотреть что у нас теперь есть в словаре
>>> data[1]
value2
>>> data[True]
value2
Кажется мы сломали питон) Но на самом деле нет. Это ошибка разработчика…
В предыдущем посте мы запускали нужную нам функцию внутри генератора списка. Выглядит странно, когда мы не сохраняем результат этого генератора, так как обычно нужен именно он.
Обычно, в таких случаях более наглядно смотрится функция map():
Дело в том, что в Python3 многие итеративные функции (если не все) стали генераторами. А что такое генератор? Это не тот генератор списка который List Comprehensions, а именно Generator.
Объект, который внутри себя имеет алгоритм получения следующего элемента и он не станет запускать итерацию пока мы не начнём запрашивать эти элементы.
Что возвращает функция map()?
Можно просто в цикле for
#tricks
Обычно, в таких случаях более наглядно смотрится функция 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(...))Тоже самое произошло и с функцией range
<map object at 0x0000023114ADE3C8>
>>> 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
В моих триках вы часто можете видеть способы сокращения кода. Когда длинный код заменяется на однострочный. Скорее всего, я продолжу эту практику, но хочу напомнить о важной вещи!
⚠️ Понятное лучше чем запутанное ⚠️
Да, иногда эти трики помогают сделать код чище, когда они применяются в виде "синтаксического сахара".
Но если не следить за собой то эта привычка может создавать совершенно безумные вещи!
Если это можно сделать в Python, то это не значит что это нужно сделать.
Смотрим реальный пример из моей практики, когда я увлёкся и загнал все сравнения в один оператор if
Почему это плохо?
🔸 Это круто когда "заработало" но не круто когда через время смотришь на код и не поймешь что он делает.
🔸 Когда через время попробуешь что-то поправить, становится проще переписать заново (мой случай 😉)
🔸 Если работаете в команде, можно получить знатных [подставить страшное слово]лей. Такой код просто невозможно поддерживать.
Можно получить знатной брани на свою голову от себя же, пока не вспомнишь что это ты и писал)
🔸Если вы решились немного сгладить вину и расставить комментарии, то в однострочном выражении это не всегда получится. Символ игнора перехода на новую строку не позволит вам за ним писать комментарий.
Оба выражения ниже вызовут ошибку синтаксиса
⚠️ Простое лучше сложного ⚠️
А тем, кто еще не понял:
⚠️ Понятное лучше чем запутанное ⚠️
Да, иногда эти трики помогают сделать код чище, когда они применяются в виде "синтаксического сахара".
Но если не следить за собой то эта привычка может создавать совершенно безумные вещи!
Если это можно сделать в 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Помните пример с неудачной вставкой комментария?
Кстати, они же помогают избежать символа "\" в других случаях
Без скобок
Да и лишние скобки в Python смотрятся не всегда уместно)
#tricks
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
Стандартный модуль 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