При абсолютном импорте Python почему-то ищет импортируемые зависимости рядом
Почему-то... На самом деле никакой магии нет.
При запуске скрипта python path/to/script.py, путь к директории в которой находится запускаемый скрипт добавляется в sys.path. Дальше никакой магии нет и поиск импортируемых модулей и пакетов происходит при помощи sys.path. (На самом деле все сильно сложнее, но для понимания работы этого пока будет достаточно).
Когда вы импортируете http_get.modules.http_get, сперва будет проверен кеш модулей sys.modules, и если модуль с указанным именем в нем не обнаружен, будет произведена попытка поиска модуля в sys.path.
Пакет http_get будет найден непосредственно в sys.path и все подмодули будут импортированы относительно него. Во время импорта интерпретатор выполняет код модуля, создает объект модуля и создает ссылку на него в словаре sys.modules.
Ключем в этом словаре выступает полное имя модуля, значением соответственно ссылка на объект модуля. Таким образом в sys.modules появилась новая запись с ключем http_get.modules.http_get.
И тут PyCharm подкладывает вам и всем новичкам утку. При запуске при помощи PyCharm, по-умолчанию PyCharm устанавливает переменную окружения PYTHONPATH, значением которой передает путь к корню проекта (или точней к "Sources Root").
Забавно, что вне PyCharm ваш код просто так работать не будет. Попробуйте запустить свой main.py из консоли, не трогая PYTHONPATH.
Таким образом директория проекта тоже попадает в sys.path.Теперь у вас в sys.path два пересекающихся пути.
Таким образом, когда вы импортируете ModulesAndPackages.module_examples.http_get.modules.http_get, будет снова проверен sys.modules, и так как этот модуль имеет совершенно другое имя, он не будет найден в sys.modules, а соответственно будет произведена попытка его поиска в sys.path. Что и успешно происходит, так как PyCharm заботливо подложил путь к корню проекта в sys.path.
Это типичная ошибка новичков, использующих IDE и не имеющих понимания ни работы системы импортов, ни инструментов, с которыми они работают. К сожалению преисполнившись самоуверенности они начинают строчить статьи на Хабр.
А почему же это проблема? Да, потому что два экземпляра модуля это два совершенно разных независимых объекта в памяти, которые имеют независимое состояние. И хоть мы все знаем, что глобальные переменные (глобальные состояние) - зло, рано или поздно это вызовет проблему, которую к слову не легко обнаружить и которая проявляется неочевидным образом.
P.S. Поэтому если уж используете PyCharm, я настоятельно рекомендую отключать эти две опции в настройках запуска:
И учиться работать, не трогая PYTHONPATH или sys.path без необходимости. Для этого всего лишь нужно грамотно огранизовать структуру проекта.
На самом деле, как бы странно не звучало, но невозможно загрузить один и тот же модуль дважды.
Вы крайне серьезно заблуждаетесь.
id(модуля с длинным импортом(по моим словам абсолютным)) = id(модуля с коротким импортом(по моим словам неявно относительным))
Чушь.
У модуля один и тот же путь(C:\file.py, .\file.py и file.py по факту могут указывать на один файл) и хеш.
С чего вы это взяли? При чем тут путь, какой еще хеш? Система импортов в питоне работает совершенно не так.
На самом деле это легко проверить, возьмем ваш же любезно предоставленный репозиторий и любимый PyCharm. Добавим в функцию main всего пару принтов:
import sys
from ModulesAndPackages.module_examples.http_get.modules.http_get import get_dict as absolute
from http_get.modules.http_get import get_dict as relative
def main():
print(absolute is relative)
print(absolute.__module__ is relative.__module__)
print(id(sys.modules["http_get.modules.http_get"]))
print(id(sys.modules["ModulesAndPackages.module_examples.http_get.modules.http_get"]))
# Workable
print(absolute())
print(relative())
Почему так происходит? Да потому модуль загружен в двух экземплярах, в sys.modules существует две записи, указывающие на каждый из загруженных модулей соответственно.
P.S. Может быть стоит сперва изучить букварь по языку, прежде чем статьи на Хабр писать?
Далее в корне создадим main.py файл, в который импортируем наш модуль двумя разными способами(об импортах описано в статье):
from ModulesAndPackages.module_examples.http_get.modules.http_get import get_dict as absolute from http_get.modules.http_get import get_dict as relative
У вас тут не два разных способа импорта (относительный и абсолютный), а один - абсолютный импорт. А тот факт, что вы можете импортировать один и тот же модуль при помощи двух разных абсолютных путей говорит о наличии мусорки в sys.path. При этом один и тот же модуль будет загружен в двух экзмплярах, а это ошибка, которая ведет к тяжелым последствиям.
Относительные импорты в современном питоне начинаются с точки: from .module import name.
А то что вы называете "относительными импортами" существовало в двойке, было объявлено устаревшим и в тройке этот механизм убрали.
Если у Вас есть опыт языка Си и знание английского, то будет достаточно прочитать официальный туториал [1] и дальше уже пользоваться официальной документацией [2].
В противном же случае могу порекомендовать книги Изучаем Python, Марк Лутц (довольно объемное обстоятельное чтиво) или "Укус Питона" (A Byte of Python).
Это не попытки притащить парадигмы из одного языка в другой. src-layout – это довольно распространенный паттерн Python проектов, который призван решать некоторые проблемы при разработке приложений. Подробней об этом можно узнать здесь:
Это не очень плохая идея, это стандартное поведение итераторов ("Протокол итераторов") в питоне. Итератор должен возвращать self в методе __iter__, это нужно для того, чтобы итераторы можно было использовать в циклах или функциях, ожидающих итерируемый объект.
iterator.__iter__()
Return the iterator object itself. This is required to allow both containers and iterators to be used with the for and in statements.
Понимаете, тут вопрос в применимости. Для аутентификации по паролю онлайн (в удалённой системе) необходимо и достаточно хранить необратимые и стойкие к коллизиям солёные хеши.
Вы заблуждаетесь. Вектор атаки на секрет имея хеш, а так же время и ресурсы существует вне зависимости от того онлайн система, осуществляющая обработку данных, или офлайн. Соответственно использование вычислительноемких функций преобразования паролей имеет смысл в обоих случаях.
А вообще в первую очередь следует пользоваться общепринятыми рекомендациями, например OWASP Password Storage Cheat Sheet, вместо того чтобы выдумывать свои схемы.
На мой взгляд лучше уж какой-нибудь учебник по основам сетевого программирования. Из документации библиотеки socket в принципе тоже можно было бы разобраться, но челвоеку, не знакомому с предметной областью, это будет сделать слегка затруднительно. Так как библиотека `socket` это почти прямой враппер над соответствующими системными вызовами, описания этих системных вызовов в документации не полные.
#Получим 1024 байта от первого клиента
a = conn1.recv(1024)
После этого статью можно в принципе дальше не читать. Хрестоматийный пример того как делать не надо.
#Получаем строку от сервера
b = client_socket.recv(1024)
#Печатаем, предварительно раскодировав
print(b.decode("utf-8"))
А здесь еще лучше.
Во-первых socket.recv возвращает не ровно 1024 байта, а до 1024 байта.
Во-вторых нет никакой гарантии что эта функция вернет все сообщение целиком (не забываем что у нас здесь потоковые сокеты). Да и вообще нет никакой гарантии того, что байтовая последовательность, которую вернет sock.recv будет корректной utf-8 последовательностью по этой же причине.
С socket.send ровно та же проблема. То что этот код создает видимость работоспособного всего лишь удачное стечение обстоятельств, не более.
Вишенкой на торте две функции acceptor1 и acceptor2, использующие глобальные паременные и отличающиеся только одной переменной.
А еще лучше использовать специально предназначенный для измерения интервалов, неубывающий, имеющий максимальную доступную точность time.perf_counter() [1]
Человека, незнакомого с языком, вполне закономерно может удивить тот факт, что реализации операторов + и += для некоторых типов могут иметь разную семантику.
Так как во многих языках a += b как-правило является эквивалентом a = a + b, поведение этих операторов должно бы быть консистентным.
В Python же для списка реализация оператора a += b эквивалентна вызову метода a.extend(b) (за исключением присваивания), что описано в документации [1], который работает не только со списками, а с любым iterable.
Кем считается? В чем именно проявляется «неоптимальность»?
pipenv и poetry облегчают ведение проекта, но решают несколько разные задачи.
Кроме того что авторы `pipenv` имели ощутимые проблемы во взаимодействия с комьютнити, проблемы с совместимостью [1], а последние несколько месяцев не ведется никакой активной разработки. Проект фактически умер. Поэтому я бы его не стал использовать или рекомендовать для разработки новых проектов.
`poetry` же, на мой взгляд, весьма перспективный проект, но все еще имеет проблемы с производительностью (ощутимые на некоторых сценариях) [2] и регрессии [3].
В любом случае оба этих проекта так или иначе используют виртуальные окружения.
Конкретно пакет virtualenv действтельно считается устаревшим, так как поддержка виртуальных окружений является частью стандартной библиотеки Python начиная 3.3 [4].
непонятно только, зачем было так переводить вполне логичное слово «экспансия»
полагаю, потому что слово expanse обозначает "a wide and open area of something, especially land or water" [1] и наиболее близким переводом этого слова является пространство [2], в то время как expansion переводится как экспансия [3].
А модель конкурентности построенная на зеленых потоках \ сопрограммах \ горутинах это тоже "многопоточность" по-вашему?
Eventlet -- 2008 год
Gevent -- 2009 год
В Twisted использовались реактор и коллбэки, да и сейчас используются. На asyncio он никогда не переходил.
Почему-то... На самом деле никакой магии нет.
При запуске скрипта
python path/to/script.py
, путь к директории в которой находится запускаемый скрипт добавляется вsys.path
. Дальше никакой магии нет и поиск импортируемых модулей и пакетов происходит при помощиsys.path
. (На самом деле все сильно сложнее, но для понимания работы этого пока будет достаточно).Когда вы импортируете
http_get.modules.http_get
, сперва будет проверен кеш модулейsys.modules
, и если модуль с указанным именем в нем не обнаружен, будет произведена попытка поиска модуля вsys.path
.Пакет
http_get
будет найден непосредственно вsys.path
и все подмодули будут импортированы относительно него. Во время импорта интерпретатор выполняет код модуля, создает объект модуля и создает ссылку на него в словареsys.modules
.Ключем в этом словаре выступает полное имя модуля, значением соответственно ссылка на объект модуля. Таким образом в
sys.modules
появилась новая запись с ключемhttp_get.modules.http_get
.И тут PyCharm подкладывает вам и всем новичкам утку. При запуске при помощи PyCharm, по-умолчанию PyCharm устанавливает переменную окружения
PYTHONPATH
, значением которой передает путь к корню проекта (или точней к "Sources Root").Забавно, что вне PyCharm ваш код просто так работать не будет. Попробуйте запустить свой
main.py
из консоли, не трогаяPYTHONPATH
.Таким образом директория проекта тоже попадает в
sys.path
.Теперь у вас вsys.path
два пересекающихся пути.Таким образом, когда вы импортируете
ModulesAndPackages.module_examples.http_get.modules.http_get
, будет снова проверенsys.modules
, и так как этот модуль имеет совершенно другое имя, он не будет найден вsys.modules
, а соответственно будет произведена попытка его поиска вsys.path
. Что и успешно происходит, так как PyCharm заботливо подложил путь к корню проекта вsys.path
.Это типичная ошибка новичков, использующих IDE и не имеющих понимания ни работы системы импортов, ни инструментов, с которыми они работают. К сожалению преисполнившись самоуверенности они начинают строчить статьи на Хабр.
А почему же это проблема? Да, потому что два экземпляра модуля это два совершенно разных независимых объекта в памяти, которые имеют независимое состояние. И хоть мы все знаем, что глобальные переменные (глобальные состояние) - зло, рано или поздно это вызовет проблему, которую к слову не легко обнаружить и которая проявляется неочевидным образом.
P.S. Поэтому если уж используете PyCharm, я настоятельно рекомендую отключать эти две опции в настройках запуска:
И учиться работать, не трогая
PYTHONPATH
илиsys.path
без необходимости. Для этого всего лишь нужно грамотно огранизовать структуру проекта.Материал для дальнейшего изучения:
https://docs.python.org/3/library/sys.html#sys.modules
https://docs.python.org/3/library/sys.html#sys.path
https://docs.python.org/3/reference/import.html
https://packaging.python.org/en/latest/tutorials/packaging-projects/
Вы крайне серьезно заблуждаетесь.
Чушь.
С чего вы это взяли? При чем тут путь, какой еще хеш? Система импортов в питоне работает совершенно не так.
На самом деле это легко проверить, возьмем ваш же любезно предоставленный репозиторий и любимый PyCharm. Добавим в функцию
main
всего пару принтов:И что же мы видим?
Почему так происходит? Да потому модуль загружен в двух экземплярах, в
sys.modules
существует две записи, указывающие на каждый из загруженных модулей соответственно.P.S. Может быть стоит сперва изучить букварь по языку, прежде чем статьи на Хабр писать?
У вас тут не два разных способа импорта (относительный и абсолютный), а один - абсолютный импорт. А тот факт, что вы можете импортировать один и тот же модуль при помощи двух разных абсолютных путей говорит о наличии мусорки в
sys.path
. При этом один и тот же модуль будет загружен в двух экзмплярах, а это ошибка, которая ведет к тяжелым последствиям.Относительные импорты в современном питоне начинаются с точки:
from .module import name
.А то что вы называете "относительными импортами" существовало в двойке, было объявлено устаревшим и в тройке этот механизм убрали.
Если у Вас есть опыт языка Си и знание английского, то будет достаточно прочитать официальный туториал [1] и дальше уже пользоваться официальной документацией [2].
В противном же случае могу порекомендовать книги Изучаем Python, Марк Лутц (довольно объемное обстоятельное чтиво) или "Укус Питона" (A Byte of Python).
https://docs.python.org/3/tutorial/
https://docs.python.org/3/
Это не попытки притащить парадигмы из одного языка в другой.
src
-layout – это довольно распространенный паттерн Python проектов, который призван решать некоторые проблемы при разработке приложений. Подробней об этом можно узнать здесь:https://docs.pytest.org/en/7.1.x/explanation/goodpractices.html#choosing-a-test-layout-import-rules
https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure
Это не очень плохая идея, это стандартное поведение итераторов ("Протокол итераторов") в питоне. Итератор должен возвращать
self
в методе__iter__
, это нужно для того, чтобы итераторы можно было использовать в циклах или функциях, ожидающих итерируемый объект.https://docs.python.org/3/library/stdtypes.html#typeiter
P.S. А если клиентский код захочет делать tee, для этого есть модуль
itertools
и функцияitertools.tee()
как раз для этой задачи.Вы заблуждаетесь. Вектор атаки на секрет имея хеш, а так же время и ресурсы существует вне зависимости от того онлайн система, осуществляющая обработку данных, или офлайн. Соответственно использование вычислительноемких функций преобразования паролей имеет смысл в обоих случаях.
А вообще в первую очередь следует пользоваться общепринятыми рекомендациями, например OWASP Password Storage Cheat Sheet, вместо того чтобы выдумывать свои схемы.
На мой взгляд лучше уж какой-нибудь учебник по основам сетевого программирования. Из документации библиотеки
socket
в принципе тоже можно было бы разобраться, но челвоеку, не знакомому с предметной областью, это будет сделать слегка затруднительно. Так как библиотека `socket` это почти прямой враппер над соответствующими системными вызовами, описания этих системных вызовов в документации не полные.Нет, это не "он будет слушать два других", это размер беклога. Это означает что до двух других входящих подключений могут ожидать в очереди на прием.
После этого статью можно в принципе дальше не читать. Хрестоматийный пример того как делать не надо.
А здесь еще лучше.
Во-первых
socket.recv
возвращает не ровно 1024 байта, а до 1024 байта.Во-вторых нет никакой гарантии что эта функция вернет все сообщение целиком (не забываем что у нас здесь потоковые сокеты). Да и вообще нет никакой гарантии того, что байтовая последовательность, которую вернет
sock.recv
будет корректной utf-8 последовательностью по этой же причине.С
socket.send
ровно та же проблема. То что этот код создает видимость работоспособного всего лишь удачное стечение обстоятельств, не более.Вишенкой на торте две функции
acceptor1
иacceptor2
, использующие глобальные паременные и отличающиеся только одной переменной.time.perf_counter()
[1][1]: docs.python.org/3/library/time.html#time.perf_counter
+
и+=
для некоторых типов могут иметь разную семантику.Так как во многих языках
a += b
как-правило является эквивалентомa = a + b
, поведение этих операторов должно бы быть консистентным.В Python же для списка реализация оператора
a += b
эквивалентна вызову методаa.extend(b)
(за исключением присваивания), что описано в документации [1], который работает не только со списками, а с любым iterable.[1] docs.python.org/3/library/stdtypes.html#mutable-sequence-types
venv
входит в стандартную библиотеку Python начиная с версии 3.3 и устанавливать сторонний пакет `virtualenv` нет необходимости.pipenv
иpoetry
облегчают ведение проекта, но решают несколько разные задачи.Кроме того что авторы `pipenv` имели ощутимые проблемы во взаимодействия с комьютнити, проблемы с совместимостью [1], а последние несколько месяцев не ведется никакой активной разработки. Проект фактически умер. Поэтому я бы его не стал использовать или рекомендовать для разработки новых проектов.
`poetry` же, на мой взгляд, весьма перспективный проект, но все еще имеет проблемы с производительностью (ощутимые на некоторых сценариях) [2] и регрессии [3].
В любом случае оба этих проекта так или иначе используют виртуальные окружения.
Конкретно пакет
virtualenv
действтельно считается устаревшим, так как поддержка виртуальных окружений является частью стандартной библиотеки Python начиная 3.3 [4].[1] chriswarrick.com/blog/2018/07/17/pipenv-promises-a-lot-delivers-very-little
[2] github.com/python-poetry/poetry/issues/2094
[3] github.com/python-poetry/poetry/issues/2170
[4] docs.python.org/3/library/venv.html
полагаю, потому что слово expanse обозначает "a wide and open area of something, especially land or water" [1] и наиболее близким переводом этого слова является пространство [2], в то время как expansion переводится как экспансия [3].
[1] www.oxfordlearnersdictionaries.com/definition/english/expanse
[2] translate.google.com/#view=home&op=translate&sl=en&tl=ru&text=expanse
[3] translate.google.com/#view=home&op=translate&sl=en&tl=ru&text=expansion