Pull to refresh

Comments 17

Именованный - Минусы

Импорт зависит от точки входа.

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

Программисту необходимо знать структуру приложения.

вообще это полезная штука, да. независимо от импортов.

Код сильно связан с приложением.

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

Что по сути усложняет отладку, тестирование, и тд.

по сути ничего не усложняется, если не усложнять без надобности.

Программист становится сильно вовлеченным в проект.

?‍♂️

PS
в начале поста не хватает большими красными буками капсом:
tl; dr: используйте абсолютные импорты, а относительные не используйте, и будет хорошо.

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

Точки входа в программу. Если просто то

if __name__ == "__main__":

Если же у Вас импорт в текущем модуле будет описан именованно, то запустить и исполнить код из этого же модуля или другого места, которым не является ваш корневой файл, не возможно. Если импорт описан не именовано, то это становится возможно. Для чего же это может понадобиться? Да просто, на просто запустить кусок кода во время разработки, а не поднимать всю софтину для проверки. Да такое возможно сделать и с именованным, но это требует поработать с так называемым PATH, что не так приятно делать каждый раз для каждого файла.

вообще это полезная штука, да. независимо от импортов.

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

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

От того, что у тебя явно прописано местоположения модулей, названия папок/директорий и тп. что осложняет выдергивание кода его запуск в других местах. О чем я, выдернуть модуль из контроллеров и засунуть его в либу или хендрлер без переписывания импортов не возможно(импорты бывают объемными, на моей памяти доходило до 30+ строк), а также просто изолированный запуск.

по сути ничего не усложняется, если не усложнять без надобности.

См. ответ на первую цитату.

PS
в начале поста не хватает большими красными буками капсом:
tl; dr: используйте абсолютные импорты, а относительные не используйте, и будет хорошо.

Спасибо, за совет, рассмотрю его и постараюсь исправить статью. А если говорить о частном, статья направлена на освящение методов, а не рекомендации(делай так 2 раза в день и спина болеть не будет) и это было описано.

Плюсы:

Для разработки в команде это дает более четкое понимание, что и где делает каждый при выполнении "задания".

Минусы:

Нужно понимать, что делается и для чего

Как это так? Импорты улучшают понимание, но нужно понимать?

Вообще, не хватает обзора варианта без импортов, имхо. Не понятно с чем идёт сравнение импортов и какие там минусы

Я Вас понял, последующие статьи буду писать с учётом полученного опыта.

Пока распишу варианты:

1) жестокий и в лоб. Пишем все в одном файле. Это много кода который не разбит на файлы и тд. Ну это достаточно неудобно от 1 тысячи строк кода.

2) бьем на файлы и папки. Появляется логика из серии парка database и файлик user где описано взаимодействие с базой, а вчастности с таблицей User. Что это даёт: при импорте мы видим:

from app.database import User

По одной строке нам понятно что мы работаем с базой данных и таблицей Users.

Если сравнить то имея функцию get_user() можно иметь вариации. Получить информацию пользователя который обращается к нашему сервису или пользователь которого достаём из базы данных. Соответственно когда все в одном файле появляется момент что непонятно что есть что. А тут просто и четко следует из импорта.

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

Соответственно когда идёт работа в команде и у одного задача реализовать работу с пользователями(человек будет работать с сущностями User), а другому нужно сделать работу с изображениями(будет работать с сущностями Images). Если мы разбиваем все на файлы то понятно, кто где копается. А если файл один, то это все пишется в одном файле и это достаточно неудобно(система контроля версий гит. Один файл меняет толпа людей. Лог изменений громадный и непонятно что менялось, надо читать все и это грустно)

Если данных пояснений жалко то напишите комментарий я постараюсь реализовать свою идею в рамках гитхаба и скинуть ссылку для более полноценного понимания)

UFO landed and left these words here

Интересно, но мало :-) Кстати, насколько помню, импорты с точками вместо имён у меня почти никогда не получались.

Ну и главное - не затронута совсем такая бяка, как циклический импорт. Что делать, если в модуль "А" нужно импортировать модуль"Б", и наоборот - в модуль "Б" нужно импортировать модуль "А". Мне из-за этого пришлось отказаться от разбиения модуля на два, а также от аннотаций функций. Скажем, модуль "А" использует функции из "Б", а функции в "Б" работают с типами из "А"...

Импорты для аннотаций можно завернуть в if typing.TYPE_CHECKING: чтобы в рантайме они не импортировались

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

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

Поэтому возникновение циклического импорта, для меня диковина. Из вариантов это более тщательный подход в выделении логических блоков кода для их выноса в другие модули. Или метод топорный, но рабочий, это сделать третий модуль, туда импортировать необходимое и описать уже их симбиоз…(в рамках ооп подобная идея называется ромбовидное наследование, по факту цикл, но из-за того что это ромб оно работает однонаправленно)

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

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

P.S.

На досуге постараюсь подумать, где идейно можно и нужно применить циклический импорт.

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

Буквально вчера решал данную проблему. Вроде как решил с помощью указания:

# child_classes.py
from base_class import BaseClass

__all__ = [
  "ChildClass1",
  "ChildClass2",
]

class ChildClass1(BaseClass):
  pass

class ChildClass2(BaseClass):
  pass

...
# base_class.py
from ..root import child_classes

__all__ = [
  "BaseClass",
]

def BaseClass:

  @classmethod
  def create(cls, params):
    return getattr(child_classes, cls.__name__)(params)

Суть решения в использовании "all", в котором указываются функции и классы, которые надо импортить при импорте.

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

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

Все файлы, функции и тд. это объект. Но что это за объект и что за класс за ним стоит?

Все просто, это любимый всеми программистами класс, использующий паттерн проектирования Singleton.

Совершенно непонятно, что тут имел в виду автор. Singleton — это конкретный порождающий паттерн, предполагающий, что объект сам управляет своим жизненным циклом (создаёт себя и следит за тем, чтобы был создан только один экземпляр). Singleton это класс с методом навроде get_instance(), который всегда возвращает один и тот же экземпляр класса. Если экземпляр класса создаётся другим классом, это уже не Singleton, даже если экземпляр создаётся один.

Функции это объекты класса function. Класс function это не Singleton, так же как и любой объект класса type. Класс int это не синглтон, у него нет метода get_instance, а вызов int() вернёт новый инстанс в зависимости от переданного аргумента.

Другое дело, что все имена в Питоне это по сути именованные ссылки, то есть любой объект передаётся по ссылке, а значит мутабельный объект может быть изменён другим кодом. Но это никак не относится ни к импортам, ни к синглтонам.

объект сам управляет своим жизненным циклом (создаёт себя и следит за тем, чтобы был создан только один экземпляр)

Раскройте пожалуйста эту мысль, просто на своей памяти я не могу вспомнить ни единого объекта который бы порождал себя сам, и сам управлял своим жизненным циклом. Тоесть в моем представлении объект класса Singletone управляет экземплярами(ом) данного класса.

Singleton это класс с методом навроде get_instance(), который всегда возвращает один и тот же экземпляр класса.

В моем понимании import является своего рода методом который вызывает регистрацию запрашиваемого модуля в ядре(CPython допустим), и возвращает экземпляр класса. Что и есть своего рода get_instance().

Функции это объекты класса function. Класс function это не Singleton,
так же как и любой объект класса type. Класс int это не синглтон, у него
нет метода get_instance, а вызов int() вернёт новый инстанс в
зависимости от переданного аргумента.

В данном случае, соглашусь с неясной трактовков моих мыслей. Речь шла именно про файлы(модули). Исправил. Спасибо за комментарий.

Другое дело, что все имена в Питоне это по сути именованные ссылки, то
есть любой объект передаётся по ссылке, а значит мутабельный объект
может быть изменён другим кодом. Но это никак не относится ни к
импортам, ни к синглтонам.

Я хотел осветить в данном случае проблему импорта, ведь написав импорт мы явно ничего не создаем, а при этом всем может измениться(кем-то переопределиться и т.п.) любой экземпляр в рамках модуля, точнее будет сказать что они не "переопределяются" при новом импорте. Не очевидный пример:

# main.py
if __name__ == '__main__':

    # Выводим int = 2
    import test1
    print(test1.a)

    # Изменяем int = 2 на 4 и выводим
    import test2
    test2.new_t()

    # Выводим тот же int когда-то равный двум(уже четырем)
    import test1
    print(test1.a)
# test1.py
a = 2
# test2.py
def new_t():
    import test1
    test1.a = 4
    print(test1.a)
# print
2
4
4

Поэтому файлы(модули) = объект, а ввиду набора определенных свойств импорта эти объекты порождают один и тот же экземпляр класса. В совокупности этих факторов я и написал про паттерн Singletone.

В примере, id(a) будет разный, поскольку как вы правильно заметили int иммутабельный, но входит в состав мутабельного экземпляра. Хоть было бы проще привести в пример id(test1)*, но это бы не показало какие проблемы подобные импорты порождают. И поскольку данная проблема преследует при мутабельных и иммутабельных составляющих модулей, я решил что будет логично осветить это в теме import а не мутабельности.

P.S.
С остальным содержанием комментария автора согласен и считаю его мысли не противоречищами с идеями излагаемыми в статье.

* id(test1) = id(test1) при новом импорте.

Раскройте пожалуйста эту мысль, просто на своей памяти я не могу вспомнить ни единого объекта который бы порождал себя сам, и сам управлял своим жизненным циклом. Тоесть в моем представлении объект класса Singletone управляет экземплярами(ом) данного класса.

Конечно, некорректно говорить, что объект порождает сам себя. Объект не может ничего делать, пока его нет. Правильнее будет сказать, что это класс инстанцирует сам себя. То есть код вызова конструктора ClassName() находится внутри кода самого класса.

Именно такая реализация — инстанцирование себя внутри своего же кода — предполагается у Singleton. Иными словами, синглтон — это паттерн "фабричный метод", на который также повешена функциональность контроля за количеством инстансов (не больше одного). Именно из-за двойной ответственности его и называют антипаттерном, в отличие от, например, Service Locator'а, который тоже умеет инстанцировать класс в единичном экземпляре.

В моем понимании import является своего рода методом который вызывает регистрацию запрашиваемого модуля в ядре(CPython допустим), и возвращает экземпляр класса.

Импорт действительно выполняет код разово, помещая результаты его работы в отдельное пространство имён. Это не обязательно должен быть декларативный код — там могут быть любые сайд-эффекты. Ну и если там было объявление класса, то будет создан объект типа type. От этого type не становится синглтоном — хотя бы потому, что объектов типа type (то есть классов) может быть больше одного.

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

По поводу подхода инстанцирования вопросов нет, и я понимаю как оно живет(в общих чертах).

Ну и если там было объявление класса, то будет создан объект типа type .

Тут собственно говоря тоже нет возражений. Но далее Вы ссылаетесь на то, что именно этот класс не является Singleton, хотя речь шла именно про файл содержащий код. Это как описать класс в рамках класса и использовать его там. Файл имеет свой тип и паттерн, и класс в рамках файла имеет свой тип и паттерн, все логично и спорить не с чем.

но я бы убрал упоминание про Singleton

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

P.S.

Именно из-за двойной ответственности его и называют антипаттерном, в отличие от, например, Service Locator'а, который тоже умеет инстанцировать класс в единичном экземпляре.

Стоит заметить, что в некотором случае локатор служб фактически является анти-шаблоном.

https://ru.wikipedia.org/wiki/Локатор_служб#:~:text=Стоит заметить%2C что в некотором случае локатор служб фактически является анти-шаблоном

Опять таки тут возникает вопрос вкусовщины.

У меня нет противоречий, что синглтон антипаттерн.

Sign up to leave a comment.

Articles