PYTHON IN DEPTH🐍
448 subscribers
3 photos
10 links
ПАЙ8
Download Telegram
Channel created
Импорт в Python и организация проекта

Когда вы работаете с модулями в Python, импорт играет важную роль, и понимание его работы может значительно улучшить вашу разработку. В этом посте мы рассмотрим, как работает оператор import в Python и как организовать структуру вашего проекта для более эффективного использования импорта.

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

По умолчанию sys.path содержит следующие каталоги:

- Каталог, из которого был запущен скрипт
- Каталоги, указанные в переменной окружения PYTHONPATH
- Каталог текущего активного виртуального окружения
- Каталог установки Python

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

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

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

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

Вынесите запускаемые скрипты на верхний уровень, а остальной код упакуйте в пакет.
Упаковка вашего кода в пакет с уникальным именем позволит избежать конфликтов имен. При этом вынесение всех запускаемых файлов на один уровень делает состав sys.path предсказуемым.

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

Copy code
├── appname
│ ├── __init__.py
│ ├── other_module.py
│ └── some_module.py
├── cli_module.py
└── requirements.txt
Создайте распространяемый пакет (рекомендуется).
В этом случае вы упаковываете весь ваш код в пакет, что помогает избежать конфликтов имен. Для запуска команд вы можете использовать синтаксис python -m appname.cli_module или заполнить секцию entry_points в файле с описанием вашего проекта (например, setup.cfg или pyproject.toml). После этого вы сможете запускать ваш код, находясь в любом каталоге, без необходимости указывать полные пути к файлам.

Для удобства разработки с таким подходом удобно устанавливать пакет в режиме редактирования с помощью команды 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/Рабочий_каталог
/ и * в определнии функции

Видели когда-нибудь вот такой синтаксис?

def foo(first, /, second, *, third):
print(first, second, third)

Выглядит странно?

На самом деле / и * навязывают положение ключевых и позиционных аргументов.

Попытаемся, например, вызвать foo() с неправильным набором параметров:

>>> foo("bar", "qux")
...
TypeError: foo() missing 1 required keyword-only argument: 'third'

Нужен keyword аргумент, окей, попробуем еще раз:

>>> foo("bar", third="qux")
...
TypeError: foo() missing 1 required positional argument: 'second'

Снова нет. Укажем все параметры:

>>> foo("bar", "baz", third="qux")
bar baz qux

Получилось!

Оба символа опциональны. И, в моем опыте, если ставят, то обычно только звездочку.

Источник: https://docs.python.org/3/tutorial/controlflow.html#special-parameters
Пара фактов о численных типах
(которые вы, возможно, не знали)

Факт 1

В Python есть три встроенных численных типа. Кроме int и float, которыми мы обычно пользуемся, есть еще complex — комплексные числа.

Комплексные числа много используют в математике (например, с их помощью можно брать некоторые забористые интегралы, которые обычным способом не берутся), и в физике (особенно в расчетах, связанных с электричеством и магнетизмом).

Сконструировать комплексное число в Python можно так:

a = complex(2, 1)

или вот так:

a = 2 + 1j

Получится одно и то же.

Факт 2

Все численные типы в Python унаследованы от класса Number. Проверить это можно так:

from numbers import Number

isinstance(1984, Number) #True
isinstance(3.1415926, Number) #True
isinstance(1j, Number) #True

Кстати, сюрприз: bool тоже унаследован от Number:

isinstance(False, Number) #True

Факт 3

Под капотом логический тип — те же числа, только bool имеет всего два значения: 0 и 1. Это обеспечивает нам легкое приведение True к единице, а False к нулю.

Это же, впрочем, дает ни разу не интуитивное поведение в некоторых случаях:

1/False # ZeroDivisionError: division by zero

True * 1 # 1
False * 1 # 0

complex(True, 3.9) # (1+3.9j)

my_list = [1, 2, 3, 4]
my_list[False] # 1

"False"[True] # a

А, и да

Факт 4

Complex не является составным типом. Это просто объект, который принимает до двух параметров при инициализации.
Про культуру коммитов

Работаю над куском кода, который опирается на json файл. Файл этот длиной 7к+ строк (только не спрашивайте, как так вышло). И кусок этот одновременно
- сложный
- используется большим количеством пользователей.

И тут из мастера в этот json прилетают изменения. В которых во всем файле пробелы заменены на табы. То есть, дифф слишком большой и отсмотреть глазами не получается. Поправлю, конечно, но досадно -- и небезопасно.

В связи с чем напоминаю, как коммитить (вот, например, хорошая статья от другого автора)

Для меня главное в этом тексте:
Один коммит = одна атомарная задача

И от себя бы еще добавила:
Дифф удобно смотреть ривьюеру

Чтобы этого добиться, перед каждым коммитом нужно отсматривать изменения в каждом файле, которые хотите залить в репо: с помощью git diff или в IDE.

И еще я никогда не делаю

git add .

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

Синтаксис встроенной функции filter такой:

filter(function, iterable).

Эта функция фильтрует значения переданной последовательности с помощью функции function. Если function получает очередной элемент последовательности и возвращает True, то элемент попадает в результат работы filter, иначе нет.

Например, таким способом можно отфильтровать только строки, состоящие из чисел:

>>> strings = ['two', 'list', '', 'dict', '100', '1', '50']
>>> list(filter(str.isdigit, strings))
['100', '1', '50']

Или только четные значения:

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
list(filter(lambda x: x % 2 == 0, numbers))

Часто в качестве фильтров используют лямбда-функции или член-функции классов.

А еще (внезапно) вместо функции можно использовать None:

>>> random = [1, 'a', 0, False, True, '0', '']
>>> list(filter(None, random))
[1, 'a', True, '0']

И тогда filter вернет только truthy значения.
👍3