TGTGInsighttelegram intelligenceLIVE / telegram public index
Назад кон каналите
Python Заметки avatar

TGINSIGHT CHAT

Python Заметки

@pythonotes

Education

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

Претплатници2,220Тековни претплатници
Следени објави384Број на индексирани објави
Неодамнешен опфат5,358Збир на неодамнешни прегледи
Неодамнешни објави

Неодамнешни објави

Ознака: #tricks · 178 објави

当前筛选 #tricks清除筛选

Објавено 10 фев.

Простая задача: получить рандомную строку. Такое может потребоваться для создания секретного ключа, токена, одноразовой ссылки и тд. Как бы вы решали такую задачу? Допустим, требуется строка в 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

422 views

Hashtags

Објавено 9 фев.

😉 Трик про 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

424 views

Hashtags

Објавено 7 фев.

Скорее всего уже слышали, что складывать строки через + это плохая практика. Падение производительности, и всё такое. Без лишних слов, давайте измерять: 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

416 views

Објавено 5 фев.

Сколько вы знаете базовых "легальных" способов форматирования строк с помощью переменных в 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

447 views

Hashtags

Објавено 3 фев.

Все мы знаем, что в Python всё является объектом. Это значит, что всё можно сохранить в переменную, передать аргументом или вернуть из функции через return. Но известно ли вам, что объектом можно сделать даже срез списка?! То есть сохранить в переменную алгоритм среза и применить его позже. Это можно сделать с помощью 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

424 views

Hashtags

Објавено 30 јан.

У словаря есть полезный метод get() который может "аккуратно" спросить значение по ключу и вернуть что-то по умолчанию если такого ключа не нашлось. Я встречал два способа записать эту логику, очень похожие но имеющие серьезную разницу. По умолчанию, если ключа нет в словаре, метод возвращает 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

518 views

Hashtags

Објавено 28 јан.

Дополнение к посту про shell в subprocess. Чем полезен режим вызова через 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

446 views

Hashtags

Објавено 26 јан.

Правильно ли вы используете аргумент shell у методов subprocess? Вкратце опишу разницу состояний этого аргумента. (полный разбор — тема для статьи в блоге, возможно позже) Флаг "shell" определяет, будет ли использоваться системный шел как основной исполняемый файл для вызова вашей команды. ⏩ shell=True - К вашей команде добавится исполняемый файл /bin/sh или cmd.exe - Ваша команда будет аргументом флага -с, поэтому команду нужно передавать строкой - Команду необходимо передавать с готовым экранированием и лексическим разбором пробелов. ✅ Правильно: subprocess.check_output('ls -sl', shell=True) Команда выглядит так: /bin/sh -c "ls -sl" Здесь видно, что мы передаём значение аргумента -c а не саму команду. ❌Неправильно subprocess.check_output(['ls', '-sl'], shell=True) Команда выглядит так: /bin/sh -c "ls" -sl Ошибки не будет, но аргументы используются неверно. Получите не то что ожидаете. ⏩ shell=False - команду нужно передать списком - ожидается, что первым аргументом будет исполняемый файл - будет запущен непосредственно файл из первого аргумента, без /bin/sh или cmd.exe - автоматическое экранирование пробелов в аргументах списка ✅Правильно subprocess.check_output(['ls', '-sl'], shell=False) Команда будет выглядеть так ls -sl ❌Неправильно subprocess.check_output('ls -sl', shell=False) Эта команда завершится ошибкой: No such file or directory То есть система пытается найти файл "ls -sl" а не файл "ls" А также, если не используется shell то путь к исполняемому файлу требуется писать абсолютным, даже стандартные системные утилиты. ___________________ - для Windows всё аналогично - для других методов из subprocess всё аналогично #tricks#libs

474 views

Hashtags

Објавено 24 јан.

В предыдущем посте⬆️ был пример кеширования функции. В стандартной библиотеке есть еще один способ, но для классов. Это functools.cached_property (Python3.8+). Логика работы точно такая же, но: - он предназначен только для метода класса. - аналогичен декоратору property, то есть мы получим не функцию а свойство класса. - не принимает аргументов (кроме self), так же как и property. - кеш сохраняет в атрибутах класса (внутри ˍˍdictˍˍ). Эдакий частный случай lru_cache для класса. В результате, вместо такой записи class MyClass: @property @functools.lru_cache(maxsize=1) def value(self): return 123 мы можем записать более красиво и адаптированно для property from functools import cached_property class MyClass: @cached_property def value(self): return 123 Помимо более логичной записи, этот декоратор решает еще ряд проблем, возникающих при декорирвовании свойств классов через lru_cache. Так что в таких случаях его использование не то чтобы желательно, а обязательно! #tricks

438 views

Hashtags

Објавено 22 јан.

Кеширование, это способ оптимизировать скорость программы за счёт повторного переиспользования рассчитанных данных. При этом инвалидация кеша вопрос сложный и неоднозначный. Тем не менее, есть ряд простых случаев, когда кеш вполне уместен. Имеется функция, которая принимает аргументы и возвращает некое значение. При одинаковых аргументах результат всегда одинаковый. Давайте приведём такой очевидный пример: def add(a, b): return a + b Каждому понятно, что, если в эту функцию мы будем отправлять одни и те же данные, мы будем получать одинаковый результат, это важно. Тогда зачем нам каждый раз это пересчитывать? Давайте кешировать! Для этого используем готовое решение из стандартной библиотеки functools.lru_cache() (Python3.4+) from functools import lru_cache import time @lru_cache(maxsize=64) def add(a, b): time.sleep(1) return a + b Добавил задержку чтобы имитировать расчёты. Параметр декоратора maxsize указывает сколько именно разных пар аргументы-результат мы будем хранить. Теперь вызываем функцию с замером времени. Используем массив с повторяющимися значениями >>> for i in [1, 2, 3, 2, 1, 4]: >>> start = time.perf_counter() >>> add(2, i) >>> end = time.perf_counter()-start >>> print(f'i={i}, Time={end}') i=1, Time=1.0007981109 i=2, Time=1.0008854520 i=3, Time=1.0008842469 i=2, Time=0.0000204799 # из кеша i=1, Time=0.0000132510 # из кеша i=4, Time=1.0008038339 В распечатке времени видно, как только входящие данные повторяются, вместо пересчёта нам возвращается готовое значение из кеша. Такое кеширование будет неверным, если результат зависит не только от входящих данных. Учитывайте это! #tricks

446 views

Hashtags

Објавено 20 јан.

Чем вы измеряете время? Обычно, когда мы хотим измерить время выполнения функции, мы пишем так: import time start_time = time.time() execute_something() print('Time:', time.time()-start_time) Всё верно, мы посчитаем время выполнения с точностью до долей секунды. Но данный способ не даёт 100% гарантии правильного расчёта. Почему? Дело в том, что метод time() возвращает системное время. Допустим, мы начали замер времени, сохранив текущее время. А во время выполнения функции кто-то зашел и переставил системные часы на час назад (например автоматическая синхронизация времени или переход на зимнее\летнее время). Когда функция завершится, мы вполне можем получить отрицательное время! Это очевидный фейл. Поэтому, для таких случаев есть специальный метод time.monotonic(). В описании метода ясно написано, что это время не может идти назад, так как это относительное время. Именно этот метод будет делать правильные изменения. Но самый правильный способ замера производительности это метод time.perf_counter(). Он даёт максимально возможный точный замер времени (меньше наносекунды). Полезно для профайлинга очень быстрых функций. Итого, наш тест будет выглядеть так: start_time = time.perf_counter() execute_something() print('Time:', time.perf_counter()-start_time) ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Про эти функции можно почитать в PEP418 Возможно, кто-то привык использовать время метод time.clock(), то есть время работы программы с момента старта. Учтите, что этот метод устарел и начиная с Python 3.8 будет удалён. #tricks

449 views

Hashtags

Објавено 18 јан.

Кто-то в вашей компании любит "брейкпринты" ??? (это такой способ "дебажить" 🐞 код с помощью функции print()) Проблема такого подхода состоит в том, что для просмотра output нужно лично присутствовать у монитора. Более нигде эта информация не записывается и пропадает после закрытия приложения. Если присутствие невозможно, то приходится искать обходные пути. Кроме переписывания кода, записи консоли на видео и нарезки серии скриншотов есть и более хитрый способ. Например, глобально перенаправить вывод функции print() в файл в сети (или потом попросить прислать). В стандартной библиотеке уже есть подходящее решение: from contextlib import redirect_stdout with open('//mnt/share/temp.log', 'a') as f: with redirect_stdout(f): # здесь вызываем основной код print('Some Debug Info') # текст запишется в файл main() Советы: 🔸1. Следует заведомо позаботиться, чтобы путь к файлу был сетевой. 🔸2. Убедитесь что есть доступ на запись файла 🔸3. Эту обёртку нужно делать в самом начале выполнения скрипта, во время вызова основной функции (например main()). 🔸4. Научите коллег пользоваться логгингом! 🔸5. Проследите, что коллеги изучили и используют логгинг. На самом деле вместо файла можно указать любой другой аналогичный объект, например отправка в сеть, но это уже другая история #tricks

454 views

Hashtags

12•••10•••12131415
ПретходнаСтраница 14 од 15Следна