- PVSM.RU - https://www.pvsm.ru -

Import or from import, that is the question

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

Например, что лучше: import module или from module import function?

Я решил разобраться в этом чуть поглубже, ответы на StackOverflow меня не удовлетворили.

Для тех, кому лень читать: все варианты хороши.
Import or from import, that is the question - 1

Советы от создателей языка

Обратимся к первоисточникам, а именно — к PEP8 [1]

Когда импортируете класс из модуля, будет нормально сделать это так:

from myclass import MyClass
from foo.bar.yourclass import YourClass

Если это вызывает локальный конфликт имён, то можно сделать это явно:

import myclass
import foo.bar.yourclass

И использовать как myclass.MyClass и foo.bar.yourclass.YourClass
PEP-8 поддерживает оба подхода и даёт некоторые рекомендации.

Кто принимает решение?

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

Документация

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

"Фляжка" и "бутылка" идут по пути точечных импортов.

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!

from bottle import route, run, template

@route('/hello/<name>')
def index(name):
    return template('<b>Hello {{name}}</b>!', name=name)

run(host='localhost', port=8080)

Пайтест выбирает импортирование модуля.

import pytest

def test_zero_division():
    with pytest.raises(ZeroDivisionError):
        1 / 0

В случае пайтеста это вполне логичный выбор, так как эта библиотека должна быть максимально совместима со всем кодом на планете.
У неё один из самых высоких шансов получить локальный конфликт имён.
Есть даже плагин [2] для линтера который проверяет, что импорты оформлены правильно.

Имена атрибутов модуля

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

import json

json.load(...)
json.loads(...)
json.dump(...)
json.dumps(...)

Без импорта модуля имена теряют контекст и могут внести путаницу.

from pickle import load
from marshal import dumps
from xmlrpc.client import loads
from xml.etree.ElementTree import dump

load без контекста может быть как json.load так, и pickle.load.
При этом вторая функция имеет ограничения по применению в связи безопасностью и ей при просмотре нужно уделить особое внимание. Обратный случай – когда имена модуля и его содержимого очень большие и загромождают код. Короткие, абстрактные имена – это прерогатива создателей стандартной библиотеки языка. Для своего же кода я выбираю имена среднего и большого размера, которые понятны без контекста в виде имени модуля. Но граничные случаи редки, и большинство библиотек не вызывают проблем при использовании любого из подходов.

Секция импортов

Если импортировать атрибуты из модуля, то секция импортов начинает пухнуть. А если через модуль, то пухнет код.

Секция импортов — это та часть кода, которая хорошо поддаётся оптимизации.
Например, некоторые IDE умеет её сворачивать, а так же самостоятельно форматировать. Да и импорты я редко прописываю руками, подсказки по коду предлагают это сделать за меня. Еще можно форматировать импорты сторонними инструментами, например isort [3].

Лично для меня некоторое многословие в импортах менее страшно чем многословие в коде.

Простота чтения

При импорте отдельных объектов получатся более короткий код. Его проще читать. Меньше переносов строк.

В целом чуда не случается, но читать приятнее.

Рефакторинг

В IDE поддерживаются оба случая на одном уровне, и автоматика работает одинаково. Но иногда бывают сбои: мерджи с другим кодом, ручные переименования/перемещения файлов. Если переименовали объект, а использования забыли, то при импортировании атрибутов код упадёт сразу,
а при доступе через единый модуль – только в момент вызова.

Если у вас 100% покрытие тестами – разницы нет. Если нет, то второй случай пропустить легко.

MonkeyPatch

При написании тестов порой надо подменить какие-то методы. Вот тут с импортами из модуля начинается цирк. К моменту, когда накладывается патч, ссылка на объект уже имеется в тех местах, где он импортирован, и патчить нужно не место, где объект объявлен, а все места, где был сделан импорт.
Несложно, но немного запарно, и есть шанс что-то поломать при рефакторинге.

Этот момент – достаточно частый источник недоумения.

Производительность и память

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

Пространство имён

Если в пространстве имён есть конфликты, то Питон позволяет их обойти двумя способами. Либо import module, либо from module import item as module_item. Мне подход с модулем больше нравится, as практически не использую, пусть лучше будет чуть больше кода с именами модулей, чем добавление новых имён. Хотя есть и устоявшиеся традиции.

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

Единообразие

На мой взгляд, использование обоих подходов не сильно нарушает единообразие, и можно их спокойно смешивать. Желательно, правда, не для одного и того же модуля. Некоторые инструменты на такое ругаются: https://lgtm.com/rules/1818040193/ [4]

Заключение

Для себя я выбираю следующий подход при импортировании и написании своих модулей.
Использовать import from, кроме случаев:

  • есть (или вероятен впоследствии) конфликт имён;
  • функции/классы модуля имеют короткие и распространённые имена (json);
  • существуют устоявшиеся традиции или конкретные рекомендации авторов модуля, например pytest.

Автор: Andrey_Solomatin

Источник [5]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/python/361878

Ссылки в тексте:

[1] PEP8: https://www.python.org/dev/peps/pep-0008/#imports

[2] плагин: https://github.com/m-burst/flake8-pytest-style/blob/v1.3.0/docs/rules/PT013.md

[3] isort: https://pypi.org/project/isort/

[4] https://lgtm.com/rules/1818040193/: https://lgtm.com/rules/1818040193/

[5] Источник: https://habr.com/ru/post/543832/?utm_source=habrahabr&utm_medium=rss&utm_campaign=543832