Как стать автором
Обновить

Отступы в Python — вариант решения

Время на прочтение8 мин
Количество просмотров39K
В подавляющем большинстве языков, если во всем исходном тексте программы убрать все отступы, а затем применить автоформатирование, то программа останется полностью рабочей и при этом станет оформлена в едином стиле.

Казалось бы, в случае с Python такая операция не возможна.

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


Вступление


Уважаемый читатель!
Если для тебя близко утверждение:
1) Что программирование — это высокое искусство.
2) Что при программировании на каком-либо языке нужно по возможности максимально использовать всю мощь и разнообразие этого языка для минимализации исходного кода.
3) Что при программировании нужно показать в исходном тексте программы свой высокий уровень, чтобы про твою программу ни кто не мог сказать, что она «тупая».

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

Уважаемый читатель!
Если для тебя уже близко утверждение:
1) Что программирование — это рутинная, довольно однообразная работа, похожая просто на рытье бесконечной извилистой канавы.
2) Что при программировании на каком-либо языке нужно использовать определенный минимально-оптимальный набор команд языка (возможно отбросив большую часть его команд), чтобы в твоей программе легко(!) разобрался даже программист-юниор.
3) Что при программировании твоя программа должна быть в определенной степени «тупой». Чтобы после того, как ты ее внедрил, ты мог заняться новым проектом и спокойно поставить программиста-юниора, участвовавшего в проекте, на ее сопровождение и доработку под регулярные небольшие новые требования заказчика.

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

Основные недостатки Python:

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

2) Python медленный и однопоточный ( GIL — Global Interpreter Lock ).

3) В Python программные блоки основаны ТОЛЬКО(!) на отступах. Из-за этого:

  • уменьшается «читабильность» программ (см.ниже);
  • невозможно полноценное автоформатирование исходного текста;
  • появляется вероятность возникновения ошибки при случайном и незамеченном смещении отступов, которую иногда бывает очень трудно найти и исправить.

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

Второй недостаток Python решается тем, что у него прекрасная двусторонняя интероперабельность с C/C++.

Часто успешный проект развивается довольно стремительно. И сначала высоких требований к быстродействию нет, и в Python при необходимости используют крошечные вставки на С/С++.
Но по мере развития и расширения проекта, требования к быстродействию соответственно возрастают, и Python все чаще начинает выполнять функции вызываемого языка из С/С++. При этом, роль самого Python не уменьшается, т.к. при программировании участков, не требующих высокой скорости выполнения (а их обычно довольно много в крупных проектах), Python является более удобным инструментом, чем С/С++.

А для третьего недостатка Python я хотел бы предложить свой вариант решения.

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

Т.е. с какими бы отступами ни была написана программа на этом языке, при запуске автоформатирования все отступы будут приведены к стандартному виду. Для Python такое кажется невозможным.

Любой программист, написавший проекты в несколько тысяч строк языка, знает, что процесс разработки — это не просто придумывание очередного куска программы и его наполнение, а постоянное перемещение участков кода то в подпрограмму, то между блоками программы, то в общие внешние модули и т.п.

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

И тогда в Python, в случае значительного изменения десятков кусков чужой(!) программы (или же своей, но которую уже совершенно не помнишь), стоит при переносе блока случайно зацепить лишний участок кода, принадлежащего другому блоку, то программа может остаться полностью работоспособной, но алгоритм ее работы поменяется (читай «программа начнет работать неправильно»), и найти место такой ошибки в чужой программе и потом правильно восстановить отступы иногда бывает очень непросто!

В этом случае можно, конечно, посмотреть исходный текст (до изменений), но если уже внес в этом месте много корректировок, то придется «отматывать» всю эту цепочку исправлений.

Решение проблемы отступов в Python


Отступы в Python формируются следующими командами:

- class
- def

- for
- while

- if

- try

- with

И для удаления зависимости от отступов, для каждой из этих команд я решил взять за правило использовать «завершающую» команду, которая однозначно будет закрывать блок команд (отступов).

Команды «class» и «def»


Для команд class/def обычно нет проблемы с завершением блока отступов, т.к. блок закрывается новой командой class/def.

Единственный случай — это наличие одной или нескольких подпрограмм, объявленных внутри другой подпрограммы/метода.

def ppg_1():
    def ppg_2():
        ... команды ppg_2 ...
    def ppg_3():
        ... команды ppg_3 ...
        ... команды ppg_3 ...
        ... команды ppg_3 ...
    ... команды ppg_1 ...

Тогда, если случайно сместить блок команд последней внутренней подпрограммы, то он сольётся с командами подпрограммы/метода, в которой объявлена эта внутренняя подпрограмма.

def ppg_1():
    def ppg_2():
        ... команды ppg_2 ...
    def ppg_3():
        ... команды ppg_3 ...
    ... команды ppg_3 ...
    ... команды ppg_3 ...
    ... команды ppg_1 ...

В этом случае в конце этой внутренней подпрограммы/метода нужно просто поставить «return», что и будет четко обозначать окончание блока её команд.

def ppg_1():
    def ppg_2():
        ... команды ppg_2 ...
    def ppg_3():
        ... команды ppg_3 ...
        ... команды ppg_3 ...
        ... команды ppg_3 ...
        return
    ... команды ppg_1 ...

Команды «for» и «while»


В конце оператора «for» и «while» нужно ставить «continue».

Т.е. команда будут выглядеть так:

for  <...> :             # начало блока
    ... команды ....
    continue             # конец блока

и

while  <...> :           # начало блока
    ... команды ....
    continue             # конец блока

Например:
        ... команды ...

        for i in range(10):
            ... команды for ...

            ... команды for  ...

            ... команды for  ...

        ... команды ...

Случайное удаление отступа в последних командах блока «for» не приводит к ошибке выполнения программы, но приводит к ошибочным результатам! И очень не просто найти такой сбой, если точно не знаешь алгоритм программы (например, если это программа уволившегося коллеги)!

Вот как здесь:
        ... команды ...

        for i in range(10):
            ... команды for ...

        ... команды for  ...

        ... команды for  ...

        ... команды ...

А в нижеуказанном примере случайное удаление отступа сразу выдаст ошибку:
        ... команды ...

        for i in range(10):
            ... команды for ...

            ... команды for  ...

            ... команды for  ...

            continue

        ... команды ...

Команда «if»


В конце оператора «if» нужно ставить команду «elif 0: pass», а вместо «else» использовать команду «elif 1:».

Т.е. для «if» будет законченный блок команд:

if <>                      # начало блока
    ... команды ....
elif <>
    ... команды ....
elif 1:                    # вместо "else"
    ... команды ....
elif 0: pass               # конец блока

Например:
        ... команды ...

        if  result != -1 :

            ... команды if ...

            ... команды if ...

            ... команды if ...

        elif 0: pass

        ... команды ...

Если оформить так, как показано выше, то в блоке команд «if… elif 0: pass» удаление отступа сформирует ошибку запуска.

Без «elif 0: pass», если потом случайно удалятся отступы в последних строках блока «if», то сначала будешь искать место, из-за которого программа неожиданно стала неправильно работать, а потом думать: какие отступы должны быть в блоке, а какие — нет.
        ... команды ...

        if  result != -1 :

            ... команды if ...

        ... команды if ...

        ... команды if ...

        ... команды ...

Почему еще, на мой взгляд, желательно закрывать блоки команд, созданные операторами «for»,
«while», «if» и т.д…

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

Тогда конструкции с «continue» и «elif 0: pass», помимо защиты от случайного удаления, позволят также указать какой блок чем начат и написать комментарий к нему.

Например, видишь конец большого блока:

                            ... команды ...

                            ... команды ...

                            ... команды ...

                            ... команды ...

                        ... команды ...

                        ... команды ...

                        ... команды ...

                        ... команды ...

    elif result == 1 :

        ... команды ...

        ... команды ...

        ... команды ...

        ... команды ...

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

Но уже гораздо проще, когда это выглядит вот так:

                            ... команды ...

                            ... команды ...

                            ... команды ...

                            ... команды ...

                            continue    # убираем символов b'код\\r\\n' по словарю 

                        ... команды ...

                        ... команды ...

                        ... команды ...

                        ... команды ...

                    elif 0: pass     # варианты "закончили ряд" или "весь товар"

                elif 0: pass         # Если был спец.коды - обрабатываем

                continue             # цикл по сканированию кодов

            continue                 # цикл бесконеч., выход - когда "весь товар"
                      
    elif result == 1 :

        ... команды ...

        ... команды ...

        ... команды ...

        ... команды ...

Команда «try»


Тут полная аналогия с «if».

try:                   # начало блока
    ... команды ....
except <...>:
    ... команды ....
except 1:              # вместо "else"
    ... команды ....
except 0: pass         # конец блока

Единственная проблема — команда «finally:». После нее больше нельзя поставить ни одну из команд текущего try-блока.

Поэтому если есть необходимость в ее использовании, то для сохранения возможности автоформата и защиты от случайного удаления отступов нужно убрать весь блок команд после «finally:» в локальную подпрограмму (т.е. объявить её как подпрограмму внутри текущей подпрограммы).

Т.е. текст с «finally:» будет таким:

    def my_ppg():
        ...
        return

    ...

    finally:
        my_ppg()

    ...

В этом случае тоже можно спокойно применить автоформатирование и не бояться
случайного удаления отступов.

Команда «with»


Для «with» в Python нет никаких дополнительных команд, которые могли бы послужить концом блока. Поэтому ситуация с «with» аналогична ситуации с «finally».
Т.е. либо мы все команды, выполняющиеся в блоке оператора «with», выносим в локальную подпрограмму, либо… А вот дальше я скажу жутко кощунственную вещь: "… либо просто не нужно его никогда использовать."

Дело в том, что «with», с одной стороны, всего лишь «обертка» для существующих команд Python (т.е. ее всегда можно заменить аналогичным набором команд), с другой стороны, практика показала, что для программистов-юниоров этот контекстный менеджер трудноват для полноценного освоения. И поэтому, если хочешь, чтобы после тебя внедренный проект спокойно сопровождал программист-юниор, то не нужно в проекте использовать команды, затрудняющие его работу.

Заключение



Я думаю вы уже поняли, что если на Python написать программу с применением ВЕЗДЕ(!) вышеуказанных приемов оформления блоков отступов, то достаточно легко написать ПОЛНОЦЕННОЕ АВТОФОРМАТИРОВАНИЕ такой программы даже при «перекосе» или полном удалении отступов в ней, т.к. для всех блоков команд есть начало и конец блока, не зависящие от отступов.

А теперь с улыбкой поставим вопрос так: «Какие недостатки есть у Python, если правильно оформлять блоки отступов, при необходимости взаимодействовать с С/С++ и не применять Python в мобильных и ресурсокритичных приложениях?».

Ответ: «Только несущественные недостатки. Т.е. по большому счету — нет.»

И при такой постановке вопроса нам останется только наслаждаться основными достоинствами Python.

  1. Простота.
  2. Минимальный цикл тестирования участков: написал-запустил-проверил.
  3. Мощность — библиотеки/фреймворки для Python есть «на любой вкус и цвет».

Эти три достоинства вместе дают, на мой взгляд, практически идеальный вариант языка (не забываем, что это только при соблюдении условий, указанных в вопросе выше!).
Теги:
Хабы:
-18
Комментарии32

Публикации

Изменить настройки темы

Истории

Работа

Python разработчик
135 вакансий
Data Scientist
62 вакансии

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн