Элементы языка программирования python

Язык программирования-это больше. Чем просто средство обучения компьютера выполнению задач. Язык также служит основой. В рамках которой мы организуем наши представления о вычислительных процессах. Программы служат для передачи этих идей среди членов сообщества программистов. Таким образом. Программы должны быть написаны для людей. Чтобы их можно было читать. И только случайно для машин. Чтобы их можно было выполнять. Когда мы описываем язык. Мы должны обратить особое внимание на средства. Которые язык предоставляет для объединения простых идей. Чтобы сформировать более сложные идеи. Каждый мощный язык имеет три таких механизма:

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

В программировании мы имеем дело с двумя типами элементов: функциями и данными. (Вскоре мы обнаружим. Что на самом деле они не так уж различимы.) Неформально данные — это то. Чем мы хотим манипулировать. А функции описывают правила манипулирования данными. Таким образом. Любой мощный язык программирования должен уметь описывать примитивные данные и примитивные функции. А также иметь некоторые методы объединения и абстрагирования как функций. Так и данных.

1.2.1 Выражения

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

Начнем с примитивных выражений. Одним из видов примитивного выражения является число. Точнее, выражение. Которое вы вводите. Состоит из цифр. Представляющих число в базе 10.

Выражения, представляющие числа. Могут быть объединены с математическими операторами. Чтобы сформировать составное выражение. Которое интерпретатор будет оценивать:

>>> -1 - -1 0 >>> 1/2 + 1/4 + 1/8 + 1/16 + 1/32 + 1/64 + 1/128 0.9921875 

Эти математические выражения используют инфиксную нотацию. Где оператор (например,+, -, *или /) появляется между операндами (числами). Python включает в себя множество способов формирования составных выражений. Вместо того. Чтобы пытаться перечислить их все сразу. Мы будем вводить новые формы выражения по мере продвижения вместе с языковыми функциями. Которые они поддерживают.

1.2.2 Выражения вызова

Наиболее важным видом составного выражения является выражение вызова, которое применяет функцию к некоторым аргументам. Вспомним из алгебры. Что математическое понятие функции-это отображение некоторых входных аргументов на выходное значение. Например, функция max отображает свои входы на один выход. Который является самым большим из входов. Способ, которым Python выражает применение функций. Такой же. Как и в обычной математике.

> > > > > > > > > макс(7.5, 9.5) 9.5 

Это выражение вызова имеет подвыражения: оператор-это выражение. Которое предшествует скобкам. Заключающим разделенный запятыми список выражений операндов.

Оператор задает функцию. Когда это выражение вызова вычисляется. Мы говорим. Что функция max вызывается с аргументами 7.5 и 9.5 и возвращает значение 9.5.

Порядок аргументов в выражении вызова имеет значение. Например, функция pow поднимает свой первый аргумент до степени своего второго аргумента.

> > > > > > > > > > ( 100, 2) 10000 >>>>>> >>> pow(2, 100) 1267650600228229401496703205376 

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

> > > > > > > > макс(1, -2, 3, -4) 3 

Никакой двусмысленности возникнуть не может. Поскольку имя функции всегда предшествует ее аргументам.

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

> > > > > > > > max(min(1, -2), min(pow(3, 5), -4)) -2 

Нет предела (в принципе) глубине такой вложенности и общей сложности выражений. Которые интерпретатор Python может оценить. Однако люди быстро запутываются в многоуровневом гнездовании. Важная роль для вас как программиста состоит в том. Чтобы структурировать выражения так. Чтобы они оставались интерпретируемыми вами. Вашими партнерами по программированию и другими людьми. Которые могут читать ваши выражения в будущем.

В-третьих, математическая нотация имеет большое разнообразие форм: умножение появляется между терминами. Экспоненты появляются в виде надстроек. Деление-в виде горизонтальной линейки. А квадратный корень-в виде крыши с наклонным сайдингом. Некоторые из этих обозначений очень трудно напечатать! Однако вся эта сложность может быть объединена с помощью нотации выражений вызова. В то время как Python поддерживает общие математические операторы с использованием нотации инфикса (например, + и -), любой оператор может быть выражен как функция с именем.

1.2.3 Импорт библиотечных Функций

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

> > > > > > > > from math import sqrt >>>>> > > > sqrt(256) 16.0 

а операторный модуль обеспечивает доступ к функциям соответствующим инфиксным операторам:

> > > > > > > > из  импорта оператора add, sub, mul >>>>>> > > > > добавить(14, 28) 42 >>>>>> >>> sub(100, mul(7, add(8, 4))) 16 

Оператор import обозначает имя модуля (например, operator или math), а затем перечисляет именованные атрибуты этого модуля для импорта (например, sqrt). После того. Как функция импортирована. Она может быть вызвана несколько раз.

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

В документах библиотеки Python 3 перечислены функции. Определенные каждым модулем. Например математическим модулем. Однако эта документация написана для разработчиков. Которые хорошо знают весь язык. На данный момент вы можете обнаружить. Что экспериментирование с функцией говорит вам больше о ее поведении. Чем чтение документации. По мере знакомства с языком и словарем Python эта документация станет ценным справочным источником.

1.2.4 Названия и окружающая среда

Критическим аспектом языка программирования являются средства. Которые он предоставляет для использования имен для обозначения вычислительных объектов. Если значение получило имя. Мы говорим. Что имя связывается со значением.

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

> > > > > > > > > радиус = 10 >>>>>> >>> радиус 10 >>>>>> >>> 2 * радиус 20 

Имена также связываются с помощью операторов импорта.

> > > > > > > > из math import pi >>>>> > > > pi * 71 / 223 1.0002380197528042 

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

Возможность привязки имен к значениям и последующего извлечения этих значений по имени означает. Что интерпретатор должен поддерживать некоторую память. Которая отслеживает имена. Значения и привязки. Эта память называется средой.

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

> > > > > > > > > max  

Мы можем использовать операторы присваивания. Чтобы дать новые имена существующим функциям.

> > > > > > > > f = max >>>>> > > > f  >>>>>> >>> f(2, 3, 4) 4 

А последовательные операторы присваивания могут повторно привязывать имя к новому значению.

> > > > > > > > f = 2 >>>>>> >>> f 2 

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

> > > > > > > > макс = 5 >>>>>> >>> макс 5 

После присвоения max значения 5 имя max больше не привязано к функции, и поэтому попытка вызова max(2, 3, 4) вызовет ошибку.

При выполнении оператора присваивания Python вычисляет выражение справа от=, прежде чем изменить привязку к имени слева. Поэтому можно ссылаться на имя в правостороннем выражении. Даже если это имя будет связано оператором присваивания.

> > > > > > > > x = 2 >>>>>> >>> x = x + 1 >>>>>> >>> x 3 

Мы также можем присвоить несколько значений нескольким именам в одном операторе. Где имена слева от = и выражения справа от = разделяются запятыми.

> > > > > > > > площадь, окружность = pi * radius * radius, 2 * pi * radius >>>>> > > площадь 314.1592653589793 >>>>>> >>> окружность 62.83185307179586 

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

> > > > > > > > > > радиус = 11 >>>>>> >>> площадь 314.1592653589793 >>>>>> >>> площадь = pi * радиус * радиус 380.132711084365 

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

>>> x, y = 3, 4.5 >>> y, x = x, y >>> x 4.5 >>> y 3 

1.2.5 Оценка Вложенных Выражений

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

Чтобы вычислить выражение вызова. Python сделает следующее:

  1. Вычислите подвыражения оператора и операнда, а затем
  2. Примените функцию. Которая является значением подвыражения оператора. К аргументам. Которые являются значениями подвыражений операндов.

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

Например, оценка

> > > > > > > > sub(pow(2, add(1, 10)), pow(2, 5)) 2016 

требуется. Чтобы эта процедура оценки применялась четыре раза. Если мы нарисуем каждое выражение. Которое мы оцениваем. Мы сможем визуализировать иерархическую структуру этого процесса.

Эта иллюстрация называется деревом выражений. В информатике деревья обычно растут сверху вниз. Объекты в каждой точке дерева называются узлами; в этом случае они являются выражениями в паре со своими значениями.

Оценка его корня. Полного выражения в верхней части. Требует сначала оценки ветвей. Которые являются его подвыражениями. Конечные выражения (то есть узлы без ветвей. Вытекающих из них) представляют либо функции. Либо числа. Внутренние узлы имеют две части: выражение вызова. К которому применяется наше правило вычисления. И результат этого выражения. Рассматривая вычисление в терминах этого дерева. Мы можем представить себе. Что значения операндов просачиваются вверх. Начиная с конечных узлов и затем объединяясь на все более высоких уровнях.

Затем обратите внимание, что повторное применение первого шага приводит нас к тому, что нам нужно вычислять не выражения вызова, а примитивные выражения, такие как числа (например, 2) и имена (например, add). Мы заботимся о примитивных случаях. Оговаривая, что

  • Цифра оценивает число. Которое она называет,
  • Имя вычисляется как значение. Связанное с этим именем в текущей среде.

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

> > > > > > > > > добавить(x, 1) 

без указания какой — либо информации об окружающей среде. Которая придавала бы значение имени x (или даже имени add). Среды обеспечивают контекст. В котором происходит оценка. Что играет важную роль в нашем понимании выполнения программы.

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

does not return a value nor evaluate a function on some arguments. Since the purpose of assignment is instead to bind a name to a value. In general, statements are not evaluated but executed; they do not produce a value but instead make some change. Each type of expression or statement has its own evaluation or execution procedure.

A pedantic note: when we say that «a numeral evaluates to a number,» we actually mean that the Python interpreter evaluates a numeral to a number. It is the interpreter which endows meaning to the programming language. Given that the interpreter is a fixed program that always behaves consistently. We can say that numerals (and expressions) themselves evaluate to values in the context of Python programs.

1.2.6   The Non-Pure Print Function

Throughout this text. We will distinguish between two types of functions.

Pure functions. Functions have some input (their arguments) and return some output (the result of applying them). The built-in function

>>> abs(-2) 2 

can be depicted as a small machine that takes input and produces output.

The function abs is pure. Pure functions have the property that applying them has no effects beyond returning a value. Moreover, a pure function must always return the same value when called twice with the same arguments.

Non-pure functions. In addition to returning a value. Applying a non-pure function can generate side effects, which make some change to the state of the interpreter or computer. A common side effect is to generate additional output beyond the return value. Using the print function.

>>> print(1, 2, 3) 1 2 3 

While print and abs may appear to be similar in these examples. They work in fundamentally different ways. The value that print returns is always None, a special Python value that represents nothing. The interactive Python interpreter does not automatically print the value None. In the case of print, the function itself is printing output as a side effect of being called.

A nested expression of calls to print highlights the non-pure character of the function.

>>> print(print(1), print(2)) 1 2 None None 

If you find this output to be unexpected. Draw an expression tree to clarify why evaluating this expression produces this peculiar output.

Be careful with print! The fact that it returns None means that it should not be the expression in an assignment statement.

>>> two = print(2) 2 >>> print(two) None 

Pure functions are restricted in that they cannot have side effects or change behavior over time. Imposing these restrictions yields substantial benefits. First, pure functions can be composed more reliably into compound call expressions. We can see in the non-pure function example above that print does not return a useful result when used in an operand expression. On the other hand. We have seen that functions such as max, pow and sqrt can be used effectively in nested expressions.

Second, pure functions tend to be simpler to test. A list of arguments will always lead to the same return value. Which can be compared to the expected return value. Testing is discussed in more detail later in this chapter.

Third, Chapter 4 will illustrate that pure functions are essential for writing concurrent programs. In which multiple call expressions may be evaluated simultaneously.

By contrast. Chapter 2 investigates a range of non-pure functions and describes their uses.

For these reasons. We concentrate heavily on creating and using pure functions in the remainder of this chapter. The print function is only used so that we can see the intermediate results of computations.