При абсолютном импорте 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