Лексическая структура программ на языке Python

Оглавление

Теперь вы подробно изучили переменные, операторы и типы данных Python, а также познакомились с большим количеством примеров кода. До сих пор код состоял из коротких отдельных операторов, просто присваивающих объекты переменным или отображающих значения.

Но вы хотите сделать нечто большее, чем просто определить данные и отобразить их! Давайте начнем организовывать код в более сложные группы.

Вот что вы узнаете в этом уроке: Вы углубитесь в лексическую структуру Python. Вы узнаете о синтаксических элементах, из которых состоят утверждения, основные единицы, из которых состоит программа на Python. Это подготовит вас к следующим нескольким урокам, посвященным управляющим структурам, конструкциям, которые направляют поток программы между различными группами кода.

Питоновские утверждения

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

В сеансе REPL утверждения выполняются по мере их ввода, пока интерпретатор не будет завершен. При выполнении файла сценария интерпретатор считывает утверждения из файла и выполняет их до тех пор, пока не будет обнаружен конец файла.

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

>>> print('Hello, World!')
Hello, World!

>>> x = [1, 2, 3]
>>> print(x[1:2])
[2]

Примечание: Во многих примерах REPL, которые вы видели, утверждение часто состоит из выражения, набранного прямо в подсказке >>>, для которого интерпретатор послушно выводит значение:

>>> 'foobar'[2:5]
'oba'

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

Продолжение строки

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

>>> person1_age = 42
>>> person2_age = 16
>>> person3_age = 71

>>> someone_is_of_working_age = (person1_age >= 18 and person1_age <= 65) or (person2_age >= 18 and person2_age <= 65) or (person3_age >= 18 and person3_age <= 65)
>>> someone_is_of_working_age
True

Или, возможно, вы определяете длинный вложенный список:

>>> a = [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15], [16, 17, 18, 19, 20], [21, 22, 23, 24, 25]]
>>> a
[[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15], [16, 17, 18, 19, 20], [21, 22, 23, 24, 25]]

Вы заметите, что эти утверждения слишком длинные, чтобы поместиться в окне браузера, и браузер вынужден отображать блоки кода с горизонтальными полосами прокрутки. Вас это может раздражать. (Приносим свои извинения - эти примеры представлены таким образом, чтобы показать суть. Больше такого не повторится.)

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

line-wrap

Чрезмерно длинные строки кода обычно считаются плохой практикой. На самом деле, существует официальное Style Guide for Python Code, разработанное Python Software Foundation, и одно из его положений гласит, что максимальная длина строки в коде Python должна составлять 79 символов.

Примечание: Руководство по стилю для кода Python также называют PEP 8. PEP расшифровывается как Python Enhancement Proposal. PEP - это документы, содержащие подробную информацию о возможностях, стандартах, проблемах проектирования, общие рекомендации и информацию, относящуюся к Python. Дополнительную информацию можно найти на сайте Python Software Foundation Index of PEPs.

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

>>> someone_is_of_working_age = person1_age >= 18 and person1_age <= 65 or
SyntaxError: invalid syntax

В коде Python оператор может быть продолжен с одной строки на другую двумя различными способами: неявное и явное продолжение строки.

Неявное продолжение линии

Это более простая техника продолжения строки, и именно она является предпочтительной согласно PEP 8.

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

Например, определение вложенного списка, приведенное выше, можно сделать гораздо более читабельным с помощью неявного продолжения строки из-за открытых скобок:

>>> a = [
...     [1, 2, 3, 4, 5],
...     [6, 7, 8, 9, 10],
...     [11, 12, 13, 14, 15],
...     [16, 17, 18, 19, 20],
...     [21, 22, 23, 24, 25]
... ]

>>> a
[[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15],
[16, 17, 18, 19, 20], [21, 22, 23, 24, 25]]

Длинное выражение можно также продолжить на несколько строк, заключив его в группирующие круглые скобки. PEP 8 явно поддерживает использование скобок таким образом, когда это уместно:

>>> someone_is_of_working_age = (
...     (person1_age >= 18 and person1_age <= 65)
...     or (person2_age >= 18 and person2_age <= 65)
...     or (person3_age >= 18 and person3_age <= 65)
... )

>>> someone_is_of_working_age
True

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

Круглые скобки

  • Группировка выражений

    >>> x = (
    ...     1 + 2
    ...     + 3 + 4
    ...     + 5 + 6
    ... )
    >>> x
    21
    
  • Вызов функции

    >>> print(
    ...     'foo',
    ...     'bar',
    ...     'baz'
    ... )
    foo bar baz
    
  • Вызов метода

    >>> 'abc'.center(
    ...     9,
    ...     '-'
    ... )
    '---abc---'
    
  • Определение кортежа

    >>> t = (
    ...     'a', 'b',
    ...     'c', 'd'
    ... )
    

Фигурные скобки

  • Словарное определение

    >>> d = {
    ...     'a': 1,
    ...     'b': 2
    ... }
    
  • Определение набора

    >>> x1 = {
    ...     'foo',
    ...     'bar',
    ...     'baz'
    ... }
    

Квадратные скобки

  • Определение списка

    >>> a = [
    ...     'foo', 'bar',
    ...     'baz', 'qux'
    ... ]
    
  • Индексирование

    >>> a[
    ...  1
    ...  ]
    'bar'
    
  • Нарезка

    >>> a[
    ...  1:2
    ...  ]
    ['bar']
    
  • Ссылка на ключ словаря

    >>> d[
    ...  'b'
    ...  ]
    2
    

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

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

>>> a = [
...     [
...         ['foo', 'bar'],
...         [1, 2, 3]
...     ],
...     {1, 3, 5},
...     {
...         'a': 1,
...         'b': 2
...     }
... ]

>>> a
[[['foo', 'bar'], [1, 2, 3]], {1, 3, 5}, {'a': 1, 'b': 2}]

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

Явное продолжение строки

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

Обычно символ новой строки (который вы получаете при нажатии Enter на клавиатуре) обозначает конец строки. Если оператор не завершен к этому моменту, Python вызовет исключение SyntaxError:

>>> s =
  File "<stdin>", line 1
    s =
      ^
SyntaxError: invalid syntax

>>> x = 1 + 2 +
  File "<stdin>", line 1
    x = 1 + 2 +
              ^
SyntaxError: invalid syntax

Чтобы указать явное продолжение строки, вы можете указать символ обратной косой черты (\) в качестве последнего символа в строке. В этом случае Python игнорирует следующую новую строку, и утверждение фактически продолжается на следующей строке:

>>> s = \
... 'Hello, World!'
>>> s
'Hello, World!'

>>> x = 1 + 2 \
...     + 3 + 4 \
...     + 5 + 6
>>> x
21

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

>>> # You can't see it, but there is a space character following the \ here:
>>> s = \
  File "<stdin>", line 1
    s = \
         ^
SyntaxError: unexpected character after line continuation character

Опять же, PEP 8 рекомендует использовать явное продолжение строки только в тех случаях, когда неявное продолжение строки не представляется возможным.

Многочисленные заявления в одной строке

На одной строке может находиться несколько утверждений, если они разделены символом точки с запятой (;):

>>> x = 1; y = 2; z = 3
>>> print(x); print(y); print(z)
1
2
3

Стилистически это обычно не одобряется, а PEP 8 прямо запрещает это делать. Могут быть ситуации, когда это улучшает читабельность, но обычно этого не происходит. Более того, часто в этом нет необходимости. Следующие утверждения функционально эквивалентны приведенному выше примеру, но будут считаться более типичным кодом Python:

>>> x, y, z = 1, 2, 3
>>> print(x, y, z, sep='\n')
1
2
3

Термин Pythonic относится к коду, который придерживается общепринятых общих рекомендаций по читабельности и "лучшему" использованию идиоматического Python. Когда кто-то говорит, что код не Pythonic, он подразумевает, что он не выражает замысел программиста так хорошо, как это можно было бы сделать на Python. Таким образом, код, вероятно, не настолько удобен для чтения, насколько он мог бы быть удобен для человека, свободно владеющего Python.

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

Комментарии

В Python символ хэша (#) обозначает комментарий. Интерпретатор будет игнорировать все, начиная с символа хэша и до конца строки:

>>> a = ['foo', 'bar', 'baz']  # I am a comment.
>>> a
['foo', 'bar', 'baz']

Если первый не пробельный символ в строке является хэшем, вся строка эффективно игнорируется:

>>> # I am a comment.
>>>     # I am too.

Естественно, хэш-символ внутри строкового литерала является защищенным и не указывает на комментарий:

>>> a = 'foobar # I am *not* a comment.'
>>> a
'foobar # I am *not* a comment.'

Комментарий просто игнорируется, так какой же цели он служит? Комментарии дают вам возможность прикрепить поясняющие детали к вашему коду:

>>> # Calculate and display the area of a circle.

>>> pi = 3.1415926536
>>> r = 12.35

>>> area = pi * (r ** 2)

>>> print('The area of a circle with radius', r, 'is', area)
The area of a circle with radius 12.35 is 479.163565508706

До сих пор ваша работа на Python состояла в основном из коротких, изолированных сессий REPL. В таких условиях необходимость в комментариях минимальна. Со временем вы будете разрабатывать более крупные приложения, содержащие несколько файлов сценариев, и комментарии станут все более важными.

Правильное комментирование позволяет с первого взгляда понять смысл вашего кода, когда его читает кто-то другой, или даже когда вы сами его читаете. В идеале вы должны стремиться к тому, чтобы писать код как можно более ясным, кратким и понятным. Но бывают случаи, когда вы принимаете решения по проектированию или реализации, которые не очевидны из самого кода. Вот тут-то и приходит на помощь комментирование. Хороший код объясняет, как; хорошие комментарии объясняют, почему.

Комментарии могут быть включены в неявное продолжение строки:

>>> x = (1 + 2           # I am a comment.
...      + 3 + 4         # Me too.
...      + 5 + 6)
>>> x
21

>>> a = [
...     'foo', 'bar',    # Me three.
...     'baz', 'qux'
... ]
>>> a
['foo', 'bar', 'baz', 'qux']

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

>>> x = 1 + 2 + \   # I wish to be comment, but I'm not.
SyntaxError: unexpected character after line continuation character

Что делать, если вы хотите добавить комментарий длиной в несколько строк? Многие языки программирования предоставляют синтаксис для многострочных комментариев (также называемых блочными комментариями). Например, в C и Java комментарии разграничиваются лексемами /* и */. Текст, содержащийся в этих разделителях, может занимать несколько строк:

/*
[This is not Python!]

Initialize the value for radius of circle.

Then calculate the area of the circle
and display the result to the console.
*/

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

>>> # Initialize value for radius of circle.
>>> #
>>> # Then calculate the area of the circle
>>> # and display the result to the console.

>>> pi = 3.1415926536
>>> r = 12.35

>>> area = pi * (r ** 2)

>>> print('The area of a circle with radius', r, 'is', area)
The area of a circle with radius 12.35 is 479.163565508706

Однако для кода в файле сценария технически существует альтернатива.

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

Рассмотрите этот файл сценария foo.py:

"""Initialize value for radius of circle.

Then calculate the area of the circle
and display the result to the console.
"""

pi = 3.1415926536
r = 12.35

area = pi * (r ** 2)

print('The area of a circle with radius', r, 'is', area)

При выполнении этого сценария вывод выглядит следующим образом:

Командная строка Windows

C:\Users\john\Documents\Python\doc>python foo.py
The area of a circle with radius 12.35 is 479.163565508706

Строка в тройных кавычках не отображается и никак не меняет ход выполнения скрипта. Она фактически представляет собой многострочный блочный комментарий.

Хотя это работает (и однажды было представлено самим Гвидо в качестве совета по программированию на Python), PEP 8 на самом деле не рекомендует этого делать. Причина этого, по-видимому, кроется в специальной конструкции Python, называемой docstring. docstring - это специальный комментарий в начале определяемой пользователем функции, который документирует поведение функции. Docstrings обычно указываются как строковые комментарии с тройными кавычками, поэтому PEP 8 рекомендует обозначать другие блочные комментарии в коде Python обычным способом, с хэш-символом в начале каждой строки.

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

Дальнейшее чтение: Вы узнаете больше о docstrings в предстоящем уроке по функциям в Python.

Для получения дополнительной информации о комментировании и документировании кода Python, включая docstrings, смотрите Документирование кода Python: Полное руководство.

Пробел

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

Обычно токены отделяются друг от друга пробелами: пустыми символами, которые обеспечивают пустое пространство для улучшения читабельности. Наиболее распространенными пробельными символами являются следующие:

Character ASCII Code Literal Expression
space 32 (0x20) ' '
tab 9 (0x9) '\t'
newline 10 (0xa) '\n'

Существуют и другие несколько устаревшие символы пробельных символов ASCII, такие как перевод строки и перевод формы, а также некоторые очень эзотерические Unicode символы, которые обеспечивают пробельные символы. Но для нынешних целей пробельные символы обычно означают пробел, табуляцию или новую строку.

Пробелы в основном игнорируются и чаще всего не требуются интерпретатором Python. Когда ясно, где заканчивается одна лексема и начинается следующая, пробельные символы можно опустить. Обычно это происходит, когда используются специальные неалфавитно-цифровые символы:

>>> x=3;y=12
>>> x+y
15

>>> (x==3)and(x<y)
True

>>> a=['foo','bar','baz']
>>> a
['foo', 'bar', 'baz']

>>> d={'foo':3,'bar':4}
>>> d
{'foo': 3, 'bar': 4}

>>> x,y,z='foo',14,21.1
>>> (x,y,z)
('foo', 14, 21.1)

>>> z='foo'"bar"'baz'#Comment
>>> z
'foobarbaz'

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

>>> value1=100
>>> value2=200
>>> v=(value1>=0)and(value1<value2)
>>> value1 = 100
>>> value2 = 200
>>> v = (value1 >= 0) and (value1 < value2)

Большинство людей, скорее всего, сочтут, что добавленные пробелы во втором примере облегчают чтение. С другой стороны, наверняка найдутся те, кто предпочтет первый пример. В какой-то степени это вопрос личных предпочтений. Но существуют стандарты пробельных символов в выражениях и утверждениях, изложенные в PEP 8, и вам следует настоятельно рекомендовать придерживаться их по мере возможности.

Примечание: Вы можете соединять строковые литералы, с пробелами или без них:

>>> s = "foo"'bar''''baz'''
>>> s
'foobarbaz'

>>> s = 'foo' "bar" '''baz'''
>>> s
'foobarbaz'

Эффект конкатенации, точно такой же, как если бы вы использовали оператор +.

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

Например, в следующем случае пробельные символы необходимы для отделения идентификатора s от ключевого слова in:

>>> s = 'bar'

>>> s in ['foo', 'bar', 'baz']
True

>>> sin ['foo', 'bar', 'baz']
Traceback (most recent call last):
  File "<pyshell#25>", line 1, in <module>
    sin ['foo', 'bar', 'baz']
NameError: name 'sin' is not defined

Вот пример, в котором пробельные символы необходимы для различения идентификатора y и числовой константы 20:

>>> y is 20
False

>>> y is20
SyntaxError: invalid syntax

В данном примере пробел необходим между двумя ключевыми словами:

>>> 'qux' not in ['foo', 'bar', 'baz']
True

>>> 'qux' notin ['foo', 'bar', 'baz']
SyntaxError: invalid syntax

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

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

Для повышения удобочитаемости следует использовать пробельные символы там, где они не являются необходимыми. В идеале следует придерживаться рекомендаций, изложенных в PEP 8.

Глубокое погружение: Фортран и пробельные символы

Самые ранние версии Fortran, одного из первых созданных языков программирования, были разработаны таким образом, что все пробельные символы полностью игнорировались. Символы пробела можно было по желанию включать или опускать практически везде - между идентификаторами и зарезервированными словами, и даже в середине идентификаторов и зарезервированных слов.

Например, если в вашем коде на Фортране есть переменная с именем total, то для присвоения ей значения 50 подойдет любой из следующих операторов:

total = 50
to tal = 50
t o t a l=5 0

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

Рассмотрим эту историю, произошедшую в NASA в 1960-х годах. Программа расчета орбиты Центра управления полетами, написанная на языке Фортран, должна была содержать следующую строку кода:

DO 10 I = 1,100

В диалекте Фортрана, использовавшемся в NASA в то время, приведенный код вводит цикл - конструкцию, которая выполняет тело кода многократно. (О циклах в Python вы узнаете в двух последующих уроках, посвященных определенной и неопределенной итерации).

К сожалению, эта строка кода оказалась в программе вместо этого:

DO 10 I = 1.100

Если вам трудно заметить разницу, не расстраивайтесь. Программисту NASA потребовалась пара недель, чтобы заметить, что между 1 и 100 вместо запятой стоит точка. Поскольку компилятор Фортрана игнорировал пробельные символы, DO 10 I было принято за имя переменной, и оператор DO 10 I = 1.100 привел к присвоению 1.100 переменной DO10I, а не к введению цикла.

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

Практически все современные языки программирования решили не заходить так далеко в игнорировании пробельных символов.

Пробел как отступ

Есть еще одна важная ситуация, в которой пробельные символы имеют значение в коде Python. Отступы - пробелы, которые появляются слева от первой лексемы в строке - имеют особое значение.

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

Командная строка Windows

C:\Users\john>echo foo
foo

C:\Users\john>    echo foo
foo

Примечание: В окне командной строки команда echo выводит свои аргументы на консоль, как и функция print() в Python. Аналогичное поведение можно наблюдать и в окне терминала в macOS или Linux.

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

Теперь попробуйте проделать примерно то же самое с интерпретатором Python:

>>> print('foo')
foo
>>>     print('foo')

SyntaxError: unexpected indent

Что сказать? Непредвиденный отступ? Пробел перед вторым оператором print() вызывает исключение SyntaxError!

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

До тех пор помните, что пробельные символы имеют значение.

Заключение

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

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

Вернуться на верх