Основы программирования в Python

Материал подготовил: Виталий Евтушенко, НИУ ВШЭ

Code Style, Variable scopes and Namespace, Testing and Debugging basics, Exception handling and Error Rising

Coding style (Стандарт оформления кода)

Commercial Photography

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

Один из стандартов: стандарт оформления кода. Также есть стандарты документации, стандарты используемых библиотек. Почти на всё есть свой стандарт, а иногда еще и внутренняя/локальная/корпоративная договоренность (конвенция).

Зачем нужен Coding style?

  • В целом, это очень утилитарная идея.
  • Ограничение на количество символов в строке - максимальная возможная длина одного монитора, чтобы не использовать scroll bar (полосу прокрутки);
  • Осмысленные имена переменных (Meaningful variables) ‒ чтобы другой человек (а также разработчик, который уже через несколько дней ‒ другой человек) понимал, что делает переменная. Пример: переменная today_temp вместо A или переменная number_of_revolution_per_year_in_developing_countries_first_database вместо переменной с названием B;
  • Пробелы ‒ максимизации для читаемости кода;
  • Отступы vs Пробелы - для отсутствия проблем с компиляцией.
  • С его помощью человек, впервые увидевший Ваш код, может потратить меньше времени на понимание кода, если он выполнен согласно стандарту ("согласно стандарту" - примерно равно согласно "здравому смыслу")
  • Возможно, coding style documents не всегда написаны кровью (по аналогии с техникой безопасности и правилами дорожного движения), но написаны ценой человеко-часов, которые не стоит отдавать вновь и стоит экономить.

Что почитать?

Каждый объект в Python'е существует в своём scope (область видимости). Есть несколько областей видимости Commercial Photography

Как это выглядит на практике? Commercial Photography

Scopes rules

  • Each name exists in a certain scope.
  • When entering a function a new local scope is created.
  • After exiting a function its local scope (with all the names in it) is deleted.
  • When referencing a name the Python interpreter first searches the local scope, then the global scope and finally the built-in scope. The complete picture also includes an Enclosing scope - check Python resources.

Что почитать?

Пример #1. На этом примере видно, как интерпретатор Python'а сначала ищет переменную a в local scope, не находит и обращается к глобальной переменной a, плюсуя к ней 5.

In [1]:
def function1():
    return a + 5


a = 5
print(f'function results is {function1()} and a is still {a}')
function results is 10 and a is still 5

Пример #2. А здесь мы явно говорим, что следует использовать переменную a из global scope

In [2]:
def function1():
    global a
    a = a + 5
    return a 


a = 5
print(f'function results is {function1()} and a is {a}')
function results is 10 and a is 10

Пример #3. В этом примере происходит то же самое, что и в первом примере, но исходное значение глобальной переменной изменяется, потому что list (список) ‒ mutable type (изменяемый тип).

In [3]:
def function2():
    a.append(5)
    return a


a = [5]
print(f'function results is {function2()} and b is {a}')
function results is [5, 5] and b is [5, 5]

Testing and Debugging basics (Тестирование и отладка)

При написании программы, которая будет выполнять хоть немного важное задание, требуется тестировать свой код. Одним из примеров тестирования может выступать конструкция assert. Она нужна для проверки истинности утверждения. Примером отладки могут выступать встроенные в IDE отладчики или библиотеки, позволяющие проводить отладку дажев Jupyter'е.

Testing

In [1]:
assert 2 + 2 == 4
print('right statement')
right statement
In [2]:
assert 2 + 2 == 5, "Уверены ли Вы, что сумма двух и двух равняется пяти?"
print('right statement')
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-2-686801ed7846> in <module>()
----> 1 assert 2 + 2 == 5, "Уверены ли Вы, что сумма двух и двух равняется пяти?"
      2 print('right statement')

AssertionError: Уверены ли Вы, что сумма двух и двух равняется пяти?
In [3]:
assert isinstance(4, int)
print('right statement')
right statement
In [4]:
assert isinstance(4, str), "Почему тут ошибка? Потому что сильная типизация."
print('right statement')
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-4-4f990796fd98> in <module>()
----> 1 assert isinstance(4, str), "Почему тут ошибка? Потому что сильная типизация."
      2 print('right statement')

AssertionError: Почему тут ошибка? Потому что сильная типизация.

В очень общих словах, это процесс очень подробного исполнения программы (контролируемое исполнение каждого действия, каждой операции и итерации) с мониторингом значения всех переменных или всех желаемых переменных и особо тщательном внимании (остановке) в местах, которые того требуют (где выставлен breakpoint ‒ точка остановки). Нужно, чтобы не выставлять print(variable) в каждом месте программы, когда что-то идёт не так, а использовать уже готовый интерфейс для этого.

Что посмотреть и почитать?

ПАСХАЛЬНОЕ ЯЙЦО: Первые три человека, кто прочитает эту статью и отправит мне на почту скрин с установленной работающей библиотекой и пример отладки, или окно отладки из IDE (например PyCharm) в процессе отладки с объяснением происходящего получит $+0.5$ балла к накопленной или экзамену, на выбор.

Также, мы можем совмещать assert с более сложной концепцией ловли ошибок (см. ниже).

In [ ]:
try:
    assert 2 + 2 == 5
except AssertionError:
    print('Wow, there were unclear statment!')

Exception handling and Error rising (Ловля ошибок и вызов ошибок)

Commercial Photography

Иногда, человек пишет код, который наиболее вероятно может сломаться наиболее вероятным образом. В целом, когда пишешь код, нужно заранее понимать, какие ошибки могут возникнуть и как их обойти, не прибегая к экстренным мерам вроде полной перезагрузки работающей программы (hello, windows blue screan). Одним из способов контролировать ошибки может служить концепция ловли ошибок.

В общем виде, ловля ошибок - управляющая конструкция со следующей схемой 1: Commercial Photography

Что почитать?

In [8]:
for i in range(2):
    try:
        int(input('''Введите числое число. Или хотя бы с плавающей точкой. Но вы же можете ввести буквы, 
        я знаю и готов к этому \n'''))
        print('Wow, nice job. That is a NUMBER!')
    except ValueError:
        print('Ha-ha, i was ready!!1!')
Введите числое число. Или хотя бы с плавающей точкой. Но вы же можете ввести буквы, 
        я знаю и готов к этому 
FiftyFive
Ha-ha, i was ready!!1!
Введите числое число. Или хотя бы с плавающей точкой. Но вы же можете ввести буквы, 
        я знаю и готов к этому 
55
Wow, nice job. That is a NUMBER!

Можно ловить совершенно любую ошибку возникшую. Но так делать не стоит, потому что программа перестает быть очевидной и Errors should never pass silently (The Zen of Python 0:10). Лучше (хотя бы) перед этим рассмотреть наиболее вероятные. Пример, при работе с интернетом: ошибка соединения, ошибка возвращаемого кода. Часто в задачах требуются шаблоные исключения, которые достаточно выучить или держать рядом чтобы сделать copy-paste

In [9]:
lst = list(range(5))
lst.append('5')
print(f'lst is {lst}')
try:
    print('Сейчас я попробую суммировать элементы в листе')
    print('Итак, сумма равна...\n')
    print(sum(lst))
except:
    print('''Ой, что-то пошло не так. Не знаю, что могло пойти не так.\n\
И Вы не узнаете. Потому что я выставил исключение на любую ошибку.\n\
Как правило, нет достаточно весомых оснований так делать''')
lst is [0, 1, 2, 3, 4, '5']
Сейчас я попробую суммировать элементы в листе
Итак, сумма равна...

Ой, что-то пошло не так. Не знаю, что могло пойти не так.
И Вы не узнаете. Потому что я выставил исключение на любую ошибку.
Как правило, нет достаточно весомых оснований так делать

Ошибка, которая возникает в описанном выше примере:

TypeError: unsupported operand type(s) for +: 'int' and 'str

Попробуем решить её:

In [11]:
lst = list(range(5))
lst.append('5')
print(f'lst is {lst}')
try:
    print('Сейчас я попробую суммировать элементы в листе')
    print('Итак, сумма равна...\n')
    print(sum(lst))
except TypeError:
    print('TypeError was catched. Good work!')
except:
    print('''Ой, что-то пошло не так. Не знаю, что могло пойти не так.\n\
И Вы не узнаете. Потому что я выставил исключение на любую ошибку.\n\
Как правило, нет достаточно весомых оснований так делать''')
lst is [0, 1, 2, 3, 4, '5']
Сейчас я попробую суммировать элементы в листе
Итак, сумма равна...

TypeError was catched. Good work!

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

Более того, иногда люди любят вызывать ошибки How to catch NotImplementedError Exception in Python?. Например, если есть готовый интерфейс, но его методы (функции) еще не до конца реализованы.

In [12]:
def function_that_finds_life_meaning():
    '''
    Эта функция находит смысл жизни.
    
    Другая постановка задачи: ответ на главный вопрос жизни, 
    вселенной и всего такого.
    
    :input something_meaningfull: dict
    :output: life_meaning
    '''
    # YOUR CODE HERE
    raise NotImplementedError() # - here the error rise
    return life_meaning

try:
    function_that_finds_life_meaning()
except NotImplementedError:
    print('Sorry, The New Ultimate Question of Life, the Universe and Everything is not found yet. Time to be.')
Sorry, The New Ultimate Question of Life, the Universe and Everything is not found yet. Time to be.