TGINSIGHT CHAT
Python Заметки
@pythonotes
EducationИнтересные заметки и обучающие материалы по Python Контакт: @paulwinex ⚠️ Рекламу на канале не делаю!⚠️ Хештеги для поиска: #tricks #libs #pep #basic #regex #qt #django #2to3 #source #offtop
Неодамнешни објави
Ознака: #tricks · 178 објави
Објавено 18 мар.
Небольшой экспрешн, с помощью которого можно выяснить, находимся ли мы в интерактивном режиме интерпретатора или нет. Например, в обычном (не интерактивном) режиме команда input() приведет к ошибке (ожидание ввода с клавиатуры). А в интерактивном режиме не всегда получится открыть какой-то диалог. Этот экспрешн поможет выбрать способ ввода данных, например авторизация пользователя. is_interactive = bool(getattr(sys, 'ps1', sys.flags.interactive)) Возможно, кто-то скажет: "зачем такие сложности??? Просто проверяем наличие переменной ˍˍfileˍˍ, в интерактивном режиме она не создаётся!" Всё верно, так оно и работает, но только до того момента, пока вы не импортируете какой-либо модуль. Внутри неймспейса этого модуля переменная ˍˍfileˍˍ определённо существует, даже если вы импортнули его в интерактиве. То есть внутри модуля способ не работает. А с помощью нашего выражения проверка правильно сработает в любом случае. PS. Не работает с некоторыми режимами эмуляции интерпретатора. #tricks
Hashtags
Објавено 16 мар.
Знаете ли вы про "магические" методы классов ˍˍgetattributeˍˍ() и ˍˍgetattrˍˍ()? ˍˍgetattributeˍˍ вызывается всякий раз когда идёт обращение к атрибуту объекта. Например метод или какая-то переменная. ˍˍgetattrˍˍ вызывается когда атрибут не найден. И вот тут-то начинается самое интересное. Вы можете написать алгоритм определения динамического атрибута. Вы получаете его имя и можете решить, что вернуть учитывая запрошенное имя. Например, можете сделать запрос в базу данных или просто вернуть значение по умолчанию. Но пост на самом деле не об этом. Дело в том, что в Python 3.7 добавили возможность определять функцию ˍˍgetattrˍˍ() в модуле с аналогичной функциональностью! (PEP562) То есть, создав внутри модуля функцию ˍˍgetattrˍˍ() мы определяем действие в случае запроса несуществующего атрибута модуля. Ровно так же как это делаем в классах. Тем самым можно динамически определять состав модуля. А еще можно создать функцию ˍˍdirˍˍ(), определяющую поведение для стандартной функции dir(). Пример модуля: # example.py my_variables = {'var1': 1, "var2": 2} def __getattr__(name): try: return my_variables[name] except KeyError: raise AttributeError def __dir__(): return list(my_variables.keys()) Как использовать модуль >>> import example >>> print(example.var1) 1 >>> print(example.var3) AttributeError >>> print(dir(example))) ['var1', 'var2'] #tricks#pep
Објавено 12 мар.
Начиная с версии Python 3.7 появился встроенный профайлер времени импорта модуля. Чтобы его активировать достаточно к запуску интерпретатора добавить аргумент -X importtime python3 -X importtime Теперь каждый загруженный модуль будет показывать время своей загрузки, а так же время загрузки вложенных модулей. Если нет возможности добавить аргумент в команду, то используйте переменную PYTHONPROFILEIMPORTTIME, результат аналогичен Linux: export PYTHONPROFILEIMPORTTIME=1 Windows set PYTHONPROFILEIMPORTTIME=1 #tricks
Hashtags
Објавено 10 мар.
Обычная практика удаления одинаковых значений из списка с помощью множества array = [1, 2, 3, 4, 5, 4, 3] uniq = list(set(array)) Альтернативная запись с помощью литералов вместо функций uniq = [*{*array}] #tricks
Hashtags
Објавено 6 мар.
Лично я на практике встречал использование побитовых операторов в двух ситуациях (их конечно намного больше). 🔸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
Hashtags
Објавено 26 фев.
Часто требуется красиво распечатать ряд переменных через запятую. Возможно это требуется для дебага, а может является частью 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
Hashtags
Објавено 24 фев.
Как удалить из списка повторяющиеся элементы, сохранив порядок? 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
Hashtags
Објавено 22 фев.
Перед вами простой словарик: 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
Hashtags
Објавено 18 фев.
Работаете в 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
Hashtags
Објавено 16 фев.
Стандартная функция 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
Hashtags
Објавено 14 фев.
Чтобы запустить два контекст менеджера одновременно можно написать так: 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
Hashtags
Објавено 12 фев.
Как соединить два списка? Список поддерживает оператор "+", так что это легко: >>> 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
Hashtags