Ранее, в посте о главных новшествах Python3, я упоминал про полный переход на unicode. А в конце было сказано:
"Конечно же это НЕ относится к именам переменных и файлов! Только строки и комменты."
На самом деле это было лишь предостережение. Можно создавать имена переменных и модулей на unicode!
То есть мы вполне можем сделать так:
Bash:
// создали файл с именем на кириллице
Или так
Или так
Или так
Надеюсь не нужно объяснять что смысла в этом ноль и так делать не стоит)))
Кстати, это была секретная информация, так что никому! Чтобы и в мыслях не было! 🤐
PS. я поддерживаю русский язык только в комментариях по коду. Конечно, если только вы уверены, что команда разработчиков будет русскоязычной и никак иначе! Это очень помогает разбираться в чужом коде (или в своём через время) Но это крайний случай.
#tricks
"Конечно же это НЕ относится к именам переменных и файлов! Только строки и комменты."
На самом деле это было лишь предостережение. Можно создавать имена переменных и модулей на unicode!
То есть мы вполне можем сделать так:
Bash:
$ echo "print('Приветы')" > моймодуль.py// создали файл с именем на кириллице
>>> import моймодуль
Приветы
Или так
>>> Василий = "Василий"
>>> Петрович = "Петрович"
>>> фулнейм = ' '.join([Василий, Петрович])
>>> print(фулнейм)
Василий Петрович
Или так
def сделать_красиво(было):
более_красиво = 100500
стало = было * более_красиво
return стало
Или так
>>> Ψ = 100
>>> Σ = -100
>>> смысл = Ψ + Σ
>>> print(смысл)
0
Надеюсь не нужно объяснять что смысла в этом ноль и так делать не стоит)))
Кстати, это была секретная информация, так что никому! Чтобы и в мыслях не было! 🤐
PS. я поддерживаю русский язык только в комментариях по коду. Конечно, если только вы уверены, что команда разработчиков будет русскоязычной и никак иначе! Это очень помогает разбираться в чужом коде (или в своём через время) Но это крайний случай.
#tricks
Иногда бывает ситуация когда dev-сервер по какой-либо причине не закрылся и висит в процессах, занимая порт.
Это может быть из-за падения IDE или просто сам забыл погасить и закрыл терминал.
Для таких случаев я набросал простую функцию с командой:
Если во время старта dev-сервера получаете ошибку что порт уже занят, просто выполните команду, подставив свой порт.
Bash
Имя команды можете изменить на любое другое.
#linux
Это может быть из-за падения IDE или просто сам забыл погасить и закрыл терминал.
Для таких случаев я набросал простую функцию с командой:
kill_on_port() {
port=$(lsof -t -i:$1)
echo "KILL PROCESS:" $port
sudo kill -9 $port
}
alias killonport="kill_on_port $@"
Код поместить в ~/.bashrc и рестартнуть систему.Если во время старта dev-сервера получаете ошибку что порт уже занят, просто выполните команду, подставив свой порт.
Bash
kill_on_port 8000Скорее всего бесполезно, если другой процесс назначен на перезапуск вашего dev-сервера в случае падения.
Имя команды можете изменить на любое другое.
#linux
Все знают как красиво написать дату и время с помощью библиотеки datetime:
1. Удобно писать более комплексные шаблоны
Чтобы не повторять аргумент dt делаем форматирование по индексу
Если используем разные даты, то можно сделать форматирование по имени
3. Зная про эту фишку, и что реализуется она с помощью magic-метода
Я набросал простой пример с таким поведением, смотрим в гистах 🔗.
>>> from datetime import datetimeНо мало кто знает, что тоже самое можно сделать и другим способом:
>>>
>>> dt = datetime.now()
>>> dt.strftime('%Y.%m.%d %H:%I')
'2020.01.08 12:00'
>>> "{:%Y.%m.%d %H:%I}".format(dt)
'2020.01.08 12:00'
Если паттерн хранится отдельно, то можно записать так>>> date_frmt = '%Y.%m.%d %H:%I'Хм, а чем это лучше, спросите вы?
>>> '{:{}}'.format(dt, date_frmt)
'2020.01.08 12:00'
1. Удобно писать более комплексные шаблоны
>>> event_name = 'Python-вебинар'(ну да, такие паттерны можно писать и в strftime, если что. Хотя, можете напороться на UnicodeEncodeError даже в Python3!)
>>> "Сегодня, {:%Y.%m.%d}, ровно в {:%H:%I} мы начинаем {}!".format(dt, dt, event_name)
'Сегодня, 2020.01.08, ровно в 12:00 мы начинаем Python-вебинар!'
Чтобы не повторять аргумент dt делаем форматирование по индексу
"{0:%Y} {0:%H.%I}".format(dt)Если используем разные даты, то можно сделать форматирование по имени
"{date1:%Y}-{date2:%Y} (current {date1:%Y})".format(date1=dt1, date2=dt2)
2. Форматирование даты находится непосредственно в тексте а не в отдельной переменной, что бывает удобней в некоторых случаях. Например, если текст хранится в базе данных то не нужно гдето отдельно хранить формат дат.3. Зная про эту фишку, и что реализуется она с помощью magic-метода
__format__можно начать писать свои классы с подобным функционалом! Собственно, в классе datetime этот метод просто вызывает уже знакомый метод strftime.
Я набросал простой пример с таким поведением, смотрим в гистах 🔗.
GitHub
python/cpython
The Python programming language. Contribute to python/cpython development by creating an account on GitHub.
Есть такая запись форматирования
Есть аналог для перемещения влево и по центру, но не об этом сейчас.
Что будет, если подать в такую строку кастомный класс?
Чтобы "конвертнуть" свой класс с помощью ˍˍstrˍˍ пишем так:
Только учтите, если не определить методы ˍˍstrˍˍ и ˍˍreprˍˍ то получится что-то типа:
PS. А если определить метод ˍˍformatˍˍ, то можно и другие буковки использовать для специфичных преобразований!
#tricks
>>> "{0:>10}".format(42)
" 42"
Что означает: сделать строку шириной 10 символов и переданный аргумент выровнять по правую сторону.Есть аналог для перемещения влево и по центру, но не об этом сейчас.
Что будет, если подать в такую строку кастомный класс?
>>> class MyClass:Упс, ошибка форматирования! То есть Python говорит что у нашего класса нет метода ˍˍformatˍˍ. Даже ˍˍstrˍˍ и ˍˍreprˍˍ не помогут.
>>> pass
>>> c = MyClass()
>>> "{0:>10}".format(c)
TypeError: unsupported format string passed to MyClass.__format__
>>> class MyClass:Что ж, это легко решается добавлением метода ˍˍformatˍˍ. Но а что если это не ваши классы и изменить исходники никак нельзя? Можно сделать небольшое преобразование, например такими способами:
>>> def __str__(self):
>>> return 'My Class'
>>> def __repr__(self):
>>> return '<My Class>'
>>> c = MyClass()
>>> "{0:>20}".format(c)
TypeError: unsupported format string passed to MyClass.__format__
>>> "{0:>20}".format(str(c))
" My Class"
>>> "{0:>20}".format(repr(c))
" <My Class>"
Но лучше использовать явный конвертор, то есть механизмы самого форматирования. В таком случае необходимое действия будет жёстко указано в самом паттерне.Чтобы "конвертнуть" свой класс с помощью ˍˍstrˍˍ пишем так:
"{0!s:>20}".format(c)
А чтобы преобразование сделалось с помощью ˍˍreprˍˍ, пишем так:"{0!r:>20}".format(c)
Теперь не важно какой класс прилетит, всё сработает!Только учтите, если не определить методы ˍˍstrˍˍ и ˍˍreprˍˍ то получится что-то типа:
"<MyClass object at 0x7f27a62ed278>"Но хотя бы не будет ошибки.
PS. А если определить метод ˍˍformatˍˍ, то можно и другие буковки использовать для специфичных преобразований!
#tricks
На заметку начинающим.
Оператор for..in имеет необязательный блок else.
Как это работает?
Этот блок выполняется только если итерация завершилась успешно, пройдя все элементы.
Чтобы блок else не выполнился, итерация должна прерваться с помощью break.
Зачем это нужно?
Предполагается, что такая конструкция нужна для определения значения по умолчанию, в случае если мы ищем нужное значение в цикле. Как только необходимые данные найдены, выходим из цикла с помощью break. Если ничего не нашли, то выполняется блок else, в котором выполняем альтернативные действия.
Чем полезно?
В целом, пишется более лаконично (читай питонично)
Позволяет сократить время вычислений, если получение дефолта достаточно затратная процедура. Например, есть такой код:
Но что, если функция get_default() занимает слишком много времени или не должна вызываться просто так? Тогда написать можно иначе:
#tricks
Оператор for..in имеет необязательный блок else.
Как это работает?
Этот блок выполняется только если итерация завершилась успешно, пройдя все элементы.
Чтобы блок else не выполнился, итерация должна прерваться с помощью break.
Зачем это нужно?
Предполагается, что такая конструкция нужна для определения значения по умолчанию, в случае если мы ищем нужное значение в цикле. Как только необходимые данные найдены, выходим из цикла с помощью break. Если ничего не нашли, то выполняется блок else, в котором выполняем альтернативные действия.
Чем полезно?
В целом, пишется более лаконично (читай питонично)
Позволяет сократить время вычислений, если получение дефолта достаточно затратная процедура. Например, есть такой код:
value = get_default()В этом примере мы сначала создаем значение по умолчанию, потом в итерации получаем нужный параметр. Если get_new_value() не вернула новое значение, блок if никогда не сработает и остается значение по умолчанию.
for i in array:
res = get_new_value(i)
if res:
value = res
break
Но что, если функция get_default() занимает слишком много времени или не должна вызываться просто так? Тогда написать можно иначе:
value = NoneА с блоком else можно записать короче
for i in array:
res = get_new_value(i)
if res:
value = res
break
if value is None:
value = get_default()
for i in array:А тех кто на Python3.8+, еще короче:
res = get_new_value(i)
if res:
value = res
break
else:
value = get_default()
for i in array:Вероятно, можно такую логику записать еще короче, но это уже другая история
if (res:=get_new_value(i)):
value = res
break
else:
value = get_default()
#tricks
Аналогично конструкции for-else есть конструкция while-else
Как работает?
Блок while перед каждой итерацией проверяет условие. Если оно верно, то выполняется блок цикла. Если нет, то выполняется блок else, после которого выход из итерации.
Так это обычно выглядит в виде простой и понятной записи:
А вот так в сокращенном виде
#tricks
Как работает?
Блок while перед каждой итерацией проверяет условие. Если оно верно, то выполняется блок цикла. Если нет, то выполняется блок else, после которого выход из итерации.
Так это обычно выглядит в виде простой и понятной записи:
while True:
if [condition]:
# здесь тело цикла
else:
# здесь выполняем код перед выходом
break
# выходим из цикла
Почему мы не поместили кондишен непосредственно после while? Чтобы перехватить его изменение и выполнить что-то еще, используя дополнительные условия.А вот так в сокращенном виде
while [condition]:Стоит уточнить, что если в теле цикла вызвать break, то блок else не сработает.
# здесь тело цикла
else:
# здесь выполняем код перед выходом
#tricks
Не удержался и решил показать пример как можно записать код из поста про if-else еще короче, буквально в одну строку!
1.Эти операторы не возвращают тип bool
Они только делают преобразование данных в этот тип на момент сравнения.
2. Как именно устроена логика? В какой момент сработает return?
any() - возвращает первый элемент, который преобразовался в True или последний элемент
all() - возвращает первый элемент, который преобразовался в False или последний элемент
or и and делают аналогично, только для двух операндов
Но все они возвращают именно исходное значение, а не bool!
Ну а наличие генератора позволяет не выполнять лишних операций, только до момента нахождения нужного значения.
Если значение так и не найдено, оператор or запустит функцию get_default().
PS. Несмотря на возможность записать таким образом, я призываю всегда отдавать предпочтение понятности кода, а не краткости. Если про понятность не понятно, делаем
#tricks
value = any((get_new_value(i) for i in array)) or get_default()Тут стоит знать две особенности функций any/all и операторов or/and.
1.Эти операторы не возвращают тип bool
Они только делают преобразование данных в этот тип на момент сравнения.
2. Как именно устроена логика? В какой момент сработает return?
any() - возвращает первый элемент, который преобразовался в True или последний элемент
all() - возвращает первый элемент, который преобразовался в False или последний элемент
or и and делают аналогично, только для двух операндов
Но все они возвращают именно исходное значение, а не bool!
Ну а наличие генератора позволяет не выполнять лишних операций, только до момента нахождения нужного значения.
Если значение так и не найдено, оператор or запустит функцию get_default().
PS. Несмотря на возможность записать таким образом, я призываю всегда отдавать предпочтение понятности кода, а не краткости. Если про понятность не понятно, делаем
import this и читаем, читаем, читаем...#tricks
Признавайтесь, кто так пишет?
В данном случае оператор not и оператор in не связаны. То есть всё, что делает оператор not, это инвертирование значения, идущего после него, причём результатом будет bool. А после мы проверяем, есть ли этот bool в списке array??? Неожиданный поворот!
Выглядеть это может так:
Но стоп, почему же тогда никаких проблем с этой записью нет и ОНО РАБОТАЕТ в таком виде!?
Дело в том, что я намеренно перечислил операторы в порядке их написания (чтобы страшней было). Хотя, на самом деле, оператор in имеет бОльший приоритет. В этом примере мы сначала проверяем наличие элемента в списке (вместо проверки "отсутствия") а потом инвертируем результат.
Да, получаем ровно тот же результат (считай повезло), но в два действия. Специально для таких случаев есть оператор not in, то есть более "питонично" писать так:
Понимайте то, что кодите!
#tricks
if not value in array:По задумке, мы пытаемся проверить, отсутствует ли значение value в списке array. Звучит логично, но!
...
В данном случае оператор not и оператор in не связаны. То есть всё, что делает оператор not, это инвертирование значения, идущего после него, причём результатом будет bool. А после мы проверяем, есть ли этот bool в списке array??? Неожиданный поворот!
Выглядеть это может так:
if (not value) in array:
...
Но стоп, почему же тогда никаких проблем с этой записью нет и ОНО РАБОТАЕТ в таком виде!?
Дело в том, что я намеренно перечислил операторы в порядке их написания (чтобы страшней было). Хотя, на самом деле, оператор in имеет бОльший приоритет. В этом примере мы сначала проверяем наличие элемента в списке (вместо проверки "отсутствия") а потом инвертируем результат.
if not (value in array):
...
Да, получаем ровно тот же результат (считай повезло), но в два действия. Специально для таких случаев есть оператор not in, то есть более "питонично" писать так:
if value not in array:Это будет выполняться в одно действие, так как оператор один (несмотря не мизерные затраты времени оператора not).
...
Понимайте то, что кодите!
#tricks
👍1
Нужно записать большое число? Обычно пишем так
Такие числа можно записать более кратко или понятно.
1. Экспонента
2. Использовать разделитель
Есть специальный синтаксис для записи длинных чисел, используем для разделителя символ подчёркивания.
for i in range(300000000):И сколько там нулей? Попробуй посчитать...
execute_test(i)
Такие числа можно записать более кратко или понятно.
1. Экспонента
for i in range(3e9):Запись
execute_test(i)
XeY означает X*(10**Y) # оператор ** это степеньЕсли нужно не ровное число с нулями, то можно добавить любое уточняющее действие
>>> 4e10+1234Экспонента может быть отрицательной, тогда нулики добавляем справа от точки
40000001234.0
>>> 15e5-42
1499958.0
>>> 7e-5+1Такая запись не создаёт другой тип данных, это просто формат записи
1.00007
2. Использовать разделитель
Есть специальный синтаксис для записи длинных чисел, используем для разделителя символ подчёркивания.
for i in range(300_000_000):Сразу видны элементы числа! Таким способом можно записать любое число.
execute_test(i)
>>>143_435_543.123_000_2Кстати, наверняка замечали что иногда Python сам выводит числа в виде экспоненты, если это возможно
143435543.1230002
>>> 0.000_000_1
1e-07
#tricksДопустим, есть некий массив
В следующем коде круглыми скобками показаны части разных итераций, чтобы было понятно чо во что входит.
Код не для исполнения!
Всегда помните:
Простое лучше чем сложное (Simple is better than complex).
И на последок пример:
Это хороший пример того, как не стоит усложнять читаемость кода.
PS. Да, "генераторы" в Python это другая сущность, но как-то иначе удачно перевести comprehension не получается. Ну не называть же их "пониматоры" списков)
PPS. Для получения множества комбинаций лучше используйте itertools.combinations
#tricks
a = [1, 2, 3]
Как мы переберём все элементы? Это очевидно.for x in a:
print(x)
Как это сделать с помощью генератора списка (list comprehension)?r = [x for x in a]
Добавим еще один массивb = 'abc'
Теперь нам нужно перебрать все возможные сочетания из этих двух списков. Тоже не проблема:for x in a:
for y in b:
print(x, y)
Можно ли такой алгоритм повторить с помощью генератора списка? На самом деле можно>>> [[x, y] for x in a for y in b]
[[1, 'a'], [1, 'b'], [1, 'c'], [2, 'a'], ...]
А если добавить еще один список?>>> c = ('yes', 'no', 'maybe')
>>> r = [[x, y, z] for x in a for y in b for z in c]
[[1, 'a', 'yes'], [1, 'a', 'no'], [1, 'a', 'maybe'], ...]
Получаем список списков, в которых есть все варианты комбинаций по 3 элемента.В следующем коде круглыми скобками показаны части разных итераций, чтобы было понятно чо во что входит.
Код не для исполнения!
[( ( ([x, y, z] for x in a) for y in b) for z in c)]
Генераторы списков это круто, но когда используются без фанатизма! Всегда помните:
Простое лучше чем сложное (Simple is better than complex).
И на последок пример:
vectors = [[[1, 2, 3], [4, 5, 6], [7, 8, 9]], [[10, 11, 12], [13, 14, 15]]]
print([digit for part in vectors for elem in part for digit in elem])
Попробуйте понять что там происходит и что распечатает этот код 😭😵🤪Это хороший пример того, как не стоит усложнять читаемость кода.
PS. Да, "генераторы" в Python это другая сущность, но как-то иначе удачно перевести comprehension не получается. Ну не называть же их "пониматоры" списков)
PPS. Для получения множества комбинаций лучше используйте itertools.combinations
#tricks
Как с помощью Python быстро расшарить файлы в локальную сеть?
Если без заморочек, то очень просто!
Заходим в нужную директорию и выполняем команду:
Shell:
Теперь остаётся заменить этот IP на свой локальный адрес.
Чтобы его узнать, можете поискать во всяких окошках, но это не наш метод 😎
Выполняем команду:
Windows:
Ищем что-то вроде 192.168.1.100
Для тех, кто чтит Python-way
Linux/Windows
Q: Не работает что-то :(
A: Проверь настройки сети, может фаервол?
Q: А как прервать передачу файлов?
A: Ctrl+C
Q: Смогут ли люди из интернета зайти на мой минисервер?
A: Если ты не на публичном сервере с белым IP то нет.
Q: А можно поставить свой "секретный" порт вместо порта по умолчанию?
A: Можно:
#tricks
Если без заморочек, то очень просто!
Заходим в нужную директорию и выполняем команду:
Shell:
$ python -m http.serverПолучаем ответ
Serving HTTP on 0.0.0.0 port 8000 (https://0.0.0.0:8000/) ...Значит, что сервер запущен!
0.0.0.0 - означает, что могут зайти все кто сможет "достучаться" до вашего компьютера.Теперь остаётся заменить этот IP на свой локальный адрес.
Чтобы его узнать, можете поискать во всяких окошках, но это не наш метод 😎
Выполняем команду:
Windows:
> ipconfigLinux:
$ ifconfigВ полученной распечатке ищем нужное сетевое устройство и его адрес.
Ищем что-то вроде 192.168.1.100
Для тех, кто чтит Python-way
Linux/Windows
> python -c "import socket;print(socket.gethostbyname(socket.gethostname()))"Теперь формируем и отправляем полученный адрес кому требуется:
https://192.168.1.100:8000/Небольшой FAQ
Q: Не работает что-то :(
A: Проверь настройки сети, может фаервол?
Q: А как прервать передачу файлов?
A: Ctrl+C
Q: Смогут ли люди из интернета зайти на мой минисервер?
A: Если ты не на публичном сервере с белым IP то нет.
Q: А можно поставить свой "секретный" порт вместо порта по умолчанию?
A: Можно:
python -m http.server 12345
А еще можно посмотреть справку чтобы узнать другие параметры запуска$ python -m http.server -hЕсть способ раздать и "наружу" в интернет, но об этом в другой раз.
#tricks
Почему нет функции, аналога для f-string?
На самом деле, f-string можно представить в виде простой функции format() в которую вместе со стркой-шаблоном передаются словари locals() и globals(). Но так делать точно не стОит! И прежде всего, в целях безопасности.
О чём это я?
Допустим, у нас есть шаблон:
Допустим, у нас некий веб сервис и мы предоставляем юзеру возможность сформировать себе любой шаблон какой ему нравится, после чего форматируем строку по аналогии с f-string.
В чем же тут может быть опасность? А в том, что в отличие от простого метода строки format() этот способ имеет возможность вшить в шаблон любой экспрешн! То есть, мы позволяем юзеру написать любой код и самолично его выполняем на сервере!
Если юзер действует по правилам, то напишет что-то вроде:
Можно и более кардинально:
Теперь можно заходить на сайт как админ и делать там что хочешь)))
Поэтому, только программист может формировать подобные строки. То есть нельзя в f-sting передать неизвестно что из переменной.
Никогда не давайте юзерам слишком много свободы.
Если требуется подобный функционал, лучше использовать простой метод format() или класс 'string.Template'. А ещё лучше, выдавать список готовых вариантов 🤓
PS. Для тех кто хочет поэкспериментировать с f-string функцией, можете попробовать этот вариант
#tricks
На самом деле, f-string можно представить в виде простой функции format() в которую вместе со стркой-шаблоном передаются словари locals() и globals(). Но так делать точно не стОит! И прежде всего, в целях безопасности.
О чём это я?
Допустим, у нас есть шаблон:
template = 'Hello {username}!'
И потом мы просто форматируем строку по этому шаблону псевдо-функцией fstring() (представим, что она существует) username = user.get_name()
greeting = fstring(template)
Выглядит всё логично, но тут есть скрытая угроза безопасности! Допустим, у нас некий веб сервис и мы предоставляем юзеру возможность сформировать себе любой шаблон какой ему нравится, после чего форматируем строку по аналогии с f-string.
В чем же тут может быть опасность? А в том, что в отличие от простого метода строки format() этот способ имеет возможность вшить в шаблон любой экспрешн! То есть, мы позволяем юзеру написать любой код и самолично его выполняем на сервере!
Если юзер действует по правилам, то напишет что-то вроде:
"Hey what's up, {username}?)))"
А если попадётся слишком догадливый, знающий о некомпетентности программиста и, допустим, что логика на Django+Python, то он может написать что-то такое:"{__import__('django.conf', fromlist=['conf']).settings.SECRET_KEY}"
И алгоритм выдаст ему секретный ключ сайта!Можно и более кардинально:
"{__import__('django.contrib.auth', fromlist=['auth']).get_user_model().objects.filter(email='[email protected]').update(is_superuser=True)}"
Этот однострочный экспрешн делает юзера суперадмином!Теперь можно заходить на сайт как админ и делать там что хочешь)))
Поэтому, только программист может формировать подобные строки. То есть нельзя в f-sting передать неизвестно что из переменной.
Никогда не давайте юзерам слишком много свободы.
Если требуется подобный функционал, лучше использовать простой метод format() или класс 'string.Template'. А ещё лучше, выдавать список готовых вариантов 🤓
PS. Для тех кто хочет поэкспериментировать с f-string функцией, можете попробовать этот вариант
def fstring(fstring_text):
return eval(f'f"{fstring_text}"', locals(), globals())
Пример:>>> template = 'Hello, {username}!'
>>> username = 'Max'
>>> fstring(template)
Hello, Max!
Теперь попробуйте придумать хитрые способы взлома)#tricks
В Python2 был необычный способ записать текст в файл с помощью print
Хорошо, что в Python3 этот синтаксис более неактуален. Теперь запись в файл с помощью print выглядит куда более логично:
#2to3
>>> print >> open(path, 'w'), 'some text'
Те, кто часто пишет на Python, заметят как не "питонично" выглядит такая запись. Просто какой-то разрыв шаблонов.Хорошо, что в Python3 этот синтаксис более неактуален. Теперь запись в файл с помощью print выглядит куда более логично:
>>> print('some text', file=open(path, 'w'))
Хотя, я пока не встречал никого кто пишет что-то в файл с помощью print )))#2to3
Кто-то в вашей компании любит "брейкпринты" ???
(это такой способ "дебажить" 🐞 код с помощью функции print())
Проблема такого подхода состоит в том, что для просмотра output нужно лично присутствовать у монитора. Более нигде эта информация не записывается и пропадает после закрытия приложения.
Если присутствие невозможно, то приходится искать обходные пути.
Кроме переписывания кода, записи консоли на видео и нарезки серии скриншотов есть и более хитрый способ. Например, глобально перенаправить вывод функции print() в файл в сети (или потом попросить прислать).
В стандартной библиотеке уже есть подходящее решение:
🔸1. Следует заведомо позаботиться, чтобы путь к файлу был сетевой.
🔸2. Убедитесь что есть доступ на запись файла
🔸3. Эту обёртку нужно делать в самом начале выполнения скрипта, во время вызова основной функции (например main()).
🔸4. Научите коллег пользоваться логгингом!
🔸5. Проследите, что коллеги изучили и используют логгинг.
На самом деле вместо файла можно указать любой другой аналогичный объект, например отправка в сеть, но это уже другая история
#tricks
(это такой способ "дебажить" 🐞 код с помощью функции 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
Заметка начинающим, которые часто сталкиваются с подобной непоняткой.
Ситуация следующая, есть список файлов:
Не знаю зачем, пример довольно надуманный) Но суть он показывает, а это главное.
Те, кто еще не очень знаком с библиотекой os.path или pathlib, вероятно решат обработать имена как строки. И тут вполне подойдет метод строки strip().
Что делает этот метод? Он отрезает указанные символы по обеим сторонам строки. Если ничего не указать, то убирает невидимые символы (пробелы, табуляции и переносы строк).
В нашем случае будет выглядеть вот так:
Можно применить аналогичный метод rstrip(), чтобы отрезать только справа, но для этого примера используем обычный.
А дело всё в том, что данный метод ищет не указанную строку, а указанные символы, и не важно в каком порядке.
Для метода strip() строка '.bkp' это не паттерн для поиска а список символов. Потому он отрезал симовол 'p' от '.bmp' и удалил точку из файла '.config.cfg'.
Как тогда правильно заменить именно паттерн? Для начинающего можно посоветовать метод строки replace(), который как раз использует для замены указанную строку целиком. В нашем примере заменим её на пустую строку.
Просто впредь будьте внимательны с этим strip().
#basic
Ситуация следующая, есть список файлов:
names = [
'image.bmp',
'second.txt.bkp',
'data.db',
'.config.cfg',
'file.ext.bkp'
]
И мы хотим убрать у них окончание ".bkp".Не знаю зачем, пример довольно надуманный) Но суть он показывает, а это главное.
Те, кто еще не очень знаком с библиотекой os.path или pathlib, вероятно решат обработать имена как строки. И тут вполне подойдет метод строки strip().
Что делает этот метод? Он отрезает указанные символы по обеим сторонам строки. Если ничего не указать, то убирает невидимые символы (пробелы, табуляции и переносы строк).
В нашем случае будет выглядеть вот так:
>>> name.strip('.bkp')
То есть просим удалить строку '.bkp' по краям имени файла, если таковая есть. Можно применить аналогичный метод rstrip(), чтобы отрезать только справа, но для этого примера используем обычный.
>>> for name in names:
>>> print(name.strip('.bkp'))
image.bm
second.txt
data.d
config.cfg
file.ext
Хм, что-то не то с нашими именами! Что случилось??? Видим нежелательное переименование в именах, где и близко не было указанной строки '.bkp'А дело всё в том, что данный метод ищет не указанную строку, а указанные символы, и не важно в каком порядке.
Для метода strip() строка '.bkp' это не паттерн для поиска а список символов. Потому он отрезал симовол 'p' от '.bmp' и удалил точку из файла '.config.cfg'.
Как тогда правильно заменить именно паттерн? Для начинающего можно посоветовать метод строки replace(), который как раз использует для замены указанную строку целиком. В нашем примере заменим её на пустую строку.
>>> for name in names:
>>> print(name.replace('.bkp', ''))
image.bmp
second.txt
data.db
.config.cfg
file.ext
Уже лучше, но помните, это лишь пример про strip(). Для работы с именами файлов есть способы и более "правильные", дающие однозначно верный результат. Я взял файлы только в качестве примера. Даже replase() тут может сделать не то что ожидаем. Просто впредь будьте внимательны с этим strip().
#basic
Чем вы измеряете время?
Обычно, когда мы хотим измерить время выполнения функции, мы пишем так:
Дело в том, что метод time() возвращает системное время. Допустим, мы начали замер времени, сохранив текущее время. А во время выполнения функции кто-то зашел и переставил системные часы на час назад (например автоматическая синхронизация времени или переход на зимнее\летнее время). Когда функция завершится, мы вполне можем получить отрицательное время!
Это очевидный фейл. Поэтому, для таких случаев есть специальный метод time.monotonic(). В описании метода ясно написано, что это время не может идти назад, так как это относительное время. Именно этот метод будет делать правильные изменения.
Но самый правильный способ замера производительности это метод time.perf_counter(). Он даёт максимально возможный точный замер времени (меньше наносекунды). Полезно для профайлинга очень быстрых функций.
Итого, наш тест будет выглядеть так:
Про эти функции можно почитать в PEP418
Возможно, кто-то привык использовать время метод time.clock(), то есть время работы программы с момента старта. Учтите, что этот метод устарел и начиная с Python 3.8 будет удалён.
#tricks
Обычно, когда мы хотим измерить время выполнения функции, мы пишем так:
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
Python.org
PEP 418 -- Add monotonic time, performance counter, and process time functions
The official home of the Python Programming Language
Кеширование, это способ оптимизировать скорость программы за счёт повторного переиспользования рассчитанных данных. При этом инвалидация кеша вопрос сложный и неоднозначный. Тем не менее, есть ряд простых случаев, когда кеш вполне уместен.
Имеется функция, которая принимает аргументы и возвращает некое значение. При одинаковых аргументах результат всегда одинаковый.
Давайте приведём такой очевидный пример:
Для этого используем готовое решение из стандартной библиотеки functools.lru_cache() (Python3.4+)
Теперь вызываем функцию с замером времени. Используем массив с повторяющимися значениями
Такое кеширование будет неверным, если результат зависит не только от входящих данных. Учитывайте это!
#tricks
Имеется функция, которая принимает аргументы и возвращает некое значение. При одинаковых аргументах результат всегда одинаковый.
Давайте приведём такой очевидный пример:
def add(a, b):Каждому понятно, что, если в эту функцию мы будем отправлять одни и те же данные, мы будем получать одинаковый результат, это важно. Тогда зачем нам каждый раз это пересчитывать? Давайте кешировать!
return a + b
Для этого используем готовое решение из стандартной библиотеки functools.lru_cache() (Python3.4+)
from functools import lru_cacheДобавил задержку чтобы имитировать расчёты. Параметр декоратора maxsize указывает сколько именно разных пар аргументы-результат мы будем хранить.
import time
@lru_cache(maxsize=64)
def add(a, b):
time.sleep(1)
return a + b
Теперь вызываем функцию с замером времени. Используем массив с повторяющимися значениями
>>> 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
В предыдущем посте⬆️ был пример кеширования функции. В стандартной библиотеке есть еще один способ, но для классов.
Это functools.cached_property (Python3.8+). Логика работы точно такая же, но:
- он предназначен только для метода класса.
- аналогичен декоратору property, то есть мы получим не функцию а свойство класса.
- не принимает аргументов (кроме self), так же как и property.
- кеш сохраняет в атрибутах класса (внутри ˍˍdictˍˍ).
Эдакий частный случай lru_cache для класса. В результате, вместо такой записи
#tricks
Это functools.cached_property (Python3.8+). Логика работы точно такая же, но:
- он предназначен только для метода класса.
- аналогичен декоратору property, то есть мы получим не функцию а свойство класса.
- не принимает аргументов (кроме self), так же как и property.
- кеш сохраняет в атрибутах класса (внутри ˍˍdictˍˍ).
Эдакий частный случай lru_cache для класса. В результате, вместо такой записи
class MyClass:
@property
@functools.lru_cache(maxsize=1)
def value(self):
return 123
мы можем записать более красиво и адаптированно для propertyfrom functools import cached_property
class MyClass:
@cached_property
def value(self):
return 123
Помимо более логичной записи, этот декоратор решает еще ряд проблем, возникающих при декорирвовании свойств классов через lru_cache. Так что в таких случаях его использование не то чтобы желательно, а обязательно!#tricks
Telegram
Python Заметки
Кеширование, это способ оптимизировать скорость программы за счёт повторного переиспользования рассчитанных данных. При этом инвалидация кеша вопрос сложный и неоднозначный. Тем не менее, есть ряд простых случаев, когда кеш вполне уместен.
Имеется функция…
Имеется функция…
👍1
Правильно ли вы используете аргумент shell у методов subprocess?
Вкратце опишу разницу состояний этого аргумента.
(полный разбор — тема для статьи в блоге, возможно позже)
Флаг "shell" определяет, будет ли использоваться системный шел как основной исполняемый файл для вызова вашей команды.
⏩ shell=True
- К вашей команде добавится исполняемый файл /bin/sh или cmd.exe
- Ваша команда будет аргументом флага -с, поэтому команду нужно передавать строкой
- Команду необходимо передавать с готовым экранированием и лексическим разбором пробелов.
✅ Правильно:
❌ Неправильно
⏩ shell=False
- команду нужно передать списком
- ожидается, что первым аргументом будет исполняемый файл
- будет запущен непосредственно файл из первого аргумента, без /bin/sh или cmd.exe
- автоматическое экранирование пробелов в аргументах списка
✅ Правильно
То есть система пытается найти файл "ls -sl" а не файл "ls"
А также, если не используется
___________________
- для Windows всё аналогично
- для других методов из subprocess всё аналогично
#tricks #libs
Вкратце опишу разницу состояний этого аргумента.
(полный разбор — тема для статьи в блоге, возможно позже)
Флаг "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
Дополнение к посту про 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