Готовый перевод How to Think Like a Computer Scientist: Learning with Python 2nd Edition. / Как стать программистом: учимся вместе с Python. Второе издание.: Chapter 5 Fruitful functions

5 Плодотворные функции

5.1 Возврат значений

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

biggest = max(3, 7, 2, 5)
x = abs(3 - 11) + 10

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

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

def area(radius):
    temp = 3.14159 * radius**2
    return temp

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

def area(radius):
    return 3.14159 * radius**2

С другой стороны, временные переменные типа temp могут упростить процесс отладки.

Иногда бывает полезно использовать несколько выражений return в каждой ветви вычислений. Мы уже работали со встроенной функцией abs, теперь попробуем написать свой аналог:

def absolute_value(x):
    if x < 0:
        return -x
    else:
        return x

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

По другому эту функцию можно написать, исключив операцию else:

def absolute_value(x):
    if x < 0:
        return -x
    return x

Эти две функции работают совершенно одинаково.

Код, написанный после выражения return никогда не будет вычислен; его назывют мертвым кодом.

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

def absolute_value(x):
    if x < 0:
        return -x
    elif x > 0:
        return x

Эта версия не корректна, потому что не предусмотрен вариант проверки нуля. В этом случае функция вернет специальное значение None.

>>> print absolute_value(0)
None

None - это уникальное значение с типом данных NoneType.

>>> type(None)

В python все функции возвращают None, если явно не возвращают другое значение.

5.2 Разработка программы

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

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

Для примера, допустим, что нам нужно найти расстояние между двумя точками с координатами (x1, y1) и (x2, y2). Расстояние вычисляется по теореме Пифагора.

Первым шагом будет представление математической функции в окружении python. Другими словами, какие параметры нужно будет ввести, и что получить на выходе.

В этом случае, две точки - это ввод, представленные четырьмя параметрами. Возвращаемое значение - это расстояние, дробное число.

Мы готовы написать каркас функции:

def distance(x1, y1, x2, y2):
    return 0.0

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

Для тестирования функции, вызовем ее с простыми параметрами:

>>> distance(1, 2, 4, 6)
0.0

Для наших параметров, горизонтальное расстояние будет равно 3, вертикальное - 4, а расстояние между точками - 5 (гипотинуза треугольника 3-4-5). Когда запускаете функцию на тестирование, лучше знать правильный ответ.

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

Логически, на втором этапе нужно найти разницу между координатами x2-x1 и y2-y1. Мы сохраним результаты во временных переменных dx и dy и выведем их на экран.

def distance(x1, y1, x2, y2):
    dx = x2 - x1
    dy = y2 - y1
    print "dx is", dx
    print "dy is", dy
    return 0.0

Если функция работает, на экране мы увидим числа 3 и 4. Зная ответ, мы можем предположить, что функция приняла параметры и правильно выполнила свои операции. Если ответы не совпадают, нам нужно разобраться в работе всего пары строк.

Далее, вычислим сумму квадратов dx и dy:

def distance(x1, y1, x2, y2):
    dx = x2 - x1
    dy = y2 - y1
    dsquared = dx**2 + dy**2
    print "dsquared is: ", dsquared
    return 0.0

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

Мы опять запускаем программу и смотрим, что получилось. Должно получиться 25.

Наконец, воспользовавшись возведением в степень 0.5, найдем квадратный корень из полученной суммы квадратов:

def distance(x1, y1, x2, y2):
    dx = x2 - x1
    dy = y2 - y1
    dsquared = dx**2 + dy**2
    result = dsquared**0.5
    return result

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

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

Ключевые аспекты метода:

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

5.3 Композиция

Как мы уже знаем, можно вызвать функцию из другой функции. Эта способность называется композицией.

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

Будем считать, что координаты центра хранятся в переменных xc и yc, а координаты точки, лежащей на периметре, будут хранится в переменных xp и yp. Сначала нам нужно вычислить радиус круга, который является расстоянием между двумя заданными точками. К счастью, мы только что написали подобную функцию, так что, все что нам надо, это воспользоваться ей:

radius = distance(xc, yc, xp, yp)

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

result = area(radius)
return result

Обернем это все в функцию:

def area2(xc, yc, xp, yp):
    radius = distance(xc, yc, xp, yp)
    result = area(radius)
    return result

Мы назвали новую функцию area2, потому как имя area уже занято. В одном модуле каждая функция должна носить уникальное имя.

Временные переменные radius и result полезны на стадии разработки и отладки, но когда программа отлажена, мы можем ее скомпановать:

def area2(xc, yc, xp, yp):
    return area(distance(xc, yc, xp, yp))

5.4 Булевы функции

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

def is_divisible(x, y):
    if x % y == 0:
        return True
    else:
        return False

Имя этой функции - is_divisible. Обычно подобным булевым функциям дают имена, звучащие как вопросы, на которые нужно отвечать да или нет. Функция is_divisible возвращает истину или ложь в зависимости от того, делится ли x на y или нет.

Мы можем сделать функцию более компактной, приняв во внимание тот факт, что условие в операции if само по себе является булевым выражением. Мы можем вернуть значение напрямую, исключив выражение if:

def is_divisible(x, y):
    return x % y == 0

Следующие примеры показывают функцию в действии:

>>> is_divisible(6, 4) False
>>> is_divisible(6, 3) True

Булевы функции чаще используют в условных операциях:

if is_divisible(x, y):
    print "x is divisible by y"
else:
    print "x is not divisible by y"

Можно, конечно, написать так:

if is_divisible(x, y) == True:

но это излишне.

5.5 Тип функция

Функция - это еще один тип в python в добавок к int, float, str, bool и NoneType.

>>> def func():
... return "function func was called..."
...
>>> type(func) <type 'function'>
>>>

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

def f(n):
    return 3*n - 6

def g(n):
    return 5*n + 2

def h(n):
    return -2*n + 17

def doto(value, func):
    return func(value)

print doto(7, f)
print doto(7, g)
print doto(7, h)

Функция doto вызывается три раза. 7 - аргумент value, а функции f, g и h - аргументы func. На выходе скрипта получим:

15
37
3

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

5.6 Программирование со стилем

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

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

- используйте 4 пробела для отступов
- импорт функций должен происходить в начале скрипта
- отделяете объявление функции двумя пустыми строками
- объявления функций должны находиться в одном месте в начале скрипта
- держите вместе выражения импорта функций, вызова функций

5.7 Тройные кавычки

В добавок к одиночным и двойным кавычкам, с которыми мы познакомились раньше, python также работает с тройными кавычками:

>>> type("""This is a triple quoted string using 3 double quotes.""")
<type 'str'>
>>> type('''This triple quoted strings uses 3 single quotes.''')
<type 'str'>
>>>

Тройные кавычки внутри себя могут содержать текст как с двойными так и одинарными кавычками:

>>> print '''"Oh no", she exclaimed, "Ben's bike is broken!"'''
"Oh no", she exclaimed, "Ben's bike is broken!"
>>>

Наконец, тройные кавычки могут содержать многострочный текст:

>>> message = """This message will
...span several
...lines."""
>>> print message
This message will
span several
lines.
>>>

5.8 Unit-тесты с doctest

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

В python есть встроенный модуль doctest предназначенный для unit-тестирования. Доктектсы в тройных кавычках могут быть написаны в начале тела функции или скрипта.

Модуль doctest автоматически выполняет любое выражение начинающеюся с >>> и сравнивает следующую строку с выводом интерпритатора

Чтобы увидеть как это работает, поместите следующее в скрипт с именем myfunctions.py :

def is_divisible_by_2_or_5(n):
"""
>>> is_divisible_by_2_or_5(8)
True
"""

if __name__ == '__main__':
import doctest
doctest.testmod()

Последние три строчки запускают doctest. Поместите их в конец любого файла, содержащий doctest. Мы объясним как они работают в Главе 10, когда мы будет обсуждать модули.

Запуская скрипт вы получите следующий результат:

$ python myfunctions.py
**********************************************************************
File "myfunctions.py", line 3, in __main__.is_divisible_by_2_or_5
Failed example:
is_divisible_by_2_or_5(8)
Expected:
True
Got nothing
**********************************************************************
1 items had failures:
1 of 1 in __main__.is_divisible_by_2_or_5
***Test Failed*** 1 failures.
$

Это пример проваленного теста. Тест говорит: если вы вызываете is_divisible_by_2_or_5(8) результат должен быть True. Так как is_divisible_by_2_or_5 написан без возврата чего либо, тест проваливается, и doctest сообщает нам что ожидается True, но ничего не получено.

Мы можем сдать этот тест возвратив True:

def is_divisible_by_2_or_5(n):
"""
>>> is_divisible_by_2_or_5(8)
True
"""
return True

if __name__ == '__main__':
import doctest
doctest.testmod()

Переведено на Нотабеноиде
http://notabenoid.org/book/9398/29767

Переводчики: czech, Trashman

Настройки

Готово:

75.67% КП = 1.0

Непереведённые фрагменты будут пропущены.