Полагаю, все однажды видели или использовали вот эту конструкцию:
Это обычный способ реализации логики вида "нужно поймать эксепшен конкретного типа, проигнорировать его и продолжить работу". В стандартной библиотеке есть более лаконичный способ:
Это контекстный менеджер
Документация стандартной библиотеки напоминает, что "как и с любым другим механизмом, полностью подавляющим эксепшены, этот контекстный менеджер следует использоваться только в конкретных специфических случаях, когда молчаливое продолжение работы программы после возникновения эксепшена точно является правильным поведением".
В общем, не глотайте эксепшены без повода, процесс дебага может стать очень болезненным (:
Хороший пример, когда этот контекстный менеджер удобно использовать, -- удаление файла. Я хочу почистить старые файлы перед запуском программы, но может быть так, что это первый запуск и старых файлов ещё не накопилось. Делаю так:
Этот контекстный менеджер доступен, начиная с Python 3.4.
Если вы используете более старые версии Питона, то вы можете добавить в проект собственную реализацию, которая будет выглядеть так:
try:
<какой-то код>
except <какой-то тип эксепшена>:
pass
Это обычный способ реализации логики вида "нужно поймать эксепшен конкретного типа, проигнорировать его и продолжить работу". В стандартной библиотеке есть более лаконичный способ:
from contextlib import suppress
with suppress(<какой-то тип эксепшена>):
<какой-то код>
Это контекстный менеджер
suppress, который импортируется из модуля contextlib. Ему передаётся тип эксепшена (или типы, можно несколько, да), который необходимо "проглотить", дальше в теле with пишем код, который эти эксепшены может выкинуть. Если эксепшен возникнет, он будет пойман и проигнорирован, а управление перейдёт к первой строчке после блока with. Документация стандартной библиотеки напоминает, что "как и с любым другим механизмом, полностью подавляющим эксепшены, этот контекстный менеджер следует использоваться только в конкретных специфических случаях, когда молчаливое продолжение работы программы после возникновения эксепшена точно является правильным поведением".
В общем, не глотайте эксепшены без повода, процесс дебага может стать очень болезненным (:
Хороший пример, когда этот контекстный менеджер удобно использовать, -- удаление файла. Я хочу почистить старые файлы перед запуском программы, но может быть так, что это первый запуск и старых файлов ещё не накопилось. Делаю так:
with suppress(FileNotFoundError):
os.remove('my-file.txt')
Этот контекстный менеджер доступен, начиная с Python 3.4.
Если вы используете более старые версии Питона, то вы можете добавить в проект собственную реализацию, которая будет выглядеть так:
from contextlib import contextmanager
@contextmanager
def suppress(*exceptions):
try:
yield
except exceptions:
pass
Я тут писала письмо пользователям, где доносила мысль, что для влючения некоторого функционала нужно добавить переменную в конфиг. И для облегчения пользовательской жизни (чтобы им не нужно было открывать файл, вставлять туда данные, сохранять и закрывать файл), решила использовать
Команда сначала выглядела как-то так:
Предполагается, что команда должна добавить в конец файла новую строчку с переменной. Так вот.
Не надо так.
Что может пойти не так? В local.py уже могут быть данные. Но мы же используем >> , это как раз про добавление в конец файла, разве нет? Да, но кто сказал, что добавление данных в конец файла == добавление данных на новую строку? Чтобы новые данные встали на новую строку, предыдущая строка должна заканчиваться \n. Где гарантия, что при предыдущих изменениях файла этот \n там появился? А вдруг файл выглядит вот так?
Если для такого файла я воспользуюсь командой из начала, то в итоге получу:
И конфиг, очевидно, не будет работать. Что делать? Вставлять \n самостоятельно перед своими данными. Однако, если просто добавить \n в команду, снова получится не то:
Чтобы всё это заработало, echo нужно передать опцию
Теперь конфиг в безопасности.
echo. Команда сначала выглядела как-то так:
echo 'BOO = True' >> /my/project/local.py
Предполагается, что команда должна добавить в конец файла новую строчку с переменной. Так вот.
Не надо так.
Что может пойти не так? В local.py уже могут быть данные. Но мы же используем >> , это как раз про добавление в конец файла, разве нет? Да, но кто сказал, что добавление данных в конец файла == добавление данных на новую строку? Чтобы новые данные встали на новую строку, предыдущая строка должна заканчиваться \n. Где гарантия, что при предыдущих изменениях файла этот \n там появился? А вдруг файл выглядит вот так?
~$ cat /tmp/local.py
FOO = False~$
Если для такого файла я воспользуюсь командой из начала, то в итоге получу:
~$ echo 'BOO = True' >> /tmp/local.py
~$ cat /tmp/local.py
FOO = FalseBOO = True
И конфиг, очевидно, не будет работать. Что делать? Вставлять \n самостоятельно перед своими данными. Однако, если просто добавить \n в команду, снова получится не то:
~$ echo '\nBOO = True' >> /tmp/local.py
~$ cat /tmp/local.py
FOO = False\nBOO = True
Чтобы всё это заработало, echo нужно передать опцию
-e (enable interpretation of backslash escapes): ~$ echo -e '\nBOO = True' >> /tmp/local.py
~$ cat /tmp/local.py
FOO = False
BOO = True
Теперь конфиг в безопасности.
Несколько минут назад недоумённо смотрела на это:
Вы видите то, что вижу я? 7.5 округляется вверх, а 10.5 округляется вниз. Это в третьем Питоне.
А вот во втором в обоих случаях округляется вверх:
Оказалось, что в Python 3 используется "округление до ближайшего чётного", то есть 7,5 -> 8, 10,5 -> 10. Это даже написано в документации к round():
Бедный неприкаянный последний участник, которому не досталось место за столом из-за округления ):
Короче, будьте бдительны с round().
В случаях, когда вам точно всегда нужно округление вверх, вместо round() можно использовать:
1) либо
2) либо вот так (когда делим на 2):
3) либо вот так (тоже когда делим на 2):
4) либо вот так (для любого делителя):
# python3
>>> round(7.5)
8
>>> round(10.5)
10
Вы видите то, что вижу я? 7.5 округляется вверх, а 10.5 округляется вниз. Это в третьем Питоне.
А вот во втором в обоих случаях округляется вверх:
# python2
>>> round(7.5)
8.0
>>> round(10.5)
11.0
Оказалось, что в Python 3 используется "округление до ближайшего чётного", то есть 7,5 -> 8, 10,5 -> 10. Это даже написано в документации к round():
rounding is done toward the even choice. Википедия объясняет такой подход тем, что он minimizes the expected error when summing over rounded figures. То есть если вы складываете много округлённых чисел, то итоговая погрешность будет намного меньше при таком подходе. Однако, в случае, когда вам нужно посчитать, сколько двухместных столов вам нужно на группы из 20 и 21 человека -- у вас могут быть проблемы. По логике третьего Питона на любую из этих групп понадобится по 10 столов: >>> round(20 / 2)
10
>>> round(21 / 2)
10
Бедный неприкаянный последний участник, которому не досталось место за столом из-за округления ):
Короче, будьте бдительны с round().
В случаях, когда вам точно всегда нужно округление вверх, вместо round() можно использовать:
1) либо
math.ceil() -- округление вверх из модуля math (универсальный способ для любых делителей): >>> import math
>>> math.ceil(20 / 2)
10
>>> math.ceil(21 / 2)
11
2) либо вот так (когда делим на 2):
>>> n = 20
>>> n // 2 + n % 2
10
>>> n = 21
>>> n // 2 + n % 2
11
3) либо вот так (тоже когда делим на 2):
>>> n = 20
>>> (n + 1) // 2
10
>>> n = 21
>>> (n + 1) // 2
11
4) либо вот так (для любого делителя):
>>> a = 2
>>> n = 20
>>> (n + a - 1) // a
10
>>> n = 21
>>> (n + a - 1) // a
11
Если вы не знаете, куда на сервере положить какие-то файлы (например, относящиеся к вашему сервису -- исполняемые, кэши, статику, логи, вот это всё), то почитать о том, "как правильно" и для чего вообще на машинах существуют всякие
hier от hierarchy. Содержит в себе "description of the filesystem hierarchy". Там мало, но полезно.
/usr/share, /usr/lib, /var/lib, /lib, /etc и пр., можно тут: man hier
hier от hierarchy. Содержит в себе "description of the filesystem hierarchy". Там мало, но полезно.
Пересмотрела старый видос Раймонда Хеттингера (core-разработчик Python) про "красивый, идиотматичный Питон" и вслед за Раймондом хочу напомнить, что вот это:
-- лучше вот этого:
Clarify function calls with keyword arguments. Если передаваемые аргументы не говорят сами за себя, кем они являются, указывайте имя параметра. Не стоит заставлять себя и других людей ходить в объявление функции, чтобы понять, с чем именно она тут вызывается.
Вот хорошее на эту же тему:
twitter_search('@lauvenhelz', retweets=False, numtweets=20, popular=True)-- лучше вот этого:
twitter_search('@lauvenhelz', False, 20, True)Clarify function calls with keyword arguments. Если передаваемые аргументы не говорят сами за себя, кем они являются, указывайте имя параметра. Не стоит заставлять себя и других людей ходить в объявление функции, чтобы понять, с чем именно она тут вызывается.
Вот хорошее на эту же тему:
Forwarded from Вестник села Маракуйева
Увидел у Мэтта Годболта в докладе хорошо сформулированное, и с вами поделюсь.
Сегодняшняя заметка в итоге вышла настолько толстой, что написала её в Телеграфе. Смотреть, как из вложенного цикла сделать плоский, узнать, что ещё умеет делать sum, кроме складывания чисел, и как замерить время выполнения кода, -- тут.
Telegraph
Как из вложенного цикла сделать плоский, что ещё умеет делать sum, кроме складывания чисел, и как легко замерить время выполнения…
Вкручиваю фичу в проект, наткнулась в коде на возможность использовать sum() неизвестным мне ранее способом. Обычно sum() используется, чтобы сложить числа внутри списка: > sum([1, 2, 3]) 6 На самом деле, вызов этой функции выглядит как: sum([1, 2, 3], start=0)…
А у нас снова рубрика #ненадотак
Я регулярно сравниваю какие-то хеши. И регулярно я делаю это глазами, сравнивая несколько первых и последних символов. Если они совпадают, всё ок, хеши одинаковые, едем дальше.
Так вот. Не надо так (: Следите:
Будьте бдительны, сравнивайте хеши полностью, а не только начало-конец.
Для наглядности картинкой:
Я регулярно сравниваю какие-то хеши. И регулярно я делаю это глазами, сравнивая несколько первых и последних символов. Если они совпадают, всё ок, хеши одинаковые, едем дальше.
Так вот. Не надо так (: Следите:
~$ echo -n "subtitle illusive planes" | md5sum
4188d4cdcf2be92a112bdb8ce4500243 -
~$ echo -n "wantings premises forego" | md5sum
4188d209a75e1a9b90c6fe3efe300243 -
Будьте бдительны, сравнивайте хеши полностью, а не только начало-конец.
Для наглядности картинкой:
Если у вас python3.8+, вы дебажитесь и вам нужно распечатать значения нескольких переменных, то можно в f-string использовать знак
Обратите внимание, что если вызывать с
=, будет вот так: >>> a = 'boo'
>>> b = 'foo'
>>> print(f'{a=} {b=} {a.upper()=}')
a='boo' b='foo' a.upper()='BOO'
Обратите внимание, что если вызывать с
upper(), то в принте будет указан a.upper(). Удобно сравнивать результаты вызова разных функций или референс и результат обработки: не нужно создавать дополнительные переменные.Перескажу свежий твит Раймонда Хеттингера, чтобы вы на него подписались.
Объявляя константы с длинным числовым значением, разделяйте их андерскором, чтобы, например, не вглядываться в количество нулей:
Когда дебажитесь и выводите на печать длинные числа, облегчайте себе жизнь им же:
А Раймонда читать тут.
Объявляя константы с длинным числовым значением, разделяйте их андерскором, чтобы, например, не вглядываться в количество нулей:
>>> c = 1_000_000_000
>>> c
1000000000Когда дебажитесь и выводите на печать длинные числа, облегчайте себе жизнь им же:
>>> c = 49581370
>>> f'{c:_d}'
'49_581_370'А Раймонда читать тут.
X (formerly Twitter)
Raymond Hettinger (@raymondh) on X
Chief trainer for Mutable Minds.
Certified Public Accountant, Retired
Python guru. Alloy & TLA⁺ enthusiast.
Aspiring pianist. Former pilot.
Born at 320 ppm CO₂.
Certified Public Accountant, Retired
Python guru. Alloy & TLA⁺ enthusiast.
Aspiring pianist. Former pilot.
Born at 320 ppm CO₂.
Решаю задачи на Leetcode, узнаю полезные мелочи, делюсь с вами.
Известное: встроенный zip "склеивает" два итератора в финальный с длиной равной самому короткому итератору:
Но я хочу в финальном результате "хвост" и от длинного итератора. А пустое место заполнить каким-то дефолтным значением. В itertools есть zip_longest с параметром fillvalue. Результат будет длиной самого длинного итератора, а значения, которых не хватает, заполнятся из fillvalue. Мне неясно, почему было не сделать это функционалом zip и почему это вытащили в itertools, но как есть (:
Известно: у списка есть метод insert(idx, value), который вставляет перед указанным индексом idx значение value:
Обнаружила, что insert может работать как append() в случае, если указанного индекса в списке ещё нет. Ожидала, что развалится с IndexError, но нет, просто вставляет значение в конец, очень круто:
Известное: встроенный zip "склеивает" два итератора в финальный с длиной равной самому короткому итератору:
>>> list(zip('aa', 'bbb'))
[('a', 'b'), ('a', 'b')]Но я хочу в финальном результате "хвост" и от длинного итератора. А пустое место заполнить каким-то дефолтным значением. В itertools есть zip_longest с параметром fillvalue. Результат будет длиной самого длинного итератора, а значения, которых не хватает, заполнятся из fillvalue. Мне неясно, почему было не сделать это функционалом zip и почему это вытащили в itertools, но как есть (:
>>> from itertools import zip_longest
>>> list(zip_longest('aa', 'bbb', fillvalue=''))
[('a', 'b'), ('a', 'b'), ('', 'b')]
Известно: у списка есть метод insert(idx, value), который вставляет перед указанным индексом idx значение value:
>>> l = [1, 2, 3]
>>> l.insert(0, 'a')
>>> l
['a', 1, 2, 3]
Обнаружила, что insert может работать как append() в случае, если указанного индекса в списке ещё нет. Ожидала, что развалится с IndexError, но нет, просто вставляет значение в конец, очень круто:
>>> l = []
>>> l.insert(0, 1)
>>> l
[1]
>>> l.insert(4, 10)
>>> l
[1, 10]
В модуле operator содержатся функции соответствующие операторам.
Например, operator.eq для == или operator.mul для умножения. Зачем? Чтобы не писать страшненькие лямбды (: Например, с помощью functools.reduce() я хочу получить результат перемножения всех элементов списка.
С лямбдой запись будет такая:
А с operator.mul такая:
Тут полный список соответствия операторов функциям.
Например, operator.eq для == или operator.mul для умножения. Зачем? Чтобы не писать страшненькие лямбды (: Например, с помощью functools.reduce() я хочу получить результат перемножения всех элементов списка.
С лямбдой запись будет такая:
>>> from functools import reduce
>>> reduce(lambda x, y: x * y, [1,2,3])
6А с operator.mul такая:
>>> from functools import reduce
>>> from operator import mul
>>> reduce(mul, [1,2,3])
6Тут полный список соответствия операторов функциям.
Какой чудесный способ выбрать min/max из словаря, когда нужно получить только ключ:
Я бы раньше делала через
>>> d = {'a': 20, 'b': 5, 'c': 30}
>>> min(d, key=d.get)
'b'
>>> max(d, key=d.get)
'c'Я бы раньше делала через
d.items() и key=lambda x: x[1] (% А тут очень красиво, лаконично, понятно.Начали терзать сомнения, а не будет ли обращение к словарю с помощью get() на каждый элемент сильно медленнее, чем сразу сконструировать список пар с помощью items() и пройтись по нему. Оказалось, что нет (в который раз рекламирую timeit, пользуйтесь им, он классный):
Лямбды такие медленные ): Поход гетом в словарь на каждом элементе быстрее вызова лямбды на каждый элемент в 2.5 раза.
Так что больше не терзаюсь.
~$ python -m timeit -s 'd = {i:i for i in range(100000)}' -p 'min(d.items(), key=lambda x: x[1])[0]'
50 loops, best of 5: 7.44 msec per loop
~$ python -m timeit -s 'd = {i:i for i in range(100000)}' -p 'min(d, key=d.get)'
100 loops, best of 5: 3.15 msec per loop
Лямбды такие медленные ): Поход гетом в словарь на каждом элементе быстрее вызова лямбды на каждый элемент в 2.5 раза.
Так что больше не терзаюсь.