Программирование и научные вычисления на языке Python/§4

Термин *функция* в программировании означает несколько большее, чем в математике. Функция представляет законченную последовательность инструкций, которую вы можете вызывать в программе в любом месте и в любое время, когда программе известна ваша функция. Вы можете отсылать в функцию переменные, а функция в соответствии с инструкциями будет возвращать требуемое значение. Функции помогают избежать повторения одинаковых кусков программного кода, что в дальнейшем облегчает модифицирование программ. Функции также часто используются для разбиения программы на меньшие части так, чтобы более ясно понимать работу программы и допускать меньше ошибок. В Python имеется множество встроенных функций, что мы рассмотрели ранее (например, *math.sqrt*, *range*, *len*). Но вы и сами можете задавать свои собственные функции.



## Функции одной переменной

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



```
def F(C):
return (9.0/5)*C + 32
```


Задание функции в Python начинается со слова def, за ним следует имя функции и затем после двоеточия на новой строке с отступом начинаются инструкции функции. Слово return означает, что функция возвращает значение, которое находится после этого слова. Это значение связывается с именем функции. То есть передав в функцию аргумент (С), на выходе мы имеем значение, рассчитанное после слова return.

Строка с def, именем и аргументами функции называется *заголовком функции*, в то время как оставшаяся часть из самих инструкций - *телом функции*.

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



```
a = 10
F1 = F(a)
temp = F(15.5)
print F(a+1)
sum_temp = F(10) + F(20)
```


В нашем примере при вызове возвращается объект типа float, который может быть использован далее в любом месте кода, где может использоваться объект типа float. Например, в инструкции print или например создать для списка градусов Цельсия создать соответствующий список по Фаренгейту:



```
Fdegrees = [F(C) for C in Cdegrees]
```


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



```
>>> def F2(C):
... F_value = (9.0/5)*C + 32
... return '%.1f degrees Celsius corresponds to '\
... '%.1f degrees Fahrenheit' % (C, F_value)
...
>>> s1 = F2(21)
>>> s1
'21.0 degrees Celsius corresponds to 69.8 degrees Fahrenheit'
```



## Локальные и глобальные переменные

Продолжим предыдущее рассмотрение в интерактивном режиме. Обратимся к функции F2(C), для которой F_value является *локальной* (local) переменной. Локальная переменная не может использоваться за пределами функции. Это легко продемонстрировать продолжив:



```
>>> c1 = 37.5
>>> s2 = F2(c1)
>>> F_value
...
NameError: name 'F_value' is not defined
```


Точно так же локальной переменной является и аргумент функции C, который мы не можем использовать вне ее тела:



```
>>> C
...
NameError: name 'C' is not defined
```


С другой стороны, переменные определенные за пределами функции, например, c1 и s2 являются *глобальными* переменными, доступными из любого места программы. Локальные переменные создаются внутри функции и исчезают, когда мы покидаем функцию. Для того, чтобы лучше понять это, напишем следующий код:

```
>>> def F3(C):
F_value = (9.0/5)*C + 32
print 'Inside F3: C=%s F_value=%s r=%s' % (C, F_value, r)
return '%.1f degrees Celsius corresponds to '\
'%.1f degrees Farenheit' % (C, F_value)
>>> C = 60
>>> r = 21
>>> s3 = F3(r)
Inside F3: C=21 F_value=69.8 r=21
>>> s3
'21.0 degrees Celsius corresponds to 69.8 degrees Farenheit'
>>> C
60
```


Этот пример иллюстрирует как одновременно используются две переменные с одним именем C: одна глобальная, определенная в самое программе значением 60 (int объектом), а другая локальная, представляющая собой аргумент функции F3. Значение последней, локальной переменной определяется передаваемым значением при вызове функции. Внутри F3 локальная переменная С "скрывает" глобальную переменную С.

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



```
print sum # sum встроенная функция
sum = 500 # sum ссылается на int
print sum # sum глобальная переменная
def myfunc(n):
sum = n + 1
print sum # sum локальная переменная
return sum
sum = myfunc(2)+ 1 # новое значение глобальной переменной sum
print sum
```


В первой строке нет никаких локальных переменных, поэтому Python ищет значение sum как глобальной переменной, но ничего не находит и приступает к поиску среди встроенных функций, находит такую функцию и пишет что-нибудь вроде <built-in
function sum>.

Во второй строке создается глобальная переменная sum соответствующая объекту типа int. Теперь, когда мы используем sum в инструкции print, Python уже находит одну переменную среди глобальных. При вызове myfunc(2) sum локальная переменная, поэтому инструкция print sum приводит к тому, что Python в первую очередь стремиться использовать локальную переменную, равную 3 (а не значение глобальной переменной, равное 500). В то же время кроме инструкции print, выполняется инструкция return и внешней глобальной переменной sum придается значение 4, которое мы и видим, когда выполняем последнюю строчку.

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



```
a = 20; b = -2.5 # глобальные переменные
def f1(x):
a = 21 # новая локальная переменная
return a*x + b # 21*x - 2.5
print a # выводится 20
def f2(x):
global a
a = 21 # глобальная a изменена
return a*x + b # 21*x - 2.5
f1(3); print a # 20
f2(3); print a # 21
```


Отметим что в функции f1, выражение a = 21 создает локальную переменную a. Хотя можно было бы подумать, что так вы изменяете глобальную переменную a, но как показывает опыт это не так. Намотайте это на ус, это частая причина ошибок.



## Несколько аргументов

Предыдущие функции *F(C)* и *F2(C)* функции одной переменной, *С*, то есть функции принимают только один аргумент. Но вообще функции могут принимать сколь угодно много аргументов. Например, задачу из нашего первого урока в виде функции двух аргументов можно записать так:



```
def yfunc(t, v0):
g = 9.81
return v0*t - 0.5*g*t**2
```


Отметим, что *g* локальная переменная с фиксированным значением, в то время как *t* и *v0* аргументы функции, также являющиеся ее локальными переменными. Как можно вызвать заданную функцию нескольких аргументов:



```
y = yfunc(0.1, 6)
y = yfunc(0.1, v0=6)
y = yfunc(t=0.1, v0=6)
y = yfunc(v0=6, t=0.1)
```


Возможность при вызове передавать аргументы в виде *аргумент=значение* делает чтение кода более простым для понимания. При этом, если использовать данную конструкцию для всех аргументов, можно не думать об их очередности. Заметим, что если данный синтаксис выполняется не для всех аргументов, то требуется соблюдать последовательность: вначале значения "безымянных" аргументов, потом в виде *аргумент=значение*. То есть вызов *yfunc(t=0.1, 6)* будет некорректным.

Независимо от того пишем ли мы *yfunc(0.1, 6)* или *yfunc(v0=6, t=0.1)*, аргументы рассматриваются как локальные переменные подобно присваиванию переменным значений:



```
t = 0.1
v0 = 6
```


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

Некоторые могли бы поспорить, что *yfunc* должна быть функцией только от *t*, поскольку математически мы считаем координату *y* функцией времени *t* и пишем *y(t)*. Естественно, на это легко ответить на Python:



```
def yfunc(t):
g = 9.81
return v0*t - 0.5*g*t**2
```


Главное отличие в том, что теперь v0 просто обязана быть глобальной переменной, иначе мы не можем вызвать *yfunc*:



```
>>> def yfunc(t):
... g = 9.81
... return v0*t - 0.5*g*t**2
...
>>> yfunc(0.6)
...
NameError: global name 'v0' is not defined
```


Решением служит предопределение *v0* как глобальной переменной перед вызовом *yfunc*:



```
>>> v0 = 5
>>> yfunc(0.6)
1.2342
```


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



```
def makelist(start, stop, inc):
value = start
result = 
while value <= stop:
result.append(round(value))
value = value + inc
return result
mylist = makelist(0, 100, 0.2)
print mylist # выведет 0, 0.2, 0.4, 0.6, ... 99.8, 100
```

## Несколько возвращаемых значений

Функции Python могут возвращать больше одного значения. Предположим, в нашей первой задаче о движении мяча мы заинтересованы не только в значении координаты *y(t)*, но и скорости, которое можно определить как производную


.


Для того чтобы возвратить несколько требуемых значений, достаточно перечислить их через запятую в инструкции return:



```
def yfunc(t, v0):
g = 9.81
y = v0*t - 0.5*g*t**2
dydt = v0 - g*t
return y, dydt
```


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



```
position, velocity = yfunc(0.6, 3)
```


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



```
t_values = [0.05*i for i in range(10)]
for t in t_values:
pos, vel = yfunc(t, v0=5)
print 't=%-10g position=%-10g velocity=%-10g' % (t, pos, vel)
```


Форматирование %-10g выводит числа в наиболее компактной форме (десятичного числа или научного представления) в поле из десяти знаков. Знак минус после процента означает что выравнивание в отведенном поле происходит по левому краю.



t=0 position=0 velocity=5 t=0.05 position=0.237737 velocity=4.5095 t=0.1 position=0.45095 velocity=4.019 t=0.15 position=0.639638 velocity=3.5285 t=0.2 position=0.8038 velocity=3.038 t=0.25 position=0.943437 velocity=2.5475 t=0.3 position=1.05855 velocity=2.057 t=0.35 position=1.14914 velocity=1.5665 t=0.4 position=1.2152 velocity=1.076 t=0.45 position=1.25674 velocity=0.5855


Стоит сказать, что когда функция возвращает несколько значений, разделенных в инструкции return запятыми, в действительности возвращается кортеж. Этот факт можно продемонстрировать так:



```
>>> def f(x):
... return x, x**2, x**4
...
>>> s = f(2)
>>> s
(2, 4, 16)
>>> type(s)
<type 'tuple'>
>>> x, x2, x4 = f(2)
```


Наш следующий пример — о функции суммы:




Можно показать, что *L(x, n)* аппроксимирует функцию ln(1+x) при конечном n и x ≥ 1. Для того чтобы записать сумму в программе, нам потребуется цикл и некоторая переменная в цикле, к которой будут добавляться новые члены. В общем случае когда слагаемые представляются как c(i), такую ситуацию можно реализовать так:



```
s = 0
for i in range (1, n+1):
s += c(i)
```


В нашем конкретном случае c(i) представлено как (1/i)(x/(1 + x))i:



```
s = 0
for i in range (1, n+1):
s += (1.0/i)*(x/(1.0+x))**i
```


Будет естественно представить такую сумму, как мы и задали ранее, в виде функции с аргументами x и n, возвращающей значение суммы:



```
def L(x, n):
s = 0
for i in range (1, n+1):
s += (1.0/i)*(x/(1.0+x))**i
return s
```


Поскольку заданная математическая функция L(x, n) аппроксимирует точную функцию ln(1+x), мы могли бы определить как это качественно происходит. Новая версия будет выглядеть так:



```
def L(x, n):
s = 0
for i in range (1, n+1):
s += (1.0/i)*(x/(1.0+x))**i
value_of_sum = s
first_neglected_term = (1.0/(n+1))*(x/(1.0+x))**(n+1)
from math import log
exact_error = log(1+x) - value_of_sum
return value_of_sum, first_neglected_term, exact_error
value, approximate_error, exact_error = L(x, 100)
```

## Функции без ответа

Иногда от функций требуется только выполнение ряда инструкций, но не возвращение какого-то значения. В этом случае можно просто откинуть инструкцию *return*. В ряде языков, такие функции, не возвращающие значения, называются процедурами, в Python это просто один из возможных вариантов воплощения функции. Например, в продолжение к предыдущему примеру с функцией *L(x, n)*:



```
def table(x):
print '\nx=%g, ln(1+x)=%g' % (x, log(1+x))
for n in [1, 2, 10, 100, 500]:
value, next, error = L(x, n)
print 'n=%-4d %-10g (next term: %8.2e '\
'error: %8.2e)' % (n, value, next, error)
```


Вызов функции:

```
table(10)
table(1000)
```

дает на выходе:

x=10, ln(1+x)=2.3979 n=1 0.909091 (next term: 4.13e-01 error: 1.49e+00) n=2 1.32231 (next term: 2.50e-01 error: 1.08e+00) n=10 2.17907 (next term: 3.19e-02 error: 2.19e-01) n=100 2.39789 (next term: 6.53e-07 error: 6.59e-06) n=500 2.3979 (next term: 3.65e-24 error: 6.22e-15) x=1000, ln(1+x)=6.90875 n=1 0.999001 (next term: 4.99e-01 error: 5.91e+00) n=2 1.498 (next term: 3.32e-01 error: 5.41e+00) n=10 2.919 (next term: 8.99e-02 error: 3.99e+00) n=100 5.08989 (next term: 8.95e-03 error: 1.82e+00) n=500 6.34928 (next term: 1.21e-03 error: 5.59e-01)


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

Когда мы явно не указываем инструкции *return*, это еще не означает, что таковой не имеется, в этом случае Python вставляет "невидимую" инструкцию *return None*. *None* — специальный объект Python, который представляет собой "ничто". Он преследует своим существованием те же цели, что в математике ноль обозначает отсутствие соответствующего количества единиц или десятков, сотен и так далее. В других языках программирования, таких как C, C++ и Java схожий смысл имеет слово *void*. Таким образом, функция *table* кроме того что выполняет записанные в ней инструкции, будет возвращать и объект особого типа, объект *None*. Например, при запросе *result = table(500)*, переменная *result* будет ссылаться на объект *None*.

Значение *None* часто используется для переменных, которые должны присутствовать в программе, но значение их не определено. Стандартный способ проверить является ли объект *obj* объектом *None* или нет, таков:



```
if obj is None:
...
if obj is not None:
...
```


Также можно использовать конструкцию *obj == None*, который сравнивает значения. Напомним, что оператор *is* определяет ссылаются ли имена на один и тот же объект:



```
>>> a = 1
>>> b = a
>>> a is b # a и b ссылаются на один объект
True
>>> c = 1.0
>>> a is c
False
>>> a == c # a и c математически равны
True
```



## Аргументы по умолчанию

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

```
>>> def somefunc(arg1, arg2, kwarg1=True, kwarg2=0):
print arg1, arg2, kwarg1, kwarg2
```


Первые два аргумента мы задаем при вызове, их значения неизвестны, при вызове важна их очередность (positional), в то время как два последних обладают значениями по умолчанию (keywords). При задании функции всегда выполняется именно такое следование. Как можно вызвать указанную функцию и что получить, показано далее:



```
>>> somefunc('Hello', [1, 2])
Hello [1, 2] True 0
>>> somefunc('Hello', [1, 2], kwarg1='Hi')
Hello [1, 2] Hi 0
>>> somefunc('Hello', [1, 2], kwarg2='Hi')
Hello [1, 2] True Hi
>>> somefunc('Hello', [1, 2], kwarg2='Hi', kwarg1=6)
Hello [1, 2] 6 Hi
```


Последовательность для *keywords* не имеет значения. Можно даже и не думать о порядке и смешивать *positional* и *keywords*, если их всех при вызове записывать в формате *имя=значение*:



```
>>> somefunc(kwarg2='Hello', arg1='Hi', kwarg1=6, arg2=[1, 2])
Hi [1, 2] 6 Hello
```



### Пример

Рассмотрим функцию от *t*, которая также содержит некоторые параметры, а именно *A*, *a* и *w* : . Мы можем сопоставить математической функции *f * функцию Python, в которой параметры *A*, *a* и *w* будут обладать некоторыми значениями по умолчанию:

```
from math import pi, exp, sin
def f(t, A=1, a=1, omega=2*pi):
return A*exp(-a*t)*sin(omega*t)
```


Теперь мы можем вызывать функцию любым интересным нам способом:



```
v1 = f(0.2)
v2 = f(0.2, omega=1)
v3 = f(1, A=5, omega=pi, a=pi**2)
v4 = f(A=5, a=2, t=0.01, omega=0.1)
v5 = f(0.2, 0.5, 1, 1)
```


В последнем варианте показано, что *keyword * аргументы могут рассматриваться как *positional*, то есть естественно использовать их и без обозначения имен, но помня их очередность при определении функции.



## Doc strings

В Python имеется договоренность о том, что строки документации (doc strings) вставляются сразу после заголовка функции. Doc strings содержат краткое описание цели функции и объясняют смысл аргументов и возвращаемых значений. Doc strings заключаются в тройные кавычки """, которые позволяют разбивать текст между ними в несколько строк. Вот два примера использования строк документации в функциях, короткий и длинный:



```
def C2F(C):
"""Convert Celsius degrees (C) to Fahrenheit."""
return (9.0/5)*C + 32
def line(x0, y0, x1, y1):
"""
Compute the coefficients a and b in the mathematical
expression for a straight line y = a*x + b that goes
through two points (x0, y0) and (x1, y1).
x0, y0: a point on the line (floats).
x1, y1: another point on the line (floats).
return: coefficients a, b (floats) for the line (y=a*x+b).
"""
a = (y1 - y0)/float(x1 - x0)
b = y0 - a*x0
return a, b
```


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



```
print line.__doc__
```


в результате которой (как вы можете проверить сами) выводится все заключенное содержимое.



## Функции в качестве аргументов

Программы, что-то рассчитывающие часто требуют, чтобы функции были аргументами для других функций. Например, нам требуется вычислить численно вторую производную от функции *f(x)*:




где *h* — малое число, при стремлении его к нулю мы находим наиболее точный ответ. Функция Python для такой задачи может быть записана следующим образом:



```
def diff2(f, x, h=1E-6):
r = (f(x-h) - 2*f(x) + f(x+h))/float(h*h)
return r
```


Аргумент *f* здесь выступает как любой другой аргумент. Применение diff2 выглядит, например, так:



```
def g(t):
return t**(-6)
t = 1.2
d2g = diff2(g, t)
print "g''(%f)=%f" % (t, d2g)
```



## Lambda-функции

Существует такая однострочная конструкция, которая иногда бывает очень удобна. Называется она lambda-функция и сразу же пример:



```
f = lambda x: x**2 + 4
```


и это то же самое, что



```
def f(x):
return x**2 + 4
```


В общем случае любая конструкция вида



```
def g(arg1, arg2, arg3, ...):
return expression
```


может быть записана как



```
g = lambda arg1, arg2, arg3, ...:expression
```


Lambda-функции часто используются для быстрого определения функции как аргумента другой функции, как, например, недавно для функции *diff2* можно было определять диффуренцируемую *g(t)* тут же на месте при вызове *diff2*:



```
d2 = diff2(lambda t: t**(-6), 1, h=1E-4)
```


Lambda-функции очень удобны для того, чтобы определять небольшие функции "на лету" и потому очень популярны среди многих программистов.



## Чему мы научились

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