Import or from import, that is the question
- четверг, 25 февраля 2021 г. в 00:36:54
Есть три стадии знаний: ты используешь инструмент, ты понимаешь как он работает, ты можешь учить других работать этим инструментом. Потихонечку начал перетекать в третью и стал задавать себе вопросы, которые раньше не задавал.
Например, что лучше: import module
или from module import function
?
Я решил разобраться в этом чуть поглубже, ответы на StackOverflow меня не удовлетворили.
Для тех, кому лень читать: все варианты хороши.
Обратимся к первоисточникам, а именно — к PEP8
Когда импортируете класс из модуля, будет нормально сделать это так:
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
В случае пайтеста это вполне логичный выбор, так как эта библиотека должна быть максимально совместима со всем кодом на планете.
У неё один из самых высоких шансов получить локальный конфликт имён.
Есть даже плагин для линтера который проверяет, что импорты оформлены правильно.
Второй подход основан на выборе имён внутри модуля. Для очень абстрактных имён, например как в некоторых модулях стандартной библиотеки лучше использовать импорт из модуля.
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.
Лично для меня некоторое многословие в импортах менее страшно чем многословие в коде.
При импорте отдельных объектов получатся более короткий код. Его проще читать. Меньше переносов строк.
В целом чуда не случается, но читать приятнее.
В IDE поддерживаются оба случая на одном уровне, и автоматика работает одинаково. Но иногда бывают сбои: мерджи с другим кодом, ручные переименования/перемещения файлов. Если переименовали объект, а использования забыли, то при импортировании атрибутов код упадёт сразу,
а при доступе через единый модуль – только в момент вызова.
Если у вас 100% покрытие тестами – разницы нет. Если нет, то второй случай пропустить легко.
При написании тестов порой надо подменить какие-то методы. Вот тут с импортами из модуля начинается цирк. К моменту, когда накладывается патч, ссылка на объект уже имеется в тех местах, где он импортирован, и патчить нужно не место, где объект объявлен, а все места, где был сделан импорт.
Несложно, но немного запарно, и есть шанс что-то поломать при рефакторинге.
Этот момент – достаточно частый источник недоумения.
Бытует миф, что импортирование из модуля только части классов/функций экономит память. На деле в обоих случаях в память загружается модуль целиком, и разницы в производительности нет.
Если в пространстве имён есть конфликты, то Питон позволяет их обойти двумя способами. Либо 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/
Для себя я выбираю следующий подход при импортировании и написании своих модулей.
Использовать import from
, кроме случаев:
json
);pytest
.