https://habr.com/ru/post/501538/В подавляющем большинстве языков, если во всем исходном тексте программы убрать все отступы, а затем применить автоформатирование исходного текста, то программа останется полностью рабочей и при этом станет оформлена в едином стиле.
Казалось бы в случае с Python такая операция не возможна.
По крайней мере я ни где решения такого полноценного автоформатирования для Python не смог найти. Пришлось решить эту проблему самому.
Вступление
Дальше буду использовать сокращения: ПГ — программа, ПГ-ст — программист, ППГ — подпрограмма (функция/метод) и т.п.
Основные недостатки Python:
1) Python «не минимизирован» по использованию ресурсов и своих данных и т.о. не подходит для написания ПГ, требовательных к использованию ресурсов, таких как мобильные приложения, ПГ низкого уровня (драйвера, резидентные ПГ и т.п.) и т.д.
2) Python медленный и однопоточный ( GIL — Global Interpreter Lock ).
3) В Python программные блоки основаны ТОЛЬКО(!) на отступах. Из-за этого:
- уменьшается «читабильность» ПГ (см.ниже),
- невозможно полноценное автоформатирование исходного текста,
- появляется вероятность иногда очень трудно исправляемых ошибок при случайном и незамеченном ошибочном смещении отступов.
Первый недостаток Python сгладить сложно и тут он просто имеет ограничение в сфере своего использования. Но это естественный момент, т.к. невозможно придумать язык, который был бы максимально эффективен во всех сферах выполняемых задач.
Второй недостаток Python решается тем, что у него прекрасная двусторонняя
интероперабельность с C/C++.
Часто успешный проект развивается довольно стремительно. И сначала сильных требований к быстродействию нет и в Python при необходимости вставляют крошечные вставки на С/С++.
Но по мере расширения использования проекта требования к быстродействию быстро растут и Python все больше начинает выполнять функции вызываемого языка из С/С++. При этом значимость Python в проекте не падает, т.к. он остается более удобным инструментом чем С/С++ при ПГ-нии участков, не требующих большой скорости, которых в большом проекте все-равно много.
На третьем недостатке Python хочу остановиться подробнее.
Все вы знаете, для подавляющего большинства языков очень часто используется автоформат исходного текста.
Т.е. в каком бы стиле и с какими бы отступами ПГ-ст не написал ПГ-му, запускается функция автоформатирования исходного текста, которая приведет весь исходный текст к единому стилю. Для Python казалось бы такое не возможно.
Любой ПГ-ст, написавший проекты в несколько тысяч строк языка, знает, что процесс разработки — это часто не просто придумывание очередного куска ПГ и его набивка, а постоянный «перенос» кусков ПГ-мы то в ППГ, то в общие внешние модули, то между блоками кода.
Тем более, что на практике юзера/заказчики уже на стадии проб готового «скелета» проекта, когда они «потрогают» реальные наброски ПГ-мы, часто начинают довольно значительно «дополнять» постановку задачи, что приводит к сильной корректировке исходного кода.
И тогда в Python в случае сильного редактирования куска чужой(!) (или своей, но которую уже совершенно не помнишь) ПГ, если случайно «зацепил» ( и не заметил этого) при сдвиге/переносе блока лишний участок, который принадлежит другому блоку, то ПГ может остаться полностью работоспособной, но алгоритм ее работы поменяется (читай «ПГ начнет работать не правильно») и найти место такой ошибки в чужой(!) ПГ и потом правильно(!) восстановить отступы иногда бывает очень не просто!
В этом случае можно конечно исходный текст (до изменений) посмотреть, но если уже внес в этом месте много изменений, то придется еще и всю эту цепочку изменений «отматывать».
А вариант, как я для себя убрал третий недостаток я и хочу предложить Вашему вниманию.
Решение проблемы отступов в Python
Отступы в Python формируются следующими командами:
- class
- def
- for
- while
- if
- try
И для удаления зависимости от отступов, для каждой из этих команд я решил взять за правило использовать «закрывающую» команду, которая однозначно будет закрывать блок команд (отступов).
Команды «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 # конец блока
Например, возьмем кусок исходного кода:
f = open(imya_file,'r',encoding='utf-8')
i = 0
for line in f:
#print(line)
tek_str = line.rstrip('\n') #Уберем \n на концах строки
tek_spisok = tek_str.split('\t') #Разбивка строк в список
REGP = tek_spisok[0]
DATA = tek_spisok[1]
NNMT = tek_spisok[2]
tb0.setItem( i, 0, QtWidgets.QTableWidgetItem(REGP) )
tb0.setItem( i, 1, QtWidgets.QTableWidgetItem(DATA) )
tb0.setItem( i, 2, QtWidgets.QTableWidgetItem(NNMT) )
i += 1
f.close()
Случайное удаление отступа в последних командах блока «for» не приводит к ошибке выполнения программы, но приводит к ошибочным результатам! И не просто искать что случилось, если не знаешь точно алгоритм ПГ (например, если это ПГ уволившегося коллеги)!
Вот как здесь:
f = open(imya_file,'r',encoding='utf-8')
i = 0
for line in f:
#print(line)
tek_str = line.rstrip('\n') #Уберем \n на концах строки
tek_spisok = tek_str.split('\t') #Разбивка строк в список
REGP = tek_spisok[0]
DATA = tek_spisok[1]
NNMT = tek_spisok[2]
tb0.setItem( i, 0, QtWidgets.QTableWidgetItem(REGP) )
tb0.setItem( i, 1, QtWidgets.QTableWidgetItem(DATA) )
tb0.setItem( i, 2, QtWidgets.QTableWidgetItem(NNMT) )
i += 1
f.close()
А в нижеуказанном случае случайное удаление отступа сразу выдаст ошибку:
f = open(imya_file,'r',encoding='utf-8')
i = 0
for line in f:
#print(line)
tek_str = line.rstrip('\n') #Уберем \n на концах строки
tek_spisok = tek_str.split('\t') #Разбивка строк в список
REGP = tek_spisok[0]
DATA = tek_spisok[1]
NNMT = tek_spisok[2]
tb0.setItem( i, 0, QtWidgets.QTableWidgetItem(REGP) )
tb0.setItem( i, 1, QtWidgets.QTableWidgetItem(DATA) )
tb0.setItem( i, 2, QtWidgets.QTableWidgetItem(NNMT) )
i += 1
continue
f.close()
Команда «if»
В конце оператора «if» нужно ставить команду «elif 0: pass», а вместо «else» использовать команду «elif 1:».
Т.е. для «if» будет законченный блок команд:
if <> # начало блока
... команды ....
elif <>
... команды ....
elif 1: # вместо "else"
... команды ....
elif 0: pass # конец блока
Например:
ret = 0
find_port = seek_port()
if find_port != 'yes':
#Выведем ошибку,что сканер не найден
okno_info( 'Сканер не найден', font_medium, bg_txt)
ret = 1
Dialog_scan.close()
elif 0: pass
QR_scan_main( 'Ряды', 1 )
Если так, как выше оформить, то в блоке команд «if… elif 0: pass» удаление отступа сформирует ошибку запуска.
Без «elif 0: pass» если потом случайно удалятся отступы в последних строках блока «if», то сначала будешь искать место, из-за которого ПГ неожиданно стала не правильно работать, а потом думать: какие отступы д.б. в блоке, а какие — нет.
ret = 0
find_port = seek_port()
if find_port != 'yes':
#Выведем ошибку,что сканер не найден
okno_info( 'Сканер не найден', font_medium, bg_txt)
ret = 1
Dialog_scan.close()
QR_scan_main( 'Ряды', 1 )
Почему еще на мой взгляд желательно закрывать блоки команд, созданные операторами «for»,
«while», «if» b т.д…
Потому что, когда смотришь на конец завершающихся отступов, но при этом не видишь, где они начинаются, то уже часто не помнишь, какой отступ чем создан.
Тогда конструкции с «continue» и «elif 0: pass» еще (кроме защиты от случайного удаления отступов) позволят указать какой блок чем начат
и написать комментарий к нему.
Например, видишь конец большого блока:
#print("{0}\t{1}".format(key,slovar[key]),end="\n")
zn = str("{0}\t{1}".format(key,slovar[key]))
new_zn = zn.replace("\\r\\n'","")
itog_zn = itog_zn + new_zn[2:] + "\n"
if n == 1:
itog_str = tek_polya + str(itog_zn)
f.write(str(itog_str))
itog_zn = ''
elif n == 10 or len(slovar) == n:
itog_str = str(itog_zn)
f.write(str(itog_str))
itog_zn = ''
n += 1
f.close()
#Копируем файл txt в bak
shutil.copyfile(kat+"\\"+imya_f+'.txt', kat+"\\"+imya_f+'.bak')
#Выведем информационное сообщение
self.okno_info( 'Вы отсканировали '+str(len(slovar))+
' коробки(ок) в '+str(kych)+' ряд(ах)',
self.font_medium, self.bg_txt)
self.Dialog_scan.close()
elif res == 'Выйти(ESC)':
self.Menu.close()
self.Dialog_scan.close()
И сложно вспомнить, что значит каждый уровень отступов.
Но уже гораздо проще, когда видишь вот так:
#print("{0}\t{1}".format(key,slovar[key]),end="\n")
zn = str("{0}\t{1}".format(key,slovar[key]))
new_zn = zn.replace("\\r\\n'","")
itog_zn = itog_zn + new_zn[2:] + "\n"
if n == 1:
itog_str = tek_polya + str(itog_zn)
f.write(str(itog_str))
itog_zn = ''
elif n == 10 or len(slovar) == n:
itog_str = str(itog_zn)
f.write(str(itog_str))
itog_zn = ''
elif 0: pass
n += 1
continue # убираем символов b'код\\r\\n' по словарю
f.close()
#Копируем файл txt в bak
shutil.copyfile(kat+"\\"+imya_f+'.txt', kat+"\\"+imya_f+'.bak')
#Выведем информационное сообщение
self.okno_info( 'Вы отсканировали '+str(len(slovar))+
' коробки(ок) в '+str(kych)+' ряд(ах)',
self.font_medium, self.bg_txt)
self.Dialog_scan.close()
elif 0: pass # варианты "закончили ряд" или "весь товар"
elif 0: pass # Если был спец.коды - обрабатываем
continue # цикл по сканированию кодов
continue # цикл бесконеч., выход - когда "весь товар"
elif res == 'Выйти(ESC)':
self.Menu.close()
self.Dialog_scan.close()
Команда «try»
Тут полная аналогия с «if».
try: # начало блока
... команды ....
except <...>:
... команды ....
except 1: # вместо "else"
... команды ....
except 0: pass # конец блока
Единственная проблема — команда
«finally:». После нее больше нельзя поставить
ни одну их команд текущего try-блока.
Поэтому если есть сильная необходимость в ее использовании, то для сохранения возможности автоформата и защиты от случайного удаления отступов, нужно убрать весь блок команд после «finally:» в ППГ и эту ППГ объявить как ППГ внутри текущей ППГ.
Т.е. текст с «finally:» будет таким:
def my_ppg():
...
return
...
finally:
my_ppg()
...
В этом случае тоже можно спокойно применить автоформатирование и не бояться
случайного удаления отступов.
Также, теперь я думаю, вы уже поняли, что если на Python написать ПГ с применением ВЕЗДЕ(!) вышеуказанных приемов оформления блоков отступов, то легко написать ПОЛНОЦЕННОЕ АВТОФОРМАТИРОВАНИЕ такой ПГ даже при «перекосе» или полном удалении отступов в ней, т.к. для всех блоков команд есть начало и конец блока, не зависящие от отступов.
Заключение
А теперь с улыбкой поставим вопрос так: «Какие недостатки есть у Python, если правильно оформлять блоки отступов, при необходимости писать для него на С/С++ и не применять в мобильных и ресурсокритичных приложениях ?».
Ответ: «Только несущественные недостатки. Т.е. по большому счету — нет.»
И при такой постановке вопроса нам останется только наслаждаться основными достоинствами Python.
- Простота.
- Минимальный цикл тестирования участков: написал-запустил-проверил.
- Мощность — библиотеки/фреймворки для Python есть «на любой вкус и цвет».
Эти три кита вместе дают, на мой взгляд, практически идеальный вариант языка (не забываем, что при условии вышеуказанной постановки вопроса!).