ch
Feedback
Python Заметки

Python Заметки

前往频道在 Telegram

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

显示更多
2 373
订阅者
无数据24 小时
-17
-1130
帖子存档
Начиная с версии Python 3.7 появился встроенный профайлер времени импорта модуля. Чтобы его активировать достаточно к запуску интерпретатора добавить аргумент -X importtime python3 -X importtime Теперь каждый загруженный модуль будет показывать время своей загрузки, а так же время загрузки вложенных модулей. Если нет возможности добавить аргумент в команду, то используйте переменную PYTHONPROFILEIMPORTTIME, результат аналогичен Linux: export PYTHONPROFILEIMPORTTIME=1 Windows set PYTHONPROFILEIMPORTTIME=1 #tricks

Обычная практика удаления одинаковых значений из списка с помощью множества array = [1, 2, 3, 4, 5, 4, 3] uniq = list(set(array)) Альтернативная запись с помощью литералов вместо функций
uniq = [*{*array}]

#tricks

Всё начиналось с библиотеки six, что означает цифру 6 и является результатом умножения 2*3 (напомню что six это библиотека для написания кода одновременно совместимого для Python 2 и 3). Но как обычно всегда найдется тот, кому не всё понравится и он напишет свой вариант) В итоге получаем небольшой ряд "числовых" библиотек примерно для одного и того же https://pypi.org/project/six/ https://pypi.org/project/eight/ https://pypi.org/project/nine/ Выглядит забавно. Я решил проверить, есть ли другие библиотеки с числом в названии, хотя бы до 20. И вот что нашлось: https://pypi.org/project/one/ https://pypi.org/project/two/ https://pypi.org/project/three/ four - свободно https://pypi.org/project/five/ https://pypi.org/project/six/ https://pypi.org/project/seven/ https://pypi.org/project/eight/ https://pypi.org/project/nine/ ten - свободно https://pypi.org/project/eleven/ https://pypi.org/project/twelve/ thirteen - свободно fourteen - свободно fifteen - свободно https://pypi.org/project/sixteen/ seventeen - свободно nineteen - свободно twenty - свободно Назначения у этих проектов, конечно, разные. Есть и заброшенные и популярные. Но места еще есть 😊 Занимаем пока свободно! PS. Всех уделал Em Fresh со своей линейкой Python-альбомов😁 (жмакнуть show more) PPS. Всех читательниц моего канала поздравляю с праздником 🌼 🥳 💐 #offtop #libs #2to3

Лично я на практике встречал использование побитовых операторов в двух ситуациях (их конечно намного больше). 🔸1. Сдвиг, который соответствует некоторой математической операции (арифметический сдвиг) но работает несравнимо быстрей. Например сдвиг влево равен выражению a*2**b: a<<b == a*2**b А также определение знака числа или нахождение модуля без условного оператора, что также очень быстро делается. И другие операции. 🔸2. Числовые маски. Что это такое? Создаем несколько переменных, в которых в бинарном представлении все ячейки заполнены нулями кроме одной позиции. И у каждой переменной используется своя уникальная позиция для бита 1. FLAG1 = int('001', 2) # 2 FLAG2 = int('010', 2) # 4 FLAG3 = int('100', 2) # 8 Теперь с помощью оператора OR можем объединять все биты в одну маску >>> flags = FLAG1 | FLAG2 3 # 011 А после проверить, входит ли определённый флаг в состав битов маски? >>> flags & FLAG3 0 # 000 нет совпадений >>> flags & FLAG2 2 # 010 совпал второй бит Если результат больше 0 то флаг присутствует в маске. Если результат 0 то такого флага нет. Чтобы получить тип bool можем писать так bool(flags & FLAG2) или так flags & FLAG2 > 0 и, очевидно, так if flags & FLAG1: ... Где могут пригодиться такие маски? Один пример был в предыдущем посте про флаги в Qt фреймворке. Также такой способ часто используют в организации прав доступа к ресурсам. READ = int('001', 2) # 2 WRITE = int('010', 2) # 4 DELETE = int('100', 2) # 8 USER = READ MODERATOR = READ | WRITE ADMIN = READ | WRITE | DELETE can_write = ADMIN & WRITE Не сложно представить альтернативу на простом Python READ = 1 WRITE = 2 DELETE = 3 USER = [READ] MODERATOR = [READ, WRITE] ADMIN = [READ, WRITE, DELETE] can_write = WRITE in ADMIN Оператор in работает довольно шустро, но всё равно медленней чем побитовый оператор. #tricks

Давайте разберёмся как работают побитовые операторы. Всего есть 6 основных операторов: | OR & AND ^ XOR (исключающее OR) ~ NOT (унарная операция) >> сдвиг вправо << сдвиг влево Эти операторы работают с числами в двоичном представлении. Условно говоря, они ставят числа в двоичном виде друг над другом и по очереди обрабатывают каждый столбик с битами. Например, берём два числа, и сразу смотрим как оно выглядит в двоичном виде (Python отбрасывает ведущие нули, так что рядом допишу более удобную форму) >>> a = 3 >>> bin(a) '0b11' # 011 >>> b = 6 >>> bin(b) '0b110' # 110 🔸 Оператор OR в результат пишет 1 если в одном из элементов есть 1 >>> 3|6 7 в двоичном виде это выглядит так (запишем столбиком) 011 |110 =111 В каждом столбце был найден 1, поэтому в результате все биты равны 1 🔸 Оператор AND В результат ставит 1 только если оба бита равны 1 >>> 3&6 2 Бинарный вид 011 &110 =010 Только на 2й позиции оба бита равны 1. 🔸 Оператор XOR Пишет 1 на бит результата, для которого только один из соответствующих битов операндов равен 1. >>> 3^6 5 011 &110 =101 🔸 Оператор NOT Заменяет каждый бит на противоположный. Эта операция унарная, то есть поддерживает только один операнд. >>> ~3 -4 ~011 =100 Здесь всё понятно. Но давайте попробуем другое число: ~50 =-51 ~110010 =-110011 Вот тут не очень понятно что произошло) Это связано со способом представления отрицательных чисел в двоичном виде. Ведь мы не можем в память записать отрицательные биты. Для этого используется ведущий 0 или 1. Но это тема не поместится в пост, советую поискать информацию в интернете самостоятельно). Если кратко и из документации, то: Побитовая операция НЕ для числа x соответствует -(x+1) 🔸 Сдвиг Здесь всё просто. Все биты сдвигаются на указанное количество шагов подставляя нули >>> 3 << 1 6 011 << 1 110 Кстати, преобразовать бинарное представление обратно в число можно с помощью функции int() указав разрядность системы исчисления. >>> int('11001', 2) 25 __________________ А зачем нам вся эта информация? Узнаем в следующем посте... #triсks

В фреймворке PyQt (и PySide тоже) часто встречается настройка чего-либо с помощью так называемых флагов. widget.setWindowFlags(Qt.Window) Взаимодействие нескольких флагов делается с помощью бинарных (или побитовых) операторов. Несколько флагов можно указать с помощью оператора "|" list_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) исключить флаг из уже имеющегося набора можно так list_item.setFlags(list_item.flags() ^ Qt.ItemIsEnabled) Добавить новый флаг к имеющимся можно так list_item.setFlags(list_item.flags() | Qt.ItemIsEnabled) А проверка наличия делается так is_enabled = item.flags() & Qt.ItemIsEnabled > 0 Почему именно так? Всё дело в том как именно работают побитовые операторы. Но об этом в следующем посте. #qt

Те кто работал с Python2 наверняка помнят, как приходилось поправлять расширение python-файла из переменной ˍˍfileˍˍ чтобы получить именно .py а не .pyc
source_path = os.path.splitext(__file__)[0] + '.py'

В Python3 эта проблема ушла. Всегда возвращается путь именно к исходному файлу .py. Ну сразу бы так) #2to3

Часто требуется красиво распечатать ряд переменных через запятую. Возможно это требуется для дебага, а может является частью CLI. Обычно решается через метод строки join() >>>
args = ['val1', 'val2', 'val3']
>>>
print(", ".join(args))
val1, val2, val3

Если не все аргументы это строк то нужно их еще дополнительно преобразовать в строки. >>> args = [1, 2, 3, 4] >>>
print(", ".join([str(x) for x in args]))
или >>>
print(", ".join(map(str, args)))
1, 2, 3, 4 Но самый простой способ это обычная функция print() (Python3) >>> args = [1, 2, 3, 4] print(*args, sep=", ") 1, 2, 3, 4 К сожалению такой способ не позволит легко сохранить получившуюся строку в переменную чтобы, например, использовать в логинге. Это возможно, но избыточно. #tricks

Как удалить из списка повторяющиеся элементы, сохранив порядок?
array = ['item1', 'item2', 'item3', 'item3', 'item1', 'item3', 'item2', 'item4']

Обычно используют преобразование в множество и обратно
unq = list(set(array))

Но такой способ ломает порядок элементов. Правильный алгоритм выглядит так: 🔸Способ 0
unq = []
for item in array:
    if item not in unq:
        unq.append(item)

Теперь посмотрим как это записать короче 🔸 Способ 1 Создаем пустой список и в простом генераторе сначала проверяем а потом добавляем элемент если его еще нет в списке.
unq = []
[unq.append(item) for item in array if item not in unq]

🔸 Способ 2 Аналогичный, но с помощью set().
_set = set()
unq = [x for x in array if x not in _set and not _set.add(x)]
Здесь вторая проверка это хитрый "костыль". Функция на самом деле ничего не возвращает, просто нам надо её вызвать сразу после первой проверки, если она вернула True. Ответ функции add() инвертируем с помощью not чтобы оба условия сработали. 🔸 Способ 3 В одну строку как обычно с помощью set, но с последующей сортировкой для восстановления порядка.
unq = sorted(list(set(array)), key=array.index)

🔸 Способ 4 Здесь используем тот факт, что в словаре два одинаковых ключа быть не может и что ключи словаря теперь упорядочены (Python3+). Преобразуем элементы в ключи словаря и обратно в список.
unq = list(dict.fromkeys(array))
____________________ Способы 2-4 НЕ подходят, если элементы списка нехешируемые. То есть они не могут быть в качестве ключа словаря или элемента множества. Например, если у вас список словарей. В этом случае подходит только Способ 1. #tricks

Перед вами простой словарик:
data = {1: 'value1', True: 'value2'}

С первого взгляда всё нормально. Давайте смотреть что у нас теперь есть в словаре
>>> data[1]
value2
>>> data[True]
value2

Кажется мы сломали питон) Но на самом деле нет. Это ошибка разработчика а не Python. Дело в том, что на уровне данных для Python нет разницы между 1 и True. Сам тип bool это производный клас от int
>>> issubclass(bool, int)
True

Значение True это частный случай int, равный 1. Поэтому у них одинаковый хеш, и словарь их воспринимает как один и тот же ключ
>>> hash(1)
1
>>> hash(True)
1

Так что же у нас сейчас в словаре?
>>> data 
{1: 'value2'}

Ключи добавляются в порядке их следования. И если такой ключ уже существует, то вместо создания нового ключа просто обновляется его значение. Поэтому у нас всего один ключ и это 1 а не True. Чтобы избежать такой путаницы, возмите себе за правило ключи всегда делать одного типа. C "0" и False всё аналогично. #tricks

Визуальный пример к посту про type hint

Работаете в PyCharm? Тогда этот пост для вас! В Python3 добавлен синтаксис аннотаций. То есть, в объявлении функции можно указать какие типы данных у нас тут крутятся. От простых, до сложносоставных. Например, есть такая функция:
def my_func(x: int, y: float) -> float:
    val = x * y
    return val

В этой функции явно указаны типы, а значит, что IDE сможет адекватно анализировать ваш код и использовать всевозможные вспомогательные штуки. Автокомплит, или различные предупреждения о неверном использовании типа. Между тем, в Python2 также можно делать аннотации так, чтобы PyCharm их понял. Записываются они иначе, с помощью комментариев (type hint). Вот та же функция для Python2:
def my_func(x, y): # type: (int, float) -> float
    val = x * y
    return val

Интерпретатору не мешает, а для IDE подсказки😊 Но знаете ли вы, что такой способ можно использовать и не только для аннотирования функции? Можно указать тип любой переменной в любой строке! Например, у вас есть внешний API в котором типы не объявлены вообще никак. А хочется иметь автокомплиты для возвращаемых значений. Вот пример:
import some_api

def my_func() -> str:
    value = some_api.get_value()
    # ... life without autocomplete is pain(((
    return value

Для переменной value IDE не сможет сообразить автокомплиты или проверку типов. Но с помощью такого же type hint мы можем ему помочь! Даже подсветка будет работать)
...
value = some_api.get_value()  # type: str
# autocomplete for str here!!!
...

После этого у переменной value появится автокомплит и всё остальное. Теперь вы можете обозначать типы переменных хоть на каждой строке 😎 Запоминаем формат:
[code]  # type: [Type]
#tricks

Стандартная функция enumerate() очень удобна для получения индекса итерации
>>> mylist = ['one', 'two', 'three']
>>> for i, item in enumerate(mylist):
>>>     print(i, item)
0 one
1 two
2 three

Вот бы со словарями так! Чтобы удобно получить индекс, ключ и значение! Это можно сделать так:
>>> mydict = {10: 'item1', 20: 'item2', 30: 'item3'}
>>> for i, key in enumerate(mydict):
>>>     value = mydict[key]
>>>     print(i, key, value)
0 10 item1
1 20 item2
2 30 item3

Третья строка явно лишняя, приходится дополнительно доставать значение по ключу. Как нам сократить код? Хочется распаковать ключ и значение сразу. Например так:
>>> for i, key, value in enumerate(mydict.items()):
>>>     print(i, key, value)

И получаем ошибку
ValueError: not enough values to unpack (expected 3, got 2)
Справедливо, ведь enumerate() возвращает всегда кортеж из 2х элементов, а мы распаковывем его в 3 переменные. Но есть одна хитрость, которая позволит сделать то что мы задумали! Скобочки!
>>> for i, (key, value) in enumerate(mydict.items()):
>>>     print(i, key, value)
0 10 item1
1 20 item2
2 30 item3

Теперь норм 😎 #tricks

Чтобы запустить два контекст менеджера одновременно можно написать так: with open('script.py') as inp: with open('_bkp.py', 'w') as out: out.write(inp.read()) А можно и более компактно в одну команду: with open('script.py') as inp, open('_bkp.py', 'w') as out: out.write(inp.read()) #tricks

Как соединить два списка? Список поддерживает оператор "+", так что это легко: >>> l1 = [1, 2, 3] >>> l2 = [4, 5, 6] >>> l3 = l1 + l2 >>> print(l3) [1, 2, 3, 4, 5, 6] А как тоже самое повторить со словарями? >>> d1 = dict(k1=1, k2=2) >>> d2 = dict(k3=3, k4=4) >>> d3 = d1+d2 TypeError: unsupported operand type(s) for +: 'dict' and 'dict' Да, словари такой оператор не поддерживают. Самое распространённое решение это метод словаря update() d1.update(d2) Но это способ изменяет один из словарей, а нам нужно оставить оба словаря нетронутыми и создать третий. Это можно сделать таким способом: d3 = dict( d1.items() | d2.items() ) Или еще короче d3 = {**d1, **d2} Если количество словарей неизвестно и все они в одном списке, то вариантов еще больше. Допустим, есть такой список со словорями: dict_array = [ {'k1': 1, 'k2': 2}, {'k3': 3, 'k4': 4}, {'k5': 5, 'k6': 6}, ] Для объединения всех в один мегасловарь делаем так: >>> from operator import or_ >>> from functools import reduce >>> d3 = dict(reduce(or_, [x.items() for x in dict_array])) {'k3': 3, 'k6': 6, 'k1': 1, 'k4': 4, 'k5': 5, 'k2': 2} Если важна последовательность ключей, то еще есть способ >>> from itertools import chain >>> d3 = dict(chain.from_iterable(d.items() for d in dict_array)) {'k1': 1, 'k2': 2, 'k3': 3, 'k4': 4, 'k5': 5, 'k6': 6} Или так d3 = dict(chain(*map(dict.items, array))) Есть вариант и покороче >>> from collections import ChainMap >>> d3 = dict(ChainMap(*array)) >>> print(d3) {'k1': 1, 'k5': 5, 'k6': 6, 'k3': 3, 'k2': 2, 'k4': 4} И даже еще короче, в одну строку и с сохранением порядка: >>> d3 = dict(j for i in array for j in i.items()) >>> print(d3) {'k': 1, 'k2': 2, 'k3': 3, 'k4': 4, 'k5': 5, 'k6': 6} Можно ещё несколько вариантов придумать, но, думаю, достаточно 😁 __________ Все примеры создают новый словарь, не изменяя старые. Но если в исходных словарях есть другие словари или списки то для независимой копии нужно пройтись еще функцией copy.deepcopy() 😉 #tricks

Простая задача: получить рандомную строку. Такое может потребоваться для создания секретного ключа, токена, одноразовой ссылки и тд. Как бы вы решали такую задачу? Допустим, требуется строка в 20 символов. Чаще всего решают примерно так: from string import ascii_letters, digits from random import choice token = ''.join(random.choice(string.ascii_letters+string.digits) for _ in range(20)) Редко, но встречается и такой способ import uuid token = str(uuid.uuid4())[:20] Но самый верный способ это готовый модуль secrets (Python3.6+) из стандартной библиотеки. import secrets token = secrets.token_urlsafe(15) или token = secrets.token_hex(10) Почему лучше? - Короче запись - Более читаемый и понятный код - Быстрей работает ⏱ Время на 1М запусков: - random+string : ~14.3sec - uuid : ~5.5sec - модуль secrets : ~1.2sec ________ Прошу не путать получение рандомной строки и получение контрольной суммы. Это разные по назначению задачи. #tricks

😉 Трик про flatten-список. Задача: из списка списков сделать одноуровневый список >>> arr = [[1, 2], [3, 4], [5, 6]] Допустим, есть список интов arr = [1, 2, 3, 4, 5] Как получить их сумму? Очень просто! >>> sum(arr) 15 И тут вы подумаете: Вау, какой удобный метод. Он просто берет список объектов и склеивает их через "+". Удобно же! Такс, если list, как тип, поддерживает оператор сложения, то я же могу тогда сделать такой финт: >>> arr = [[1, 2], [3, 4], [5, 6]] >>> sum(arr) Traceback (most recent call last): File "<input>", line 2, in <module> TypeError: unsupported operand type(s) for +: 'int' and 'list' О нет! Счастье было так близко! 😭😢😩 Стоп, давайте разбираться. Если функция sum() просто прибавляет очередной аргумент списка к предыдущему, то с чем складывается самый первый элемент? Должно быть какое-то стартовое значение. И оно есть, это ноль "0". Потому-то мы и видим такую ошибку. На наше счастье мы можем указать стартовое значение вместо ноля, чтобы получить сумму, используя в качестве начала другое число: >>> arr = [1, 2, 3, 4, 5] >>> sum(arr, 5) 20 И, следуя этой логике..... >>> arr = [[1, 2], [3, 4], [5, 6]] >>> sum(arr, []) [1, 2, 3, 4, 5, 6] YESSSS!!!! 😎🥰🤟 Вы, скорее всего, ломанётесь проверять другие типы и будете правы. С другими тоже работает. Но Python не упустит случай вас потроллить))) >>> words = ['Hello ', 'world', '!'] >>> sum(words, '') TypeError: sum() can't sum strings [use ''.join(seq) instead] Не используйте "+" для склейки строк! Скорее всего именно эта ошибка сделана на случай если вы захотите склеить большой текстовый документ, прочитанный через readlines(). _______ Это далеко не единственный способ сделать flatten-список. Пост скорей про функцию sum. #tricks

⭐️ Между прочим... Читаете мой канал на телефоне? Код в примерах бывает растягивается в достаточно длинные строки. На мобиле смотреть не очень удобно. Особенно, учитывая, что переносы в Python это тоже синтаксис. Но если повернуть телефон горизонтально, то всё становится куда приятней! Казалось бы, это же очевидно! Но я сам не сразу догадался 😁

Скорее всего уже слышали, что складывать строки через + это плохая практика. Падение производительности, и всё такое. Без лишних слов, давайте измерять: from timeit import timeit def t1(): # складываем 10 строк через + из переменной t = 'text' for _ in range(1000): s = t + t + t + t + t + t + t + t + t def t2(): # склеиваем список строк через метод join arr = ['text'] * 10 for _ in range(1000): s = ''.join(arr) def t3(): # складываем через + но не из переменной а непосредственно инлайн объекты for _ in range(1000): s = 'text' + 'text' + 'text' + ... # всего 10 раз Теперь каждую строку склейки запустим по 10М раз >>> timeit(t1, number=10000) 0.21951690399964718 >>> timeit(t2, number=10000) 1.4978306379998685 >>> timeit(t3, number=10000) 0.2213820789993406 Хм, а нам говорили что через "+" это плохо и медленно ))) 😁 Тут стоит учитывать, что речь идёт о склейке множества длинных строк. Давайте изменим условия: def t4(): t = 'text'*100 for _ in range(1000): s = t + t + t + t + t + t + t + t + t def t5(): arr = ['text'*100] * 10 for _ in range(1000): s = ''.join(arr) def t6(): for _ in range(1000): s = 'text'*100 + 'text'*100 + ... # всего 10 раз >>> timeit(t4, number=10000) 12.795130728000004 >>> timeit(t5, number=10000) 2.642637542999182 >>> timeit(t6, number=10000) 0.2184546610005782 Вот, уже другой разговор, сразу видна разница, в среднем в 6 раз. Но погодите, почему последний тест t6() по скорости такой же как и t3()? Ведь строки теперь в 100 раз длиннее! Это вопросы оптимизации кода, какие простые изменения ускоряют или замедляют выполнение программы. Мы столкнулись с примером обхода обращения к переменной. Например, именно так работает директива #define в С++, во время компиляции подставляя значение переменной вместо ссылки на неё. В Python это тоже работает, но часто ли вы сможете встретить такой способ работы со строками? К сожалению, способ почти только теоретический. В целом, тесты показали то, что мы хотели. Делаем выводы самостоятельно. Полный листинг 🌍 #tricks

Сколько вы знаете базовых "легальных" способов форматирования строк с помощью переменных в Python? Я насчитал 5! 1️⃣ Оператор + >>> greting = 'Hello' >>> name = 'World' >>> print(greting + ' ' + name) 'Hello World' Самый не актуальный способ форматирования. С мелкими строками в количестве двух штук он работает быстрее всех. Но когда строки становятся длинной в сотни символов, этот метод просаживает производительность. Делов том, что каждый оператор "+" выполняется отдельно для пар переменных, создавая новый объект строки, после которого следует следующий оператор, создающий еще один объект итд... В общем не рекомендуется, кроме случаев, где он явно выигрывает по скорости. 2️⃣ Оператор % >>> name = 'World' >>> print('Hello %s' % name) 'Hello World' Быстрый, но не удобный. Устарел. 3️⃣ Метод str.format() Есть много фишек применения этого метода. Для примера используем самый простой. >>> name = 'World' >>> print('Hello {}'.format(name)) 'Hello World' Наверное, самый функциональный метод с множеством возможностей. Рекомендован к использованию. 4️⃣ f-string >>> name = 'World' >>> print(f'Hello {name}') 'Hello World' Относительно новый и удобный по синтаксису метод. Работает быстрей чем format(). Поддерживает аналогичные фишки форматирования (но не все). Еще развивается и обновляется в новых версиях Python. Рекомендован. 5️⃣ string.Template templ = string.Template('Hello $name') print(templ.substitute(name='World')) 'Hello World' Самый медленный, но самый безопасный способ собрать строку. Он в 10 раз медленней самого медленного способа. Но никаких инлайн-экспрешенов или фигурных скобочек. __________ Есть еще ряд других модулей, такие как textwrap, jinja или собственные методы строки. Но данный пост о простых способах вставки переменных в строку. #tricks