Дополнение к посту про shell в subprocess.
Чем полезен режим вызова через shell? То есть, когда вы ставите аргумент shell=True.
Ваша команда запустится не напрямую, а через системный шел. А это значит что доступны все возможности шела.
Например:
- распаковка пути с символом "~"
#tricks
Чем полезен режим вызова через shell? То есть, когда вы ставите аргумент shell=True.
Ваша команда запустится не напрямую, а через системный шел. А это значит что доступны все возможности шела.
Например:
- распаковка пути с символом "~"
subprocess.check_output('ls ~/', shell=True)
- распаковка переменных окруженияsubprocess.check_output('ls $HOME', shell=True)
- использование пайпа командsubprocess.check_output('cat $HOME/output.log | grep -n error', shell=True))
В общем, те, кто активно использует терминал, могут остальное додумать сами 😉#tricks
👍1
У словаря есть полезный метод get() который может "аккуратно" спросить значение по ключу и вернуть что-то по умолчанию если такого ключа не нашлось.
Я встречал два способа записать эту логику, очень похожие но имеющие серьезную разницу.
По умолчанию, если ключа нет в словаре, метод возвращает None или то что указано в аргументе default.
Если ключ не существует, то вернется None. Сработает оператор or и мы получим значение 123.
Но основная опасность кроется в операторе or! Дело в том, что значение из аргумента default вернется только если КЛЮЧ ОТСУТСТВУЕТ В СЛОВАРЕ. Если ключ найден, то вернется его значение.
Если же мы пишем вариантом с or, то правила меняется. Значение 123 мы получим если ключ отсутствует в словаре или если найденное значение равно False в виде bool. Например, если ключ всё же был найден но значение 0, мы всё равно получим 123, несмотря на то, что 0 может быть вполне валидным значением.
#tricks
Я встречал два способа записать эту логику, очень похожие но имеющие серьезную разницу.
По умолчанию, если ключа нет в словаре, метод возвращает None или то что указано в аргументе default.
>>> my_dict.get('unknown_key', default=123)
123
>>> my_dict.get('unknown_key')
None
Нас интересует первый вариант. Его можно записать еще и таким способом>>> my_dict.get('unknown_key') or 123
Чем он отличается от варианта с аргументом default? На первый взгляд ничем.Если ключ не существует, то вернется None. Сработает оператор or и мы получим значение 123.
Но основная опасность кроется в операторе or! Дело в том, что значение из аргумента default вернется только если КЛЮЧ ОТСУТСТВУЕТ В СЛОВАРЕ. Если ключ найден, то вернется его значение.
Если же мы пишем вариантом с or, то правила меняется. Значение 123 мы получим если ключ отсутствует в словаре или если найденное значение равно False в виде bool. Например, если ключ всё же был найден но значение 0, мы всё равно получим 123, несмотря на то, что 0 может быть вполне валидным значением.
>>> d = {"key": 0}
>>> d.get("key", 5)
0
>>> d.get("key") or 5
5
# во втором случае неоднозначный результат
⚠️ Будьте внимательны! Точно представляйте, что вы хотите получить от своего кода. #tricks
🥚 Забавная пасхалка
Попытка заглянуть в будущее и узнать, появятся ли в Python фигурный скобки даёт вполне чёткий ответ
Попытка заглянуть в будущее и узнать, появятся ли в Python фигурный скобки даёт вполне чёткий ответ
from __future__ import braces
File "<input>", line 1
SyntaxError: not a chanceВсе мы знаем, что в Python всё является объектом. Это значит, что всё можно сохранить в переменную, передать аргументом или вернуть из функции через return.
Но известно ли вам, что объектом можно сделать даже срез списка?! То есть сохранить в переменную алгоритм среза и применить его позже.
Это можно сделать с помощью builtin функции slice().
Для примера возмем простой список
Используется очень просто
#tricks
Но известно ли вам, что объектом можно сделать даже срез списка?! То есть сохранить в переменную алгоритм среза и применить его позже.
Это можно сделать с помощью builtin функции slice().
Для примера возмем простой список
>>> array = list(range(10))
Теперь создадим несколько срезов>>> half = slice(None, len(array)//2)
>>> step_by_2 = slice(None, None, 2)
>>> invert = slice(None, None, -1)
Используется очень просто
>>> array[half]
[0, 1, 2, 3, 4]
>>> array[step_by_2]
[0, 2, 4, 6, 8]
>>> array[invert]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]#tricks
Сколько вы знаете базовых "легальных" способов форматирования строк с помощью переменных в Python? Я насчитал 5!
1️⃣ Оператор +
Делов том, что каждый оператор "+" выполняется отдельно для пар переменных, создавая новый объект строки, после которого следует следующий оператор, создающий еще один объект итд... В общем не рекомендуется, кроме случаев, где он явно выигрывает по скорости.
2️⃣ Оператор %
3️⃣ Метод str.format()
Есть много фишек применения этого метода. Для примера используем самый простой.
4️⃣ f-string
Рекомендован.
5️⃣ string.Template
__________
Есть еще ряд других модулей, такие как textwrap, jinja или собственные методы строки. Но данный пост о простых способах вставки переменных в строку.
#tricks
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
Скорее всего уже слышали, что складывать строки через + это плохая практика. Падение производительности, и всё такое. Без лишних слов, давайте измерять:
Тут стоит учитывать, что речь идёт о склейке множества длинных строк.
Давайте изменим условия:
Это вопросы оптимизации кода, какие простые изменения ускоряют или замедляют выполнение программы. Мы столкнулись с примером обхода обращения к переменной. Например, именно так работает директива #define в С++, во время компиляции подставляя значение переменной вместо ссылки на неё.
В Python это тоже работает, но часто ли вы сможете встретить такой способ работы со строками? К сожалению, способ почти только теоретический.
В целом, тесты показали то, что мы хотели. Делаем выводы самостоятельно.
Полный листинг 🌍
#tricks
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 это тоже синтаксис.
Но если повернуть телефон горизонтально, то всё становится куда приятней!
Казалось бы, это же очевидно! Но я сам не сразу догадался 😁
Читаете мой канал на телефоне? Код в примерах бывает растягивается в достаточно длинные строки. На мобиле смотреть не очень удобно. Особенно, учитывая, что переносы в Python это тоже синтаксис.
Но если повернуть телефон горизонтально, то всё становится куда приятней!
Казалось бы, это же очевидно! Но я сам не сразу догадался 😁
😉 Трик про flatten-список.
Задача: из списка списков сделать одноуровневый список
Допустим, есть список интов
Как получить их сумму? Очень просто!
И тут вы подумаете: Вау, какой удобный метод. Он просто берет список объектов и склеивает их через "+". Удобно же!
Такс, если list, как тип, поддерживает оператор сложения, то я же могу тогда сделать такой финт:
Стоп, давайте разбираться. Если функция sum() просто прибавляет очередной аргумент списка к предыдущему, то с чем складывается самый первый элемент? Должно быть какое-то стартовое значение. И оно есть, это ноль "0". Потому-то мы и видим такую ошибку.
На наше счастье мы можем указать стартовое значение вместо ноля, чтобы получить сумму, используя в качестве начала другое число:
Вы, скорее всего, ломанётесь проверять другие типы и будете правы. С другими тоже работает. Но Python не упустит случай вас потроллить)))
Скорее всего именно эта ошибка сделана на случай если вы захотите склеить большой текстовый документ, прочитанный через readlines().
_______
Это далеко не единственный способ сделать flatten-список. Пост скорей про функцию sum.
#tricks
Задача: из списка списков сделать одноуровневый список
>>> 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
Простая задача: получить рандомную строку.
Такое может потребоваться для создания секретного ключа, токена, одноразовой ссылки и тд. Как бы вы решали такую задачу?
Допустим, требуется строка в 20 символов.
Чаще всего решают примерно так:
- Короче запись
- Более читаемый и понятный код
- Быстрей работает ⏱
Время на 1М запусков:
- random+string : ~14.3sec
- uuid : ~5.5sec
- модуль secrets : ~1.2sec
________
Прошу не путать получение рандомной строки и получение контрольной суммы.
Это разные по назначению задачи.
#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
Как соединить два списка? Список поддерживает оператор "+", так что это легко:
Это можно сделать таким способом:
Допустим, есть такой список со словорями:
Или так
Есть вариант и покороче
И даже еще короче, в одну строку и с сохранением порядка:
Можно ещё несколько вариантов придумать, но, думаю, достаточно 😁
__________
Все примеры создают новый словарь, не изменяя старые. Но если в исходных словарях есть другие словари или списки то для независимой копии нужно пройтись еще функцией 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