На сегодняшнем занятии мы с вами поговорим про модули в Python. Что же это такое?
В принципе модулем можно назвать любой файл Python , который содержит код. Например, сейчас открыта папка с учебными проектами, и здесь есть файл «module1.py» (Рис.1). Любой другой файл с расширением «.py» здесь можно назвать модулем.
Рис.1
Для чего это нужно? Представим такую ситуацию: мы делаем очень большой проект и держать все функции в одном месте со временем становится очень проблематично, потому что вносить изменения в код становится сложнее, ориентироваться по коду становится также сложнее из-за увеличения объёмов, то есть количества строчек в коде, соответственно, начинается каша. Мы начинаем путаться, и это не всегда хорошо. Поэтому мы можем какие-то моменты, которые отвечают за определённую часть программы вынести в отдельный файл и подключать его к нашей программе, использовать возможности из другого файла. Плюс в перспективе это нам позволяет сделать многомодульную структуру проекта, где в случае, если у нас, скажем так, произойдёт какая-то ошибка, у нас не перестанет работать код целиком, а, возможно, перестанет работать только какая-то функция в определённом модуле. И нам не составит труда туда залезть что-то подправить и это дело использовать. И плюс ко всему мы можем модули перекидывать с одного проекта на другой. То есть мы можем написать какой-то функционал, подразумеваем, может быть, какие-то математические функции, которые будут выполнять определённые вычисления, перенести их в другой проект и спокойно там использовать.
Давайте рассмотрим на примере, что можно делать с этими модулями. У нас сейчас есть модуль 1. Это наш главный модуль. Здесь давайте запишем какой-нибудь текст: допустим «Привет я из модуля 1» (Рис.2).
Рис.2
Запускаем и видим вот такую историю: у нас здесь идёт путь до интерпретатора (Рис.3). Здесь путь непосредственно до нашего скрипта, который мы запускаем (Рис.4).
Рис.3
Рис.4
Мы запустили файл «module1» и увидели результат работы «Привет я из модуля 1» (Рис.5).
Рис.5
Давайте создадим в рабочей директории ещё один файл Python. Назовём его «module2» (Рис.6, Рис.7)
Рис.6
Рис.7
Здесь во втором модуле напишем какую-нибудь функцию. Допустим «say_hi». Далее «print (Привет я из функции во втором модуле)». Давайте создадим переменную «а=5», «b=10» тоже, чтобы продемонстрировать работу (Рис.8). И пока на данном этапе этого нам хватит
Рис.8
Возвращаемся в «module1». Давайте сделаем вот таким вот образом (Рис.9). Здесь у нас открыто два файла: слева «module1», справа «module2».
Рис.9
Допустим, мы хотим использовать возможности, какие-то команды, может, переменные, функции, которые прописаны во втором модуле. Чтобы использовать второй модуль внутри нашего первого, нам его нужно импортировать. Раньше мы с вами уже импортировали какие-то библиотеки, и здесь мы можем точно так же поступить, только написать имя модуля, который мы хотим подключить. Сейчас мы подключили «module2» к пространству имён «module1» (Рис.10). И через это имя мы будем получать доступ к значениям внутри нашего «module2».
Рис.10
Если мы воспользуемся с вами командой «dir», которая позволяет посмотреть на атрибуты внутри какого-то объекта. Мы здесь увидим, что у нас есть целая куча атрибутов с двойным подчёркиванием и в самом конце те значения, которые мы сами своими руками добавили во второй модуль, то есть это переменная «а», переменная «b», функция «say_hi» (Рис.11, Рис.12).
Рис.11
Рис.12
И в принципе это означает, что мы можем обращаться к этим переменным через имя «module2», либо же к функции.
Давайте попробуем. Возьмём наш «module2» и через точку достанем оттуда, например, переменную «a». Запускаем «module1» и видим то, что у нас здесь текст «Привет я из модуля 1»- все работает, и число 5 (Рис13). Число 5 у нас хранилось переменной во втором модуле, и мы спокойно можем к нему обращаться.
Рис.13
То же самое работает и с вызовом функции. Если мы вызовем нашу функцию «say_hi» из второго модуля, здесь увидим «Привет я из функции во втором модуле», то есть у нас это работает (Рис.14).
Рис.14
По этому имени «module2» мы получаем доступ к именам, которые у нас находятся во втором модуле. В этом и заключается вся особенность. То есть мы написали какой-то код и можем его спокойно импортировать куда захотим. То есть мы сейчас его подключили к нашему первому модулю. Но здесь нужно быть готовым к нескольким моментам. Ваши программы растут, увеличивается объём кода в них, соответственно, увеличивается количество импортируемых файлов. Что может произойти?
Во- первых, у вас длинное название модуля, такое, которое тратит очень много времени на написание. Вы много, допустим, оттуда используете значений каких-то, может, переменных или функций. И вам получается каждый раз для того, чтобы вызвать какое-то значение или функцию из этого модуля, нужно писать его имя, ставить точку и уже, соответственно, вызывать нашу функцию.
Есть возможность, во-первых, добавить псевдоним для этого модуля. Мы можем добавить такой оператор «as» и написать, как мы будем обращаться к нашему «module2» (Рис.15). То есть фактически мы даём новое имя, с помощью которого мы будем ссылаться на значение в другом файле, в другом модуле. Мы написали «as m2», тем самым облегчили себе жизнь.
Рис.15
То есть мы пишем уже не «module2», а пишем «m2» и точно также можем вызывать те функции, доставать оттуда какие-либо значения, и у нас ничего не собьётся (Рис.16). С этим никаких проблем у нас не возникнет. Это ещё один способ, как мы можем подключать какой-то модуль и давать ему название.
Рис.16
Но иногда нам необходимо, допустим, подключить из второго модуля какие-то элементы к нашему первому, скажем так, основному модулю, включив их в глобальное пространство имён. То есть чтобы мы могли использовать функции и значения, не используя префикс с именем нашего файла. Чтобы это сделать, нам нужно использовать немного другую конструкцию для импорта. То есть мы должны написать слово «from», указать, откуда мы хотим взять, то есть из «module2 import». После слова «import» нужно указать, что мы хотим импортировать, например, «а, b, say_hi» (Рис17).
Рис.17
То есть сейчас фактически мы вытащили все из «module2» и можем вызывать эти функции. Можем вызывать, например, обращаться точнее, к нашим переменным, не используя префикс. То есть здесь вижу то, что я из функции во втором модуле, здесь 5, здесь 10 (Рис.18).
Рис.18
То есть мы взяли и выборочно импортировали какие-то элементы из другого файла. В нашем случае получились все элементы, которые там есть. Такую запись можно заменить звёздочкой «*» (Рис.19). То есть это будет обозначать то, что мы хотим импортировать все.
Рис.19
Здесь есть несколько нюансов. Когда мы импортируем все, соответственно, у нас каждая функция, каждая переменная берётся из этого модуля, и мы не всегда можем уследить за тем, что у нас каждое имя будет использоваться, скажем так, единожды и существовать только в одном экземпляре.
Допустим, в нашей программе будет тоже переменная «а», и в нашем модуле есть переменная «а». В таком случае, если мы будем обращаться к переменной «а», у нас в любом случае возникнет конфликт: либо мы будем обращаться к переменной «а» из нашего основного файла, либо же к переменной из импортируемого модуля. Сами посудите, не стоит с собой брать все. Допустим, вы знаете, что вам для выполнения вашей задачи нужен только один элемент. Зачем вам с собой тащить все в вашу программу. Хотя фактически, когда мы импортируем какой-то модуль, мы все равно тащим все. На самом деле при подключении какого-либо модуля интерпретатор Python считывает всю информацию с этого файла. То есть он пробегает по каждой строчке в коде, начиная с первой, заканчивая последней, и все это дело обрабатывает. Это можно посмотреть.
Допустим, если мы вот здесь с вами напишем функцию «print» и выведем просто «привет», а здесь у нас идёт импорт всего. Запускаем (Рис.20).
Рис.20
Видим в самом начале «привет», потому что первым делом наш интерпретатор начал импортировать второй модуль, и прежде чем он его импортирует, он сначала считывает всю информацию из него. То есть он обработал создание функции, создание переменных и вывел, конечно же, «print» нам. И это будет работать при любом импорте: импортируете вы какой-то отдельный элемент. Например, мы хотим только из «module2» импортировать одну переменную «a». Запускаю (Рис.21). Здесь видим «привет», то есть все равно эта функция сработала.
Рис.21
А дальше мы уже видим ошибки, то, что у нас «b» теперь не видно и «say_hi» тоже теперь не видно. Это не беда. Но, тем не менее, здесь у нас полностью все начинает срабатывать.
Обычно, если у вас есть в другом модуле какой-то вызов функции или какие-то действия, которые выполняются, скажем так, при запуске этого скрипта, но вы хотите, чтобы в тот момент, когда вы этот файл будете использовать для импортирования в какой-то другой файл, вы не хотите, чтобы какие-то элементы в вашем файле выполнялись, то вы можете использовать такую интересную конструкцию, которую многие, наверное, видели, когда не убирали галочку при создании нового проекта. Это в конце вашего файла запись вот такого рода «if __name__ == ’__main__’». Здесь запускается какая-то функция «main» (Рис.22).
Рис.22
Данная запись позволяет нам выполнить код, когда мы запускаем данный файл, вот как скрипт, прям его. То есть сделаем, например, «def main»- главная функция, это входная точка в нашу программу, что в ней будет происходить. И в этой программе, в этой функции, точнее, в этой входной точке у нас будут создаваться две переменные, выводиться текст «Привет» (Рис.23).
Рис.23
Что нам это даёт? Смотрите, когда мы запускаем «module2», то есть видим то, что здесь запускаем второй файл, видим этот самый текст «Привет» (Рис.24).
Рис.24
Если вы обратите внимание, что мы хотим вывести, допустим, имя «name» получим «main» в ответ (Рис.25).
Рис.25
Здесь есть такое условие: если «name» у нас «main», мы запускаем главную функцию. Тот скрипт, который мы запускаем, у нас всегда будет иметь имя «main», вне зависимости от того какое у него название файла. То есть он у нас называется «module2», но мы запускаем тот файл и имя у нас все равно«main».
Если мы будем импортировать этот модуль в другой файл, он у меня уже не будет называться «main». То есть вот давайте здесь импортируем все «*» или давайте сделаем вот так: попробуем «import module2 as m2». Здесь просто выведем «print(module2.__name__)». Остальные «print» уберем. У нас здесь не «module2» уже, а «m2» (Рис.26).
Рис.26
Запускаем и видим здесь module2, то есть второй скрипт уже не является «main» (Рис.27).
Рис.27
Видим, что запускаем «module1» и имя у вот этого скрипта нашего у «module2» является «module2» (Рис.28).
Рис.28
Соответственно, вот этот код, у нас вот это вот условие, где имя обязательно должно быть «main», не выполняется (Рис.29).
Рис.29
То есть, если вам нужно избавить себя и будущих пользователей ваших модулей от незапланированных, скажем так, вызовов каких-то функций или значений, внутри модуля можно добавить вот такую конструкцию «if _ _name_ _ == ’_ _main_ _’». И уже, соответственно, там прописывают логику, которая должна срабатывать, если файл вы будете запускать. Либо же эта часть не будет срабатывать, когда мы используем данный файл, как просто расширение функционала какого-то другого файла в другом модуле. Но здесь задумка такая то, что при импорте какого-то файла у нас интерпретатор полностью пробегается по всем этим значениям. Здесь ещё обратите внимание, у нас переменная «a» и «b» создаётся только тогда, когда второй модуль является у нас главным, скажем так, исполняемым скриптом. Получается, что даже если мы импортируем «m2», мы оттуда не сможем достать «a», не сможем достать «b», потому что их не существует (Рис.30).
Рис.30
У нас есть только описание функции, например, «main». Мы можем её вызвать, тогда там будут у нас какие-то значения, но изначально они недоступны (Рис.31).
Рис.31
То есть вот эти две переменные будет видно только в том случае, если мы будем запускать наш «module2», как исполняемый файл (Рис.32).
Рис.32
Это что касается особенностей в запуске, а про возможности импорта мы с вами уже поговорили. То есть это есть первый способ «import», когда мы пишем имя какого-то модуля, и можем через это имя обращаться к его пространству имён и вызывать какие-то переменные, функции и так далее (Рис.33).
Рис.33
Либо мы можем импортировать что-то из другого файла, например, «from module2 import a» или давайте«from module2 import say_hi», и тогда нам не придётся писать префикс с именем модуля 2, потому что мы, грубо говоря, функцию «say_hi» добавили в глобальное пространство имён нашего файла «module1» (Рис.34).
Рис.34
Здесь есть ещё особенность. Когда мы что-то импортируем, мы точно также можем дать этому новое название. Например, функции «say_hi» мы дали название «sh». Теперь, если мы хотим вызвать эту функцию, будем писать уже не «say_hi», а «sh» (Рис.35).
Рис.35
Все эти разные импорты могут пригодиться вам в ваших больших будущих проектах, потому что вы будете использовать библиотеки, будете использовать какие-то фреймворки, где очень много импортов вам потребуется для реализации какой-то задачи. И вы не можете гарантировать себе того, что какая-то из функций в этих модулях будет иметь, скажем так, только уникальное название. То есть вы не будете уверены в том, что ваши функции, ваши названия переменных не совпадают с названиями функций и переменных в других модулях. Поэтому есть возможность давать новые имена, импортировать либо с указанием префикса, либо с добавлением в глобальное пространство имён, то есть без указания префикса. Это все нужно для удобства, чтобы, во-первых, у вас не было конфликта, связанного с именами. И чтобы у вас не было, скажем так, какой-либо путаницы, если у вас какие-то, например, длинные названия, потому что и такое тоже бывает. Все это сделано под нас.
В принципе, это все, что у нас касается модулей. Главное понимать, что модуль это просто файл , который содержит какой-то код на языке Python, может его даже вообще не содержать (но тогда зачем его импортировать), который находится внутри вашего проекта. То есть вы по сути, можете использовать возможности каждого файла, который у вас создан в рабочей вашей директории. И запомнить способы импортов и что нужно будет сделать, если у вас какие-то функции или переменные имеют одинаковые названия. Единственное что хотелось бы добавить, если у вас всё-таки встречаются какие-то одинаковые имена, и вы никак не можете избавиться от этих одинаковых имён и хотите знать, в каком порядке, в какой момент, что у вас будет использоваться, нужно запомнить одну простую вещь: то, что было позже в вашей программе, то и будет использоваться.
Что это значит? Вот есть у нас здесь из модуля 2 функция, мы импортировали «sh». Сейчас мы её вызываем, видим «Привет я из функции во 2 модуле» (Рис.36).
Рис.36
Если мы в своём файле создадим функцию «sh», напишем «Я из модуля 1» и вызовем, то здесь уже будет «я из модуля 1», а не «sh», который мы импортировали из второго модуля (Рис.37).
Рис.37
Вот этот вызов, это определение функции у нас находилось позже. То есть мы сначала импортировали, а потом, грубо говоря, заменили, но делать так не рекомендуется (Рис.38).
Рис.38
Старайтесь придумывать уникальные имена и следить за тем, чтобы в вашей программе было меньше всего дубликатов.