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

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


C = 21
F = (9/5)*C + 32
print  F


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


Как сделать выбор

править

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


def f(x):
    if 0 <= x <= pi:
        value = sin(x)
    else:
        value = 0
    return value


Общая структура if-else выглядит так:


if условие:
    <блок инструкций, выполняемых, если условие True>
else:
    <блок инструкций, выполняемых, если условие False>


Вот еще один пример:


if C < -273.15:
    print '%g degrees Celsius is non-physical!' % C
    print 'The Fahrenheit temperature will not be computed.'
else:
    F = 9.0/5*C + 32
    print F
print 'end of program'


В случае, если температура в градусах Цельсия оказывается ниже абсолютного нуля, что противоречит постулатам термодинамики, то выводится замечание об отсутствии физического смысла и абсурдности перевода значения температуры в градусы шкалы Фаренгейта. Если же ошибки не возникает, то происходит пересчет и вывод значения. Независимо от того, выполнилось ли условие или нет, выполняется инструкция в последней строке, поскольку она не относится к блоку if-else.


if-elif-else

править

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


if condition1:
    <block of statements>
elif condition2:
    <block of statements>
elif condition3:
    <block of statements>
else:
    <block of statements>
<next  statement>


Последняя else-часть в случае ненадобности может быть опущена. Конкретное применение можно понять на таком примере:


def N(x):
    if x < 0:
        return 0.0
    elif 0 <= x < 1:
        return x
    elif 1 <= x < 2:
        return 2 - x
    elif x >= 2:
        return 0.0


В этом коде записана кусочно определенная математическая функция, которой в разных интервалах x соответствует разное описание. Если к ней внимательно приглядеться, то можно заметить, что ее можно еще значительно оптимизировать: в первой и последней инструкции return возвращается ноль. Другими словами, на языке математики можно сказать, что функция равна нулю везде, кроме интервала (0; 2). Более короткая и ясная запись функции:


def N(x):
    if 0 <= x < 1:
        return x
    elif 1 <= x < 2:
        return 2 - x
    else:
        return 0

Зачастую от if-else требуется совсем немного:


if condition:
    a = value1
else:
    a = value2


Ввиду частоты такой простой конструкции в Python есть ее однострочный аналог:


a = value1 if condition else value2


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


def f(x):
    return sin(x) if 0 <= x <= 2*pi else 0


Вспомнив конец прошлого урока, мы можем сказать, что как только мы видим миниатюрную функцию, ее можно записать в lambda-виде:


f = lambda x: sin(x) if 0 <= x <= 2*pi else 0


Заметим, что это возможно только с таким представлением, поскольку lambda-функция не работает с блоками if-else, но очень любит выражения.

Задаем вопросы и получаем ответы

править

Один из самых простых способов получить данные для программы — спросить у пользователя и дать ему ответить. Далее этот ответ заносится в переменную и обрабатывается инструкциями программы. Например, мы можем спросить С=? и подождать пока пользователь введет ответ. Далее программа читает это число и записывает его в переменную С. Все это осуществляется одной простой инструкцией:


C = raw_input('C=? ')


Функция raw_input всегда возвращает ответ пользователя как строковой объект. Поэтому, чтобы далее работать с переменной, как с объектом типа float, мы должны его конвертировать, изменить тип: C = float(C). Наша программа в итоге:


C = raw_input('C=? ')
C = float(C)
F = (9./5)*C + 32
print F


Итак, функция raw_input принимает строковый аргумент, который выводит на экран, ждет, пока пользователь не введет ответ, что определятся по нажатию [Enter]. Введенное значение присваивается объекту типа string, который мы далее конвертируем в тип float и, подставляя в формулу, выводим результат.


Волшебная функция eval

править

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


>>> r = eval('1+2')
>>> r
3
>>> type(r)
<type 'int'>


Результат выражения r = eval('1+2') тот же самый, если бы мы записали r = 1+2:


>>> r = 1+2
>>> r
3
>>> type(r)
<type 'int'>


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


>>> r = eval('2.5')
>>> r
2.5
>>> type(r)
<type 'float'>

>>> r = eval('"math  programming"')
>>> r
'math  programming'
>>> type(r)
<type 'str'>

>>> r = eval('[1, 6, 7.5]')
>>> r
[1, 6, 7.5]
>>> type(r)
<type 'list'>

>>> r = eval('(-1, 1)')
>>> r
(-1,  1)
>>> type(r)
<type 'tuple'>

>>> from  math import sqrt
>>> r = eval('sqrt(2)')
>>> r
1.4142135623730951
>>> type(r)
<type 'float'>


В общем, с eval все понятно, но в чем от нее польза? Вспомним про raw_input, которая после разговора с пользователем возвращает его ответ как объект типа string. А функция eval такие объекты принимает и выполняет. Этому можно найти множество применений. Вот одно из них: напишем маленькую программу, которая принимает и складывает два значения. Значениями может быть все, что угодно, к чему можно применять операцию сложения: целые и дробные числа, строки, списки и так далее. Поскольку мы не знаем, что именно пользователь складывает, то здесь и будет удобно использовать eval:


i1 = eval(raw_input('Give  input: '))
i2 = eval(raw_input('Give  input: '))
r = i1 + i2
print '%s + %s becomes %s\nwith value %s' % \
(type(i1), type(i2), type(r), r)


После запуска программы имеем:


Give input: 4
Give input: 3.1
<type 'int'> + <type 'float'> becomes <type 'float'>
with value 7.1


В этой же программе можно посмотреть и как сложатся списки. Добавление друг к другу строк происходит только, если они введены в кавычках. Естественно, что объекты разных типов не могут суммироваться, так же, как это происходит и в Python. Все это вы можете проверить, поэкспериментировав с программой. Отсюда видно первое применение функции eval — обработка строк «на лету», что очень удобно при разработке программ. Другой пример или подпример предыдущего представляет собой возможность ввода часто изменяющегося кода — обрабатываемых математических формул:


formula = raw_input('Give a formula involving x: ')
x = eval(raw_input('Give x: '))
from math import *     #  теперь доступны все функции из math
result = eval(formula)
print '%s for x=%g yields %g' % (formula, x, result)


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


Give a formula involving x: 2*sin(x)+1
Give x: 3.14
2*sin(x)+1 for x=3.14 yields 1.00319

Волшебная функция exec

править

Представив функцию eval, превращающую строковые объекты в код Python, нам представляется удобный случай познакомиться и с ее старшей сестрой, функцией exec, которая позволяет выполнять (execute) любой код на Python, не только выражения:


formula = raw_input('Write a formula involving x: ')
code = """
def f(x):
    return %s
""" % formula
exec(code)


Если, отвечая на вопрос, мы введем, например, sin(x)*cos(3*x) + x**2, то formula примет его и далее будет использована строкой code, которая будет работать как если бы:


"""
def f(x):
    return sin(x)*cos(3*x) +  x**2
"""


но при этом формула может быть любой, которую мы захотим. Далее, exec выполняет все, что записано в code, как если бы мы записали все это сами. Таким образом, мы можем превратить любую данную пользователем функцию в функцию Python!

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


x = 0
while x is not None:
    x = eval(raw_input('Give x (None to quit): '))
    if x is not None:
        print 'f(%g)=%g' % (x, f(x))


То есть теперь у нас есть программа, в которую мы вводим любую формулу, а потом любые значения x, пока нам не надоест и мы не введем None:


Write a formula involving x: x**4 + x
Give x (None to quit): 1
f(1)=2
Give x (None to quit): 4
f(4)=260
Give x (None to quit): 2
f(2)=18
Give x (None to quit): None


Command line

править

Чтение из аргументов командной строки

править

В Unix-системах особенно часто применяется ввод данных через командную строку (command line). Может быть, это не так красиво, как графический интерфейс, но имеет ряд преимуществ в удобстве — одновременном вызове и передаче данных. Даже если вы работаете только в Windows, советуем просмотреть этот раздел, поскольку, во-первых, он сопровождается информацией, которая нам в любом случае пригодится далее, во-вторых, лучше пораньше столкнуться с интерфейсом командной строки, чтобы уметь с ним работать при случайной встрече. В Windows также имеется интерфейс командной строки (Пуск → Все программы → Стандартные → Командная строка), перед тем как дальше изучать работу с командной строкой, узнайте хотя бы о том как перемещаться между папками, чтобы вы могли запустить программу.

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


c2f.py 21
69.8


В этом нам поможет модуль sys, который требуется нам для извлечения списка argv. Этот список содержит все обращения командной строки к программе: argv[0] это всегда имя самой программы, argv[1] — аргумент, что мы ей передаем, в нашем случае число 21. Тогда наша программа с именем c2f.py должна выглядеть так:

import sys
C = float(sys.argv[1])
F = 9.0*C/5 + 32
print F


Можно передавать и несколько аргументов:

import sys
t = float(sys.argv[1])
v0 = float(sys.argv[2])
g = 9.81
y = v0*t - 0.5*g*t**2
print y


ball_variables2.py 0.6 5
1.2342


Наконец, не стоит забывать и о возможности использования eval:

import sys
i1 = eval(sys.argv[1])
i2 = eval(sys.argv[2])
r = i1 + i2
print '%s + %s becomes %s\nwith value %s' % \
(type(i1), type(i2), type(r), r)


add_cml.py 2 3.1
<type 'int'> + <type 'float'> becomes <type 'float'>
with value 5.1

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

править

Давайте напишем программу addall.py, которая складывает любое количество аргументов, передаваемых в командную строку. То есть, чтобы наша работа в командной строке выглядела примерно так:


addall.py 1 3 5 -9.9
The sum of 1 3 5 -9.9 is -0.9


Как же это сделать? Ведь раньше мы записывали для каждого аргумента свой элемент списка argv, а теперь количество элементов нам неизвестно. Здесь нам пригодятся наши знания о срезах. Тогда наше первое решение может выглядеть так:


import sys
s = 0
for arg in sys.argv[1:]:
    number = float(arg)
    s += number
print 'The sum of ',
for arg in sys.argv[1:]:
    print arg,
print 'is ', s


Заметьте, что строка не разбивается, поскольку в конце инструкций print стоит запятая. Более компактная запись тоже возможна, если не забывать о приятных особенностях языка и форматирования:


import sys
s = sum([float(x) for x in sys.argv[1:]])
print 'The sum of %s is %s' % (' '.join(sys.argv[1:]), s)


Здесь мы с помощью генерации списков конвертируем список sys.argv[1:] в список float-объектов и посылаем его в суммирующую функцию sum. Конструкция S.join(L) размещает все элементы из списка L друг за другом, «склеивая» их строкой S (в данном случае это пробел), то есть в результате получается строка элементов, разделенных пробелами. С этой и другими полезными функциями для строк мы познакомимся на соответствующем уроке.


Option–value pairs

править

Передача аргументов командной строке похожа на передачу аргументов функции — значения должны идти в строго определенном порядке, о котором приходится помнить. Было бы неплохо и для командной строки иметь что-то вроде keyword arguments для функций. Эта возможность представляется как пары -option value, где под option понимается имя аргумента.

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


 .


Эта формула принимает четыре параметра: s0, v0, a и t. Мы можем написать программу location.py, которая принимает эти параметры и их значения из командной строки:


location.py --t 3 --s0 1 --v0 1 --a 0.5


Все эти параметры могут обладать заранее заданными значениями, так чтобы можно было изменять лишь нужные. Например, если задано, что s0 = 0, v0 = 0, a = 1 и t = 1 и мы хотим изменить только t, то запуск программы выглядит так:


location.py --t 3


В Python для этих пар имеется специальный модуль getopt. Рецепт его конкретного использования таков:


# устанавливаем значения по умолчанию:
s0 = v0 = 0;  a = t = 1
import getopt, sys
options, args = getopt.getopt(sys.argv[1:], '', [t=', 's0=', 'v0=', 'a='])


Заметьте, что имена параметров вводятся без двойного дефиса, он является частью синтаксиса. Знак равенства показывает, что параметр предполагает следование за именем значения (без знака равенства применяется только для определения булевых переменных).

Получаемый объект options представляет собой список двойных кортежей, содержащих пары, полученные в командной строке, например:


[('--v0', 1.5), ('--t', 0.1), ('--a', 3)]


В этом случае пользователь задал все параметры, кроме s0. Объект args возвращает из getopt.getopt все оставшиеся аргументы строки, то есть аргументы не являющиеся option value парами. В нашем примере таковых нет. Типичный способ дальнейшей обработки состоит в разделении по именам переменных с помощью базовой конструкции, изученной в начале этого урока и двойному проходу в цикле for:


for option, value in options:
    if   option == '--t':
        t = float(value)
    elif option == '--a':
        a = float(value)
    elif option == '--v0':
        v0 = float(value)
    elif option == '--s0':
        s0 = float(value)