скрипт (англ. script) — так часто называют программы на интерпретируемых языках, представляющие из себя простые последовательности команд, которые компьютеру нужно выполнить. Часто языки, которые максимально упрощают написание скриптов и их запуск, называют "скриптовыми языками" или же "языками для написания сценариев"

Скрипты на Python

Python отличный скриптовый язык:
последовательность команд в простых сценариях не нужно никак оформлять и запускать скрипты максимально просто — мы просто пишем команды одну за другой в файл:

# file <script.py>
print('Hello, world!')
print('This is a python-script!')

а затем просто вызываем интерпретатор с полученным файлом на входе:

$ python3 script.py
Hello, world!
This is a python-script!

Написание скриптов — отличная отправная точка для тех, кто только начинает знакомиться с программированием!

Скрипты и shebang

В unix-подобных операционных системах (macOS, Linux, BSD etc) командные оболочки умеют запускать скрипты на любых языках, в т.ч. и на Python, если эти скрипты сообщают оболочке, какой интерпретатор нужно вызывать для выполнения сценария. Интерпретатор указывается специальной строкой в самой первой строчке файла скрипта, которая называется shebang, от названий первых двух символов такой строчки:
# называется "sharp",
а ! - "bang!".

Типичный shebang выглядит так:

#!/usr/bin/python3

где после символов #! идёт путь до интерпретатора. Командная оболочка при запуске скрипта, содержащего shebang, читает первую строку и пробует запустить указанный интерпретатор. Если скрипту с указанным shebang дать права на исполнение, то интерпретатор в командной строке можно будет не указывать:

$ cat script.py
#!/usr/bin/python3
print('Hello!')

$ chmod +x script.py

$ ./script.py
Hello!

В работе програмист может использовать разные версии Python, да и путь к интерпретатору будет отличаться от /usr/bin в виртуальных окружениях! В этом случае чтобы скрипт запускался всегда с нужной версией Python нужно всего лишь не указывать путь до команды python напрямую, а использовать программу env.

Эта программа умеет находить и запускать программы с учётом переменных окружения и, т.к. при активации виртуального окружения модифицируется переменная $PATH, то env будет запускать именно ту версию интерпретатора, которая нам нужна (она просто найдётся раньше, т.к. путь до исполняемых файлов окружения добавляется в начало $PATH).

самый правильный способ указывать shebang в проектах на python:

#!/usr/bin/env python3
print('Hello!')

Путь до env всегда располагается именно в /usr/bin/ и не встречается в нескольких версиях.

Импортирование скриптов

Давайте смоделируем некий исходный скрипт:

# file <first_script.py>
        
        def greet(who):
            print('Hello, {}!'.format(who))
        
        greet('Вова')
        greet('Алла')
        

Далее — новый скрипт, в котором мы хотим переиспользовать функцию greet из первого модуля (скрипты — тоже модули):

# file <second_script.py>
        
        from first_script import greet
        
        greet('Витя')
        

Запустим первый скрипт, а затем — второй (оба файла расположены в текущей директории):

$ python3 first_script.py
        Hello, Вова!
        Hello, Алла!
        $ python3 second_script.py
        Hello, Вова!
        Hello, Алла!
        Hello, Витя!
        

Мы видим, что при выполнении второго скрипта выполнился и первый, хотя мы всего лишь импортировали из него одну функцию! Поскольку файл первого скрипта содержит не только определения, но и непосредственные действия (statements), то при загрузке файла эти действия будут выполнены.

Выходит, нам нужно как-то различать ситуации когда

  1. модуль выполняется как скрипт (выполняем побочные действия),
  2. модуль или его содержимое импортируются (не выполняем побочные действия).

Для этого нам понадобится специальная переменная

Специальная переменная __name__

Машинерия импортирования при загрузке модуля в первый раз (первый для текущего запуска интерпретатора) добавляет в этот модуль несколько переменных специального вида. Этих переменных довольно много, но нам пока интересна одна — переменная __name__.

такие имена часто встречаются в Python-коде и как правило имеют какой-то специальный смысл.

Что же хранит переменная __name__ в каждом конкретном случае?

Глядя на значение этой переменной, можем отличать "запуск" от импортирования.

перепишем наш примерfirst_script.py с применением этой переменной:

# file <first_script.py>
        
        def greet(who):
            print('Hello, {}!'.format(who))
        
        if __name__ == '__main__':
            greet('Вова')
            greet('Алла')
        

Функция main

В теле условия if __name__… у нас перечислен набор действий, которые выполняются при запуске скрипта. Со временем таких действий может стать достаточно много. Поэтому существует соглашение: в теле условия if __name__… делают всего один вызов функции без аргументов main, которую объявляют выше в этом же модуле (само условие принято располагать в самом конце модуля скрипта).

С учётом всех описанных рекомендаций финальная версия скрипта first_script.py будет выглядеть так:

#!/usr/bin/env python3

def greet(who):
print('Hello, {}!'.format(who))

def main():
greet('Вова')
greet('Алла')

if __name__ == '__main__':
main()

Такой скрипт можно

Запускаемые пакеты

Рассмотрим немного экзотический, но всё же встречающийся случай — запуск пакета. Могло бы показаться, что раз при загрузке пакета всегда загружается модуль __init__.py, то и функцию main, и условие нужно располагать в нём. Но авторы по ряду причин решили реализовать запуск пакетов несколько иначе: при загрузке пакета пред запуском ищется модуль __main__.py и выполняется, как скрипт. Здесь мы не будем углубляться в причины, побудившие авторов языка сделать именно так, и просто запомним, что исполняемые пакеты всегда содержат скрипт __main__.py.

Когда же может понадобится запуск пакета? Сходу можно представить такой пример. Пусть мы имели один небольшой скрипт. Со временем кода в нём становилось всё больше — настолько много, что этот скрипт стало совершенно невозможно поддерживать. Мы решили превратить один модуль в пакет, содержащий несколько. Но как такой пакет в дальнейшем запускать? Вот для этого мы и можем использовать модуль __main__.py!