Python Заметки
2.31K subscribers
58 photos
2 videos
2 files
212 links
Интересные заметки и обучающие материалы по Python

Контакт: @paulwinex

⚠️ Рекламу на канале не делаю!⚠️

Хештеги для поиска:
#tricks
#libs
#pep
#basic
#regex
#qt
#django
#2to3
#source
#offtop
Download Telegram
Теперь запакуем строку.
В этом случае следует передавать тип данных bytes.

>>> struct.pack('=s', b'a')
b'a'

Для записи слова следует указывать количество символов.

>>> struct.pack('=5s', b'hello')
b'hello'

Кстати, запакованный вид соответствует исходному тексту. Всё верно, символ есть в таблице ASCII, то есть его код попадает в диапазон 0-127, он может быть записан одним байтом и имеет визуальное представление. А вот что будет если добавить символ вне ASCII

>>> struct.pack(f'=s', b'ё')
SyntaxError: bytes can only contain ASCII literal characters.

Ошибка возникла еще на этапе создания объекта bytes, который не может содержать такой символ. Поэтому надо кодировать эти байты из строки.

>>> enc = 'ёжик'.encode('utf-8')
>>> struct.pack(f'={len(enc)}s', enc)
b'\xd1\x91\xd0\xb6\xd0\xb8\xd0\xba'

Заметьте, длина такой строки в байтах отличается от исходной длины, так как символы вне ASCII записываются двумя байтами и более. Поэтому здесь формат создаём на лету, используя получившуюся длину как каунтер токена.

#libs #basic
Тип строки в Python имеет очень много удобных методов. Сегодня пост про два таких метода которые чаще всего используются "однобоко". Это методы startswith() и endswith()

Самый обычный сценарий использования — проверка, начинается ли строка с указанной подстроки?

>>> "some_string".startswith("some")
True

И аналогичная ситуация с зеркальным вариантом этой функции, проверка совпадения с конца

>>> "some_string".endswith("some")
False

Так они используются в большинстве случаев что я видел. Но у этих функций есть еще два варианта использования.

🔸 Сравнение нескольких подстрок
Для проверки нескольких подстрок в одной строке обычно вызывают эти функции несколько раз. Но на самом деле достаточно передать кортеж со всеми строками один раз. Если будет хоть одно совпадение то функция вернёт True.

>>>"my_image.png".endswith(("jpg", "png", "exr"))
True

🔸 Диапазон поиска
Вторым аргументом можно передать индекс символа с которого следует начать сравнение, а третий аргумент это индекс последнего символа.

>>> ".filename.ext".startswith("file", 1)
True
>>> "file_###.ext".endswith('#', 0, -4)
True

Индексы можно указать отрицательными, что означает отсчёт с конца.

#trics #basic
Модуль ensurepip, стал стандартным начиная с версии 3.4 и портирован в 2.7

Это встроенная альтернатива файлу get-pip.py. Модуль позволяет установить или обновить pip.

🔸Установка pip:

python -m ensurepip

🔸Обновление до актуальной версии

python -m ensurepip --upgrade

🔸Установка в директорию юзера, если вас не устраивает системный или просто нет доступа для обновления (когда не используем venv, то есть ставим глобально)

python -m ensurepip --user

#libs #basic
­Возможно, стоит пояснить разницу между синхронизацией из thread/process-safe и синхронизацией с помощью Lock 🤔

Наша задача — заставить разные процессы и потоки обращаться к базе данных (или любым другим ресурсам) последовательно. Чтобы не случилось так называемого race condition, то есть состояние гонки. Это когда разные потоки или процессы пытаются одновременно что-то сделать с одним и тем же ресурсом.
В этом случае нам нужна какая-то логика ограничения. Пока один процесс не завершил своё действие, другие не могут получить доступ к ресурсу.

Так вот, thread-safe и process-safe означает что отдельно взятые операции записи в БД гарантированно будут последовательны. Запросы из разных процессов или потоков выстроятся в очередь и не будут мешать друг другу. Лучше всего когда этот блок реализован на уровне БД в виде атомарных операций или ещё как-то.

Но зачем нам тогда еще дополнительный Lock?
Этот способ синхронизации используется когда процесс никак не укладывается в одно действие и должен сделать множество операций прежде чем дать доступ следующему. В этом случае процесс ставит некий глобальный Lock на ресурс и никто другой, даже получив законное право на доступ, не может ничего сделать. Все ждут пока этот Lock не будет снят.
Это решается на уровне приложения и правильность реализации полностью в вашей ответственности. Например, если забыли разблокировать или сделали перекрёстный Lock (Deadlock как на картинке), то всё зависнет в бесконечном ожидании.

#basic
В стандартном модуле random есть две очень похожие функции

random.randint()
random.randrange()

Обе возвращают случайное значение из указанного диапазона

>>> random.randint(10, 20)
12
>>> random.randrange(10, 20)
17

В чем же отличие?
Дело в том что у randrange() есть третий параметр step.

randint() действительно возвращает случайное число из указанного диапазона.
randrange() на первый взгляд делает тоже самое если передать также два параметра.
Но есть указать еще и step то наш диапазон усложняется, то есть в него попадёт не полный ряд значений.

Например, я хочу получить случайное значение из диапазона но только чётное число. Тогда достаточно сделать так:

>>> randrange(10, 20, 2)
16

Таким образом получается что randint это частный случай randrange без указания параметра step.

Еще одно важное отличие в том, что randint() включает в диапазон второе значение а randrange() нет. То есть выражение randrange(10, 20) никогда не вернёт 20, а randint(10, 20) вернёт.

#tricks #basic
Что-то вы гоните насчет "привычного вида формата 755 и 644". Я вот вообще не понял что это! 😳

Действительно, что означают цифры которые мы получили в прошлом посте?

Это кодировка, заключающая в себе режимы доступа к файлу.
Подробней можно почитать в статье про chmod.
Там можно увидеть альтернативное обозначение того же самого с помощью символов r w x, что значит чтение, запись, исполнение.

Чтобы преобразовать восьмеричное число в такое обозначение в Python есть готовая функция

>>> stat.filemode(0o755)
'?rwxr-xr-x'

Мы видим 3 группы по 3 символа, дающие 3 типа доступа для 3 типов юзеров.

А что за знак вопроса в начале?

Давайте передадим в эту функцию необрезанное значение от os.stat

>>> stat.filemode(os.stat(path).st_mode)
'drwxr-xr-x'

Это данные, которые мы безжалостно обрезали в прошлый раз😼
Первый символ обозначает тип объекта. Это может быть файл (-), директория (d) или симлинк (l).

Вот простая схема данной кодировки

[1][3][3][3]
│ │ │ │
│ │ │ └──> Others Permissions
│ │ └─────> Group Permissions
│ └────────> Owner Permissions
└───────────> File Type

(разверните экран если вы с телефона)

Если вы попробуете получить пермишены для симлинка то получите пермишены для файла

>>> path = '.venv/bin/python3'
>>> stat.filemode(os.stat(path).st_mode)
'-rwxr-xr-x'

Чтобы получить свойства именно симлинка, нужно это явно указать

>>> stat.filemode(os.stat(path, follow_symlinks=False).st_mode)
'lrwxrwxrwx'

#tricks #basic
Наверняка вы замечали, что в Python есть удобная функция для получения переменной окружения

os.getenv(NAME)

И её "сестра" для создания или изменения переменных окружения

os.putenv(NAME, VALUE)

Но почему-то putenv() не работает как должно. Энвайромент не обновляется!

os.putenv('MYVAR', '1')
print(os.getenv('MYVAR'))

... и ничего 😴

Почему так?

На самом деле энвайромент обновляется, но это значение не добавляется в словарь os.environ.
Откройте исходник функции os.getenv(). Это просто шорткат для os.environ.get()
В то время как putenv() это built-in С-функция.

Словарь os.environ (или точней класс из MutableMapping) создаётся из энвайромента в момент инициализации. Функция putenv() самостоятельно его не изменяет.

В тоже время, когда вы создаёте или изменяете ключ в os.environ, автоматически вызывается putenv() в методе __setitem__().

То есть, технически putenv() всё делает верно, но в os.environ это не отражается. Можно проверить так:

>>> os.putenv('MYVAR', '123')
>>> os.system('python -c "import os;print(os.getenv(\'MYVAR\'))"')
123

Я объявил переменную в текущем процессе и вызвал дочерний процесс, который её унаследовал и получил в составе os.environ.

Аналогично при удалении переменной вызывается еще одна built-in функция unsetenv(), удаляющая переменную из системы.

Итого

▫️ Удобней всего явно обновлять переменные через os.environ

▫️ Есть способ неявно создать/удалить переменную через putenv/unsetenv, что не повлияет на os.environ но изменит энвайромент и передаст изменения сабпроцессам. Но так лучше не делать!

▫️ os.environ это просто обертка для built-in функций putenv() и unsetenv().

#basic
👍2
Метод строки split() разделяет строку на несколько строк по указанному символу

>>> "a_b_c".split('_')
['a', 'b', 'c']

Можно указать максимальное количество разделений

>>> "a_b_c".split('_', 1)
['a', 'b_c']

Или резать с другой стороны с помощью rsplit() (right split)

>>> "a_b_c".rsplit('_', 1)
['a_b', 'c']

А что будет если оставить аргументы пустыми?

>>> "a_b_c".split()
['a_b_c']

Получаем список с одним элементом, потому что по умолчанию используется пробельный символ.

>>> "a b c".split()
['a', 'b', 'c']

То есть это равнозначно такому вызову?

>>> "a b c".split(" ")
['a', 'b', 'c']

Кажется да, но нет! Давайте попробуем добавить пробелов между буквами

>>> "a   b   c".split(" ")
['a', '', '', 'b', '', '', 'c']

И вот картина уже не так предсказуема 😕
А вот что будет по умолчанию

>>> "a   b   c".split()
['a', 'b', 'c']

Всё снова красиво! 🤩

По умолчанию в качестве разделителя используется любой пробельный символ, будь то табуляция или новая строка. Включая несколько таких символов идущих подряд. А также игнорируются пробельные символы по краям строки.

>>> "a\t  b\n c  ".split()
['a', 'b', 'c']

Аналогичный способ можно собрать с помощью регулярного выражения. Но пробелы по краям строки придется обрабатывать дополнительно.

>>> import re
>>> re.split(r"\s+", ' a b c '.strip())
['a', 'b', 'c']

Здесь тоже можно указать количество разделений

>>> re.split(r"\s+", 'a b c', 1)
['a', 'b c']

А что если мы хотим написать красиво, то есть split() без аргументов, но при этом указать количество разделений? В этом случае первым аргументом передаём None

>>> "a\n  b c".split(None, 1)
['a', 'b c']

Данный метод не учитывает строки с пробелами, взятые в кавычки

'a "b c" '.split()
['a', '"b', 'c"']

Но для таких случаев есть другие способы.

#tricks #basic
👍25😱1😢1
Репозитори на почитать для расширения кругозора.

▫️ Большой ликбез по экосистеме Python
https://github.com/brunocampos01/understanding-the-python-ecosystem

▫️Подборка самых популярных полезностей для веб разработчиков
https://github.com/ml-tooling/best-of-web-python

▫️Подборка библиотек для тех кто уважает типизацию в Python
https://github.com/typeddjango/awesome-python-typing

#basic #libs
👍96
Функция dir() - удобна для получения списка атрибутов у любого объекта.

Ранее я писал про функцию __dir__() в модуле (не путайте её с переменной __all__(), которая указывает список объектов для импорта если встречается конструкция from module import *).

Скорее всего вы уже знаете как использовать функцию dir(). Любой объект может реализовать метод __dir__() чтобы указать список имеющийхся и динамических атрибутов. И функция dir() поможет получить список этих атрибутов.

>>> dir(str)
['__add__', '__class__', '__contains__', ...]

У этой функции есть еще один способ применения. Её можно вызвать без аргумента, и в таком случае она вернёт список имён в текущем неймспейсе.

>>> dir()
['__builtins__', '__doc__', '__file__', ...]

>>> def test():
>>> x = 1
>>> print(dir())
>>> test()
['x']

#basic #tricks
👍7