CoolPython
4.68K subscribers
20 photos
44 links
Канал об основах Python и хороших практиках разработки. Создаём системность в обрывочных знаниях.

Тем, кто хочет понимать, что пишет!
Download Telegram
У нас опять есть группа, можно покомментить и покидаться какашками

UPD Правила игры те же: нетоксично про питон, программирование, инфраструктуру и за жизнь. Но нужно быть пуськой.

UPD2 https://t.me/+CgNMmJwzjYI1MmYy
Работа import и структура проекта

Когда вы импортируете какой-то модуль в вашем коде, питон не учитывает, в каком файле этот импорт находится, влияет только то, как был запущен код.
Если модуль не был раньше загружен, питон пытается его найти по очереди в нескольких папках, которые можно посмотреть в переменной sys.path

По умолчанию она содержит примерно такие каталоги:
* каталог, добавляемый при запуске
* каталоги указанные в переменной окружения PYTHONPATH
* каталог текущего активированного виртуального окружения
* каталог установки python

1. Если вы запускаете ваш скрипт командой python scriptname.py, то первым в списке будет тот каталог, где находится запускаемый скрипт. Текущий каталог не имеет значения.
2. Если вы запускаете ваш код командой python -m packagename, то первым в списке будет текущий каталог. При запуске питон попытается найти и импортировать packagename по общим правилам.
3. Если вы запускаете код с помощью других инструментов вроде pytest, они тоже могут сами добавлять что-то в sys.path.

Скорее всего, вам не стоит самостоятельно менять sys.path, так как алгоритм его заполнения стандартный и привычен для всех. Если по каким-то причинам вас он не устраивает, возможно у вас неверная структура проекта.

Так как поиск пакетов для импорта происходит сначала в каталоге "проекта", стоит быть аккуратным именованием ваших файлов и каталогов. Если вы случайно назовете ваш модуль так же как встроенный или сторонний, при любом импорте такого модуля будет грузиться именно ваш, что сломает работу кода.

Иногда используемые нами фреймворки поддерживают только определенную, не всегда оптимальную, структуру проекта. В остальных случаях я могу предложить два подхода:

1. Вынести запускаемые скрипты на верхний уровень, а остальной код упаковать в пакет.

Упаковка кода в пакет с уникальным именем позволяет исключить конфликты имен. А вынесение всех запускаемых файлов на один уровень делает состав sys.path предсказуемым.

Выглядеть это будет примерно так:

├── appname
│ ├── __init__.py
│ ├── other_module.py
│ └── some_module.py
├── cli_module.py
└── requirements.txt

2. Создать распространяемый пакет (рекомендую).

В этом случае вы упаковываете весь код в пакет, что помогает исключить конфликты имен.
Для запуска команд вы можете использовать синтаксис python -m appname.cli_module или заполнить секцию entry_points в файле с описанием проекта (setup.cfg, pyproject.toml), после чего иметь свои кастомные консольные команды. В обоих случаях вы сможете запускать код, находясь в любом каталоге, без необходимости указывать полные пути к файлам.

Для удобства разработки с таким подходом удобно устанавливать пакет в editable-режиме с помощью команды типа pip install -e .

Структура будет примерно такой:

├── pyproject.toml
└── src
└── appname
├── __init__.py
├── cli_module.py
├── other_module.py
└── some_module.py

Дополнительные материалы:
* https://packaging.python.org/en/latest/
* https://docs.python.org/3/reference/import.html
* https://docs.python.org/3/library/sys.html#sys.path
* https://ru.wikipedia.org/wiki/Рабочий_каталог
Сегодня в 18:00 UTC+2 релизят Python 3.11 в прямом эфире, смотреть можно по ссылке ниже:

https://www.youtube.com/watch?v=PGZPSWZSkJI

UPD: еще можно подписаться на их youtube, чтобы не пропускать релизы в будущем, если вам интересно
Фича f-строк, о которой вы не знали

Смотрите, вот обычная f-строка

>>> text = 'JUPI'
>>> print(f'{text}')
JUPI

Если я добавлю в нее
* двоеточие,
* символ-заполнитель,
* и укажу требуемую длину строки с помощью символа <

>>> print(f'{text:-<15}')

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

JUPI-----------

Если указать длину строки через >, то можно заполнить строку с левой стороны:

>>> print(f'{text:->15}')
-----------JUPI

Напишите в комментариях, как заполнить строку с обоих сторон, чтобы получилось

-----JUPI------

Подсказка: можно поискать вот здесь
🐠
Что такое JSON?

JSON (JavaScript Object Notation) – это удобный для чтения человеком и машиной структурированный формат, который используют для обмена данными.

JSON представляет собой наборы пар «ключ: значение» и основан на базе синтаксиса объектов JavaScript. При этом JSON считается независимым от JavaScript, так как может использоваться с любым языком программирования.

История

Создатель формата JSON – Дуглас Крокфорд, американский программист, принимающий активное участие в развитии JavaScript. В 2001 году, во время работы в своей компании State Software он разработал стандарт JSON как аналог формата XML, который уже существовал в тот момент.

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

Самое первое сообщение JSON было отправлено в 2001 году. Вот как оно выглядело:

<html><head><script>​
document.domain = 'fudco';
parent.session.receive({
to: "session",
do: "test",
text: "Hello world"
})
</script></head></html>


Это был html-документ, только небольшая часть которого напоминает современный JSON. Сначала этот формат был предназначен для языка JavaScript, но позже его стали использовать с другими языками программирования.

Через год Крокфорд купил домен JSON.org и опубликовал там синтаксис JSON с примерами реализации, и этот сайт до сих пор работает.

Какую задачу решает

JSON – это случай, когда простой и лаконичный формат вытеснил более сложный и функциональный. Поскольку JSON предназначен для передачи данных по сети, то важные его функции – это сериализация и десериализация.

Сериализация – это кодирование данных, превращение в серию байтов, их перенос в файл JSON с целью хранения или передачи по сети. Десериализация – обратный процесс распаковки данных.

JSON полезнее всего
- для создания приложений, взаимодействующих с браузером,
- там, где необходимо обмениваться информацией между серверами,
- и когда нужно передать структурированную информацию по сети.

Как выглядит JSON

JSON файл структурирован и состоит из пар ключ-значение:

{
"name": "John Smith",
"age": 35
}


Вот список типов данных, которые поддерживает JSON:
- Строка – несколько литералов, заключенных в кавычки.
- Число – целое или действительное.
- Массив – множество значений, заключенное в квадратные скобки.
- Логическое значение – true/false.
- Пустое значение​ – null.

Как работать с JSON

Сегодня в любом языке программирования есть встроенная библиотека или модуль для работы с JSON, и в Python тоже! Для начала работы достаточно подключить модуль json с помощью команды import:

import json

Сериализовать данные (например, словарь data) можно с помощью метода dump:

with open(“my_file.txt”, “w”) as file:
​json.dump(data, file)


При кодировании из Python в JSON данные меняют свой тип следующим образом:

dict -> Object
list-> Array
tuple -> Array
str -> String
int -> Number
float -> Number
True / False -> true / false
None -> null


Записать данные в строку можно, используя метод dumps(). Методы похожи, единственное отличие состоит в том, что вторым параметром не указывается файловый объект, так как на самом деле не происходит записи на диск. В файле с кодом все будет выглядеть подобным образом:

data_string = json.dumps(data)

Чтобы десериализовать файл, можно использовать методы load() и loads():

with open(“my_file.txt”, “r”) as file:
​ data = json.load(file)​


​Так же, как и при сериализации, происходит трансформация данных в характерный для языка программирования тип. При этом переход из JSON в Python не полностью аналогичен переходу из Python в JSON.

loads() идентичен dumps(), он также предназначен для работы со строкой.

Итого

- JSON – это в первую очередь человекочитаемый формат.
- Используется для передачи данных по сети и для клиент-серверного взаимодействия.
- Важные функции – это сериализация и десериализация, которые осуществляются методами dump() и load().
Канал переименован из Python in Depth в CoolPython
Все знают, что целочисленное деление округляет результат вниз. Например, если результат деления во float

5. / 3. = 1.66666667 то в целых числах он округлится вниз, а не к ближайшему целому: 5 // 3 = 1 А если результат отрицательный? - 5 // 3 = ?
Anonymous Quiz
8%
1
38%
-1
45%
-2
8%
Ошибка
Стек и очередь

Идеи стека и очереди часто встречаются в различных языках программирования. Давайте разберемся, в каких ситуациях их применение уместно и удобно программисту.

С очередями мы сталкиваемся практически везде. В магазине очередь – это группа людей, которые стоят друг за другом, например, на кассу с продуктами. Тот, кто стоит ближе к кассе, оплатит свои продукты первым.

В программировании очередь работает аналогично. Очередь в программировании — это коллекция элементов, которые обрабатываются по принципу «первый зашел — первый вышел» (FIFO).

Очередь прекрасно подходит к задачам, которые нужно делать поэтапно. Например, если пользователь загружает несколько изображений на сайт, чтобы сервер мог их обработать и сохранить, загружаемые файлы помещаются в очередь на обработку. И первый файл, который пользователь загрузил, будет первым обработан на сервере.

Стек работает по-другому. Стек – это тоже коллекция элементов, но работает он по принципу «последним зашел — первым вышел» (LIFO)

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

Частое применение стека в программировании – это организация записей об изменениях в файле или документе. Текстовые редакторы обычно записывают действия пользователя над файлом с помощью стека, чтобы потом можно было легко отменить эти действия в обратном порядке.

При работе со стеком и очередью нельзя обращаться к произвольному элементу в коллекции, потому что это нарушило бы принципы LIFO или FIFO. Чтобы добраться до нужного элемента, необходимо перебрать все элементы стоящие перед или после него в списке по установленному порядку.

В программировании стек и очередь часто используют для математических вычислений, для работы с данными и операциями. Принципы FIFO и LIFO позволяют нам структурировать и упорядочивать работу с данными. Такой подход позволяет быть уверенными, что данные и операции будут обрабатываться в нужном порядке.
Рассказали о разнице функций input и raw_input в Python2 в карточках. Читайте!

Карточки подготовили вместе с @pro_python_code
Что выведет этот фрагмент кода? Ниже квиз
Что будет в результате?
Anonymous Quiz
42%
try clause + final clause
29%
try clause
3%
exception clause
26%
final clause
try, except, finally и оператор return в Python: как работает finally при наличии return

Python предоставляет механизм обработки исключений с помощью операторов try, except и finally. Когда внутри блока try возникает исключение, оно может быть перехвачено и обработано в блоке except. Кроме того, блок finally позволяет выполнять нужные вам действия независимо от того, произошло исключение или нет.

Но если внутри блока finally использовать оператор return, как в примере выше, возникает особенность.

Вот ещё раз пример кода из опроса:

def try_with_returns():
try:
return 'try clause'
except:
return 'exception clause'
finally:
return 'final clause'

print(try_with_returns())


Можно ожидать, что будет выведено 'try clause' и 'final clause', но фактически выводится только 'final clause'. Почему?

Документация Python объясняет такое поведение. При наличии блока finally, код внутри него должен быть выполнен в любом случае. Поэтому если блок finally содержит return, то возвращаемое значение будет именно то, которое указано внутри finally, а не внутри try, иначе до finally поток исполнения просто не дойдет.

Вот еще один пример для лучшего понимания:

def bool_return():
try:
return True
finally:
return False

print(bool_return())


В этом случае можно ожидать, что будет выведено True, но выводится False. Это снова происходит из-за оператора return в блоке finally, который прерывает выполнение блока try и возвращает значение False.

Итак, при использовании оператора return внутри try/except/finally следует иметь в виду, что возвращаемое значение будет определено блоком finally. И если finally содержит оператор return, то его значение будет возвращено вместо значения из блоков выше.

Дока: https://docs.python.org/3/tutorial/errors.html?highlight=try%20finally#defining-clean-up-actions
Вопрос от студента: как использовать в map функцию с параметром?

Все, кто сталкивался с map(), знают, как использовать её, если параметры не нужны. Например, с map() можно привести числа в списке к абсолютным значениям

numbers = [-1, 2, -3, 4, -5]
absolute_values = list(map(abs, numbers))
print(absolute_values)
# [1, 2, 3, 4, 5]


Или преобразовать все строки в верхнему регистру

names = ["alice", "bob", "charlie", "david"]
print(list(map(str.upper, names)))
# ["ALICE", "BOB", "CHARLIE", "DAVID"]


А что делать в случае, если map нуждается в параметре? Например, есть список

numbers = [0, 0.333, 0.667, 1]

и требуется округлить все числа до 1 знака после запятой, то есть, получить

[0, 0.3, 0.7, 1]

В какое место надо написать параметр при использовании map()?

Отвечаем. Просто так это сделать не получится! Для этого понадобятся lambda-функции.

В этом случае, чтобы округлить числа из списка до 1 знака после запятой, можно использовать следующий приём:

a = [0, 0.333, 0.667, 1]
result = list(map(lambda x: round(x, 1), a))

Здесь lambda x: round(x, 1) создает анонимную функцию, которая принимает аргумент x и вызывает функцию round() с параметром 1 для округления числа x до 1 знака после запятой. Затем map() применяет эту анонимную функцию ко всем элементам списка.

Результатом будет новый список, содержащий округленные значения:

[0, 0.3, 0.7, 1]

Таким образом, используя map() с анонимными функциями, можно применить функцию с параметром к элементам списка и получить нужный результат.