PYTHON IN DEPTH🐍
396 subscribers
8 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
Чем мутабельные объекты отличаются от иммутабельных?

В python объекты бывают мутабельные и иммутабельные. Значние иммутабельного объектра нельзя изменить после того, как он был создан, а значение мутабельного можно.

Рассмотрим на примере. В Python есть встроенная функция id(). В реализации CPython она возвращает адрес, по которому объект находится в памяти. Создадим список и посмотрим, в ячейке с каким номером он окажется.

beatles = [“Ringo”, “Paul”, “John”, “George”]
id(beatles)
140181376779336

Видим, что список лежит по адресу 140181376779336. Теперь изменим какой-нибудь элемент списка и посмотрим, изменился ли адрес.

>>>beatles[0] = “Pete”
>>>beatles
[“Pete”, “Paul”, “John”, “George”]
>>>id(beatles)
140181376779336

Видим, что адрес списка не меняется, если мы изменяем элементы, которые в него входят. Теперь создадим в памяти строку

>>>bass = "Paul"
>>>id(bass)
140068833595776

и добавим в нее что-нибудь

>>>bass += " McCartney"
>>>bass
Paul McCartney
>>>id(bass)
140068833600432

Видим, что адрес строки изменился.

Почему так? Строка иммутабельна, то есть, после того, как она уже создана, нельзя изменить ее содержимое. Поэтому интерпретатор перезаписывает ее по новому адресу с новыми символами. А список мутабелен, поэтому его адрес не меняется оттого, что мы заменили один элемент.

Вот какие встроенные типы есть в python:

Класс Описание Мутабелен?
---------------------------------
bool булевый тип ☹️
int целочисленный тип ☹️
float число с плавающей запятой ☹️
list список ☺️
tuple кортеж ☹️
str строка ☹️
set множество ☺️
dict словарь ☺️

Количество встроенных иммутабельных типов в python больше, но если вы захотите создать свой класс, он будет мутабельным.

Важно отличать мутабельность от иммутабельности, так как некоторые типы данных, например, dict или set, реализованы как хэш-таблицы. Когда вы добавляете элемент в множество, интерпретатор берет хэш от этого элемента и размещает значение по адресу, который совпадает с хэшом. Если мы попытаемся положить список в множество, то получим TypeError, потому что хэш бы изменился, когда изменился бы один из элементов списка:

TypeError: unhashable type: 'list'

Поэтому мутабельные объекты, такие как словари или списки, не могут быть ключами в словаре или элементами множества. А вот строки или числа могут. И это на самом деле очень естественно🐍
Please open Telegram to view this post
VIEW IN TELEGRAM
Параметры map

А вы знали, что у функции map три параметра?

Функция map используется для того, чтобы делать однотипные операции над наборами данных. Например, с ее помощью удобно приводить типы данных:

>>> chr_nums = ['1', '2', '3', '4', '5']
>>> list(map(int, chr_nums))
[1, 2, 3, 4, 5]

или округлять значения:

>>> floats = [2.2865, 3.6420, 6.6418, 8.7231, 3.1528]
>>> list(map(round, floats))
[2, 4, 7, 9, 3]

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

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

>>> list(map(pow, range(10), [2]*10))
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

То же с округлением:

>>> floats = [2.2865, 3.6420, 6.6418, 8.7231, 3.1528]
>>> list(map(round, floats, [3]*len(floats)))
[2.287, 3.642, 6.642, 8.723, 3.153]

Либо так:

>>> from itertools import repeat
>>> list(map(round, floats, repeat(1)))
[2.3, 3.6, 6.6, 8.7, 3.2]

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

Больше в доке: map, repeat.
Гид по множественному присваиванию

Уверена, что множественное присваивание в Python видели все. Выглядит оно вот так:

>>> x, y, z = 1, 2, 3
>>> x
1
>>> z
3

На самом деле на низком уровне создается tuple и в цикле инициализируется значениями 1, 2, 3. Поэтому с тем же успехом можно не опускать скобки и написать вот так:

>>> (x, y, z) = (1, 2, 3)
>>> x
1
>>> z
3

И это тоже сработает. Поэтому множественное присваивание еще называют tuple unpacking. Да и чаще всего его, кажется, используют именно с кортежами. Но вообще так можно писать с любыми iterable типами:

>>> x, y = [1, 2]
>>> y
2

И со строками тоже:

>>> x, y = 'hi'
>>> x
'h'
>>> y
'i'

Множественное присваивание, если применять его с оператором *, позволяет делать еще вот такие крутые штуки:

>>> numbers = [1, 2, 3, 4, 5, 6]
>>> first, *rest = numbers
>>> first
1
>>> rest
[2, 3, 4, 5, 6]

По сути * здесь заменяет слайсы. С тем же успехом можно было написать:

>>> first, last = numbers[0], numbers[1:]
>>> first
1
>>> last
[2, 3, 4, 5, 6]

Но через распаковку получается читаемее. Можно даже <<вырезать>> середину:

>>> first, *middle, last = numbers

И если вам не нужны все промежуточные индексы, то хороший тон это вообще использовать нижнее подчеркивание:

>>> first, *_, last = [1, 2, 3, 4, 5, 6]

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

>>> color, point = ('blue', (0, 0, 255))
>>> color
'blue'
>>> point
(0, 0, 255)

В некоторых случаях это очень удобно.

В общем, множественное присваивание можно использовать не только с кортежами, но и списками, строками и любыми iterable типами. Его плюс в том, что оно позволяет не использовать индексы, а значит, уменьшает склонность к ошибкам и делает код читаемее. А еще оно используется в <<args, kwargs>> синтаксисе, который часто смущает начинающих, но об этом в следующий раз. 🐠
🥰2