Как соединить два списка? Список поддерживает оператор "+", так что это легко:
Это можно сделать таким способом:
Допустим, есть такой список со словорями:
Или так
Есть вариант и покороче
И даже еще короче, в одну строку и с сохранением порядка:
Можно ещё несколько вариантов придумать, но, думаю, достаточно 😁
__________
Все примеры создают новый словарь, не изменяя старые. Но если в исходных словарях есть другие словари или списки то для независимой копии нужно пройтись еще функцией copy.deepcopy() 😉
#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
Чтобы запустить два контекст менеджера одновременно можно написать так:
А можно и более компактно в одну команду:
#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
Стандартная функция enumerate() очень удобна для получения индекса итерации
Это можно сделать так:
Справедливо, ведь enumerate() возвращает всегда кортеж из 2х элементов, а мы распаковывем его в 3 переменные.
Но есть одна хитрость, которая позволит сделать то что мы задумали! Скобочки!
#tricks
>>> 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
Работаете в PyCharm? Тогда этот пост для вас!
В Python3 добавлен синтаксис аннотаций. То есть, в объявлении функции можно указать какие типы данных у нас тут крутятся. От простых, до сложносоставных.
Например, есть такая функция:
Между тем, в Python2 также можно делать аннотации так, чтобы PyCharm их понял. Записываются они иначе, с помощью комментариев (type hint). Вот та же функция для Python2:
Но знаете ли вы, что такой способ можно использовать и не только для аннотирования функции? Можно указать тип любой переменной в любой строке!
Например, у вас есть внешний API в котором типы не объявлены вообще никак. А хочется иметь автокомплиты для возвращаемых значений.
Вот пример:
Теперь вы можете обозначать типы переменных хоть на каждой строке 😎
Запоминаем формат:
#tricks
В Python3 добавлен синтаксис аннотаций. То есть, в объявлении функции можно указать какие типы данных у нас тут крутятся. От простых, до сложносоставных.
Например, есть такая функция:
def my_func(x: int, y: float) -> float:В этой функции явно указаны типы, а значит, что IDE сможет адекватно анализировать ваш код и использовать всевозможные вспомогательные штуки. Автокомплит, или различные предупреждения о неверном использовании типа.
val = x * y
return val
Между тем, в Python2 также можно делать аннотации так, чтобы PyCharm их понял. Записываются они иначе, с помощью комментариев (type hint). Вот та же функция для Python2:
def my_func(x, y): # type: (int, float) -> floatИнтерпретатору не мешает, а для IDE подсказки😊
val = x * y
return val
Но знаете ли вы, что такой способ можно использовать и не только для аннотирования функции? Можно указать тип любой переменной в любой строке!
Например, у вас есть внешний API в котором типы не объявлены вообще никак. А хочется иметь автокомплиты для возвращаемых значений.
Вот пример:
import some_apiДля переменной value IDE не сможет сообразить автокомплиты или проверку типов. Но с помощью такого же type hint мы можем ему помочь! Даже подсветка будет работать)
def my_func() -> str:
value = some_api.get_value()
# ... life without autocomplete is pain(((
return value
...После этого у переменной value появится автокомплит и всё остальное.
value = some_api.get_value() # type: str
# autocomplete for str here!!!
...
Теперь вы можете обозначать типы переменных хоть на каждой строке 😎
Запоминаем формат:
[code] # type: [Type]
#tricks
This media is not supported in your browser
VIEW IN TELEGRAM
Визуальный пример к посту про type hint
Перед вами простой словарик:
Дело в том, что на уровне данных для Python нет разницы между 1 и True.
Сам тип bool это производный клас от int
Чтобы избежать такой путаницы, возмите себе за правило ключи всегда делать одного типа.
C "0" и False всё аналогично.
#tricks
data = {1: 'value1', True: 'value2'}
С первого взгляда всё нормально. Давайте смотреть что у нас теперь есть в словаре>>> data[1]Кажется мы сломали питон) Но на самом деле нет. Это ошибка разработчика а не Python.
value2
>>> data[True]
value2
Дело в том, что на уровне данных для Python нет разницы между 1 и True.
Сам тип bool это производный клас от int
>>> issubclass(bool, int)Значение True это частный случай int, равный 1. Поэтому у них одинаковый хеш, и словарь их воспринимает как один и тот же ключ
True
>>> hash(1)Так что же у нас сейчас в словаре?
1
>>> hash(True)
1
>>> dataКлючи добавляются в порядке их следования. И если такой ключ уже существует, то вместо создания нового ключа просто обновляется его значение. Поэтому у нас всего один ключ и это 1 а не True.
{1: 'value2'}
Чтобы избежать такой путаницы, возмите себе за правило ключи всегда делать одного типа.
C "0" и False всё аналогично.
#tricks
Как удалить из списка повторяющиеся элементы, сохранив порядок?
Правильный алгоритм выглядит так:
🔸Способ 0
🔸 Способ 1
Создаем пустой список и в простом генераторе сначала проверяем а потом добавляем элемент если его еще нет в списке.
Аналогичный, но с помощью set().
Здесь вторая проверка это хитрый "костыль". Функция на самом деле ничего не возвращает, просто нам надо её вызвать сразу после первой проверки, если она вернула True. Ответ функции add() инвертируем с помощью not чтобы оба условия сработали.
🔸 Способ 3
В одну строку как обычно с помощью set, но с последующей сортировкой для восстановления порядка.
Здесь используем тот факт, что в словаре два одинаковых ключа быть не может и что ключи словаря теперь упорядочены (Python3+). Преобразуем элементы в ключи словаря и обратно в список.
Способы 2-4 НЕ подходят, если элементы списка нехешируемые. То есть они не могут быть в качестве ключа словаря или элемента множества. Например, если у вас список словарей. В этом случае подходит только Способ 1.
#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 = []🔸 Способ 2
[unq.append(item) for item in array if item not in unq]
Аналогичный, но с помощью 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
Часто требуется красиво распечатать ряд переменных через запятую. Возможно это требуется для дебага, а может является частью CLI.
Обычно решается через метод строки join()
#tricks
Обычно решается через метод строки 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
Те кто работал с Python2 наверняка помнят, как приходилось поправлять расширение python-файла из переменной ˍˍfileˍˍ чтобы получить именно .py а не .pyc
Ну сразу бы так)
#2to3
source_path = os.path.splitext(__file__)[0] + '.py'В Python3 эта проблема ушла. Всегда возвращается путь именно к исходному файлу .py.
Ну сразу бы так)
#2to3
В фреймворке PyQt (и PySide тоже) часто встречается настройка чего-либо с помощью так называемых флагов.
Несколько флагов можно указать с помощью оператора "|"
#qt
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
Давайте разберёмся как работают побитовые операторы.
Всего есть 6 основных операторов:
Например, берём два числа, и сразу смотрим как оно выглядит в двоичном виде (Python отбрасывает ведущие нули, так что рядом допишу более удобную форму)
в результат пишет 1 если в одном из элементов есть 1
🔸 Оператор AND
В результат ставит 1 только если оба бита равны 1
🔸 Оператор XOR
Пишет 1 на бит результата, для которого только один из соответствующих битов операндов равен 1.
Заменяет каждый бит на противоположный. Эта операция унарная, то есть поддерживает только один операнд.
Но это тема не поместится в пост, советую поискать информацию в интернете самостоятельно). Если кратко и из документации, то:
Здесь всё просто. Все биты сдвигаются на указанное количество шагов подставляя нули
А зачем нам вся эта информация?
Узнаем в следующем посте...
#triсks
Всего есть 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
Лично я на практике встречал использование побитовых операторов в двух ситуациях (их конечно намного больше).
🔸1. Сдвиг, который соответствует некоторой математической операции (арифметический сдвиг) но работает несравнимо быстрей. Например сдвиг влево равен выражению a*2**b:
🔸2. Числовые маски.
Что это такое?
Создаем несколько переменных, в которых в бинарном представлении все ячейки заполнены
нулями кроме одной позиции. И у каждой переменной используется своя уникальная позиция для бита 1.
Чтобы получить тип bool можем писать так
#tricks
🔸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
Не сложно представить альтернативу на простом PythonREAD = 1
WRITE = 2
DELETE = 3
USER = [READ]
MODERATOR = [READ, WRITE]
ADMIN = [READ, WRITE, DELETE]
can_write = WRITE in ADMIN
Оператор in работает довольно шустро, но всё равно медленней чем побитовый оператор.#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
Но как обычно всегда найдется тот, кому не всё понравится и он напишет свой вариант) В итоге получаем небольшой ряд "числовых" библиотек примерно для одного и того же
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
PyPI
six
Python 2 and 3 compatibility utilities
Обычная практика удаления одинаковых значений из списка с помощью множества
array = [1, 2, 3, 4, 5, 4, 3]
uniq = list(set(array))
Альтернативная запись с помощью литералов вместо функцийuniq = [*{*array}]
#tricksНачиная с версии Python 3.7 появился встроенный профайлер времени импорта модуля. Чтобы его активировать достаточно к запуску интерпретатора добавить аргумент -X importtime
Теперь каждый загруженный модуль будет показывать время своей загрузки, а так же время загрузки вложенных модулей.
Если нет возможности добавить аргумент в команду, то используйте переменную PYTHONPROFILEIMPORTTIME, результат аналогичен
Linux:
Windows
#tricks
python3 -X importtimeТеперь каждый загруженный модуль будет показывать время своей загрузки, а так же время загрузки вложенных модулей.
Если нет возможности добавить аргумент в команду, то используйте переменную PYTHONPROFILEIMPORTTIME, результат аналогичен
Linux:
export PYTHONPROFILEIMPORTTIME=1Windows
set PYTHONPROFILEIMPORTTIME=1#tricks
В посте про правильное использование аргумента shell упоминалось что в некоторых случаях атрибуты следует отправлять списком а не строкой. Что делать, если команда приходит именно строкой? Как её преобразовать в список?
Ответ очевиден
Но в стандартной поставке Python давно уже есть готовое решение, моудль shlex (shell lexical analyzers) который всё это умеет.
И не переживайте что кавычки пропали, subprocess сам всё разрулит с пробелами 😎.
На Windows всё аналогично.
#libs
Ответ очевиден
>>> cmd_str = 'ls -sl'
>>> cmd_lst = cmd_str.split(' ')
>>> print(cmd_lst)
['ls', '-sl']
Но что, если команда имеет более сложный вид в плане пробелов?>>> cmd_str = 'mkdir "My Folder Name"'
>>> print(cmd_str.split(' '))
['mkdir', '"My', 'Folder', 'Name"']
Определённо что-то пошло не так! Имя директории содержит пробелы, поэтому весь путь взят в кавычки. Нам определённо не надо её разделять на аргументы. Чтобы сделать всё правильно нам потребуется распарсить строку чтобы обнаружить, что там в кавычках а что нет. Но в стандартной поставке Python давно уже есть готовое решение, моудль shlex (shell lexical analyzers) который всё это умеет.
>>> import shlex
>>> shlex.split('mkdir "My Folder Name"')
['mkdir', 'My Folder Name']
Теперь команда сработает верно. И не переживайте что кавычки пропали, subprocess сам всё разрулит с пробелами 😎.
На Windows всё аналогично.
#libs
Telegram
Python Заметки
Правильно ли вы используете аргумент shell у методов subprocess?
Вкратце опишу разницу состояний этого аргумента.
(полный разбор — тема для статьи в блоге, возможно позже)
Флаг "shell" определяет, будет ли использоваться системный шел как основной исполняемый…
Вкратце опишу разницу состояний этого аргумента.
(полный разбор — тема для статьи в блоге, возможно позже)
Флаг "shell" определяет, будет ли использоваться системный шел как основной исполняемый…
Знаете ли вы про "магические" методы классов ˍˍgetattributeˍˍ() и ˍˍgetattrˍˍ()?
ˍˍgetattributeˍˍ вызывается всякий раз когда идёт обращение к атрибуту объекта. Например метод или какая-то переменная.
ˍˍgetattrˍˍ вызывается когда атрибут не найден.
И вот тут-то начинается самое интересное. Вы можете написать алгоритм определения динамического атрибута. Вы получаете его имя и можете решить, что вернуть учитывая запрошенное имя. Например, можете сделать запрос в базу данных или просто вернуть значение по умолчанию.
Но пост на самом деле не об этом. Дело в том, что в Python 3.7 добавили возможность определять функцию ˍˍgetattrˍˍ() в модуле с аналогичной функциональностью! (PEP562)
То есть, создав внутри модуля функцию ˍˍgetattrˍˍ() мы определяем действие в случае запроса несуществующего атрибута модуля. Ровно так же как это делаем в классах. Тем самым можно динамически определять состав модуля.
А еще можно создать функцию ˍˍdirˍˍ(), определяющую поведение для стандартной функции dir().
Пример модуля:
ˍˍ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#tricks #pep
>>> print(example.var1)
1
>>> print(example.var3)
AttributeError
>>> print(dir(example)))
['var1', 'var2']
Python Enhancement Proposals (PEPs)
PEP 562 – Module __getattr__ and __dir__ | peps.python.org
It is proposed to support __getattr__ and __dir__ function defined on modules to provide basic customization of module attribute access.
Небольшой экспрешн, с помощью которого можно выяснить, находимся ли мы в интерактивном режиме интерпретатора или нет.
Например, в обычном (не интерактивном) режиме команда input() приведет к ошибке (ожидание ввода с клавиатуры). А в интерактивном режиме не всегда получится открыть какой-то диалог. Этот экспрешн поможет выбрать способ ввода данных, например авторизация пользователя.
Всё верно, так оно и работает, но только до того момента, пока вы не импортируете какой-либо модуль. Внутри неймспейса этого модуля переменная ˍˍfileˍˍ определённо существует, даже если вы импортнули его в интерактиве. То есть внутри модуля способ не работает. А с помощью нашего выражения проверка правильно сработает в любом случае.
PS. Не работает с некоторыми режимами эмуляции интерпретатора.
#tricks
Например, в обычном (не интерактивном) режиме команда input() приведет к ошибке (ожидание ввода с клавиатуры). А в интерактивном режиме не всегда получится открыть какой-то диалог. Этот экспрешн поможет выбрать способ ввода данных, например авторизация пользователя.
is_interactive = bool(getattr(sys, 'ps1', sys.flags.interactive))
Возможно, кто-то скажет: "зачем такие сложности??? Просто проверяем наличие переменной ˍˍfileˍˍ, в интерактивном режиме она не создаётся!"Всё верно, так оно и работает, но только до того момента, пока вы не импортируете какой-либо модуль. Внутри неймспейса этого модуля переменная ˍˍfileˍˍ определённо существует, даже если вы импортнули его в интерактиве. То есть внутри модуля способ не работает. А с помощью нашего выражения проверка правильно сработает в любом случае.
PS. Не работает с некоторыми режимами эмуляции интерпретатора.
#tricks
Как проверяется текущая версия Python?
Допустим, вам нужно выбрать конкретное действие в зависимости от версии используемого интерпретатора.
В моём примере мне нужна версия Python больше чем 3.5.
Самый очевидный способ проверки мажорной версии такой:
Конечно! Иначе поста бы не было 😊
Вспоминаем как работает сравнение итерируемых объектов в Python. Элементы сравниваются попарно. Если первая пара совпала, проверяется следующая пара, и так пока не найдутся разные элементы или закончится один из итераторов. Например, сравним кортежи.
Строки тоже итерируемый объект. Они сравниваются не по длине или кодам символов, а так же попарно, и по порядку символа в таблице (или в алфавите).
# tricks
Допустим, вам нужно выбрать конкретное действие в зависимости от версии используемого интерпретатора.
В моём примере мне нужна версия Python больше чем 3.5.
Самый очевидный способ проверки мажорной версии такой:
>>> import sysС минорной версией будет чуть длинней
>>> print sys.version_info.major > 3
True
>>> print sys.version_info.major == 3 and sys.version_info.minor > 5Можно ли сократить эту запись?
Конечно! Иначе поста бы не было 😊
Вспоминаем как работает сравнение итерируемых объектов в Python. Элементы сравниваются попарно. Если первая пара совпала, проверяется следующая пара, и так пока не найдутся разные элементы или закончится один из итераторов. Например, сравним кортежи.
>>> (1, 2) > (1, 3)Теперь преобразуем version_info в кортеж (или список)
False
>>> (1, 4, 5) > (1, 4, 3)
True
>>> tuple(sys.version_info)И можем использовать его для сравнения кортежей
(3, 7, 3, 'final', 0)
>>> tuple(sys.version_info) > (3, 5)На самом деле можно и не преобразовывать явно, главное запомнить что сравнивать нужно с кортежем.
True
>>> sys.version_info > (3, 5)Можно еще как-то иначе? Можно!
True
Строки тоже итерируемый объект. Они сравниваются не по длине или кодам символов, а так же попарно, и по порядку символа в таблице (или в алфавите).
>>> 'a' > 'b'А что у нас есть еще в sys? Правильно, переменная version.
False
>>> 'e' > 'c'
True
>>> '5' < '4'
False
>>> print (sys.version)Сравниваем эту переменную с другой строкой:
'3.7.3 (default, Dec 20 2019, 18:57:59) \n[GCC 8.3.0]'
>>> sys.version > '3.5'⚠️ Этот метод со строкой перестанет работать когда версия Python станет 3.10+. Не используйте его!
True
# tricks
В стандартной библиотеке Python есть поддержка нескольких текстовых форматов файлов.
Я имею в виду общепринятые форматы хранения текстовых данных. Чаще всего это конфигурационные файлы.
И вот что может читать Python из коробки:
🔸 JSON (JavaScript Object Notation)
Формат простой и понятный. Очень похож на простой Python-код.
https://ru.wikipedia.org/wiki/JSON
https://www.json.org/
🔸 CSV (Comma-Separated Values)
https://ru.wikipedia.org/wiki/CSV
https://www.w3.org/TR/tabular-data-primer/
🔸 XML (eXtensible Markup Language)
https://www.xml.com/
https://ru.wikipedia.org/wiki/XML
🔸 INI (Initialization file)
Мало популярен, но в простых случаях вполне подходит.
https://ru.wikipedia.org/wiki/.ini
Есть еще один популярный формат для конфигов, но к сожалению не в стандартной поставке. Я решил его тоже упомянуть.
🔹 YAML (Yet Another Markup Language)
В основном используется для конфигов.
https://ru.wikipedia.org/wiki/YAML
https://yaml.org/
Установка:
Конечно же существуют и другие форматы. CFG или CONF (парсер для него был в стандартной библиотеки Python2 в модуле ConfigParser), TOML и другие. Но в большинстве случаев стандартно поддерживаемых форматов хватает чтобы закрыть все потребности.
#libs
Я имею в виду общепринятые форматы хранения текстовых данных. Чаще всего это конфигурационные файлы.
И вот что может читать Python из коробки:
🔸 JSON (JavaScript Object Notation)
Модуль json
Один из лидеров по популярности. Используется во многих сферах, от простых конфигов до протоколов передачи данных.Формат простой и понятный. Очень похож на простой Python-код.
https://ru.wikipedia.org/wiki/JSON
https://www.json.org/
🔸 CSV (Comma-Separated Values)
Модуль csv
Формат описания табличных данных. Его используют аналитики, датасаентисты и Exel-мастера. Что-то вроде текстовой базы данных. https://ru.wikipedia.org/wiki/CSV
https://www.w3.org/TR/tabular-data-primer/
🔸 XML (eXtensible Markup Language)
Модуль xml
Самый популярный формат в WEB, так как любая HTML страница (то есть все страницы в сети) это XML. Многие программы используют эту разметку для сохранения данных. Удобный формат многоуровневой вложенности объектов с атрибутами.https://www.xml.com/
https://ru.wikipedia.org/wiki/XML
🔸 INI (Initialization file)
Модуль configparser
Очень простой формат конфига для Windows с возможностью группировать параметры.Мало популярен, но в простых случаях вполне подходит.
https://ru.wikipedia.org/wiki/.ini
Есть еще один популярный формат для конфигов, но к сожалению не в стандартной поставке. Я решил его тоже упомянуть.
🔹 YAML (Yet Another Markup Language)
В основном используется для конфигов.
https://ru.wikipedia.org/wiki/YAML
https://yaml.org/
Установка:
pip install pyyaml
___________Конечно же существуют и другие форматы. CFG или CONF (парсер для него был в стандартной библиотеки Python2 в модуле ConfigParser), TOML и другие. Но в большинстве случаев стандартно поддерживаемых форматов хватает чтобы закрыть все потребности.
#libs