Основные инструкции

Инструкция if, двоеточие и отступы

Рассмотрим следующий пример, являющийся ядром так называемого алгоритма блуждания, используемого во многих приложениях, например, в производство новых материалов и при изучения мозга. Действие заключается в случайном движении на север (N), юг (S), запад (W) или восток (E) с одинаковой вероятностью.

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

Для реализации этого процесса мы сгенерируем случайное число из интервала \( [0,1) \). Будем считать, что \( [0,0.25) \) соответствует N, \( [0.25,0.5) \) --- E, \( [0.5,0.75) \) --- S и \( [0.75, 1) \) --- W. Пусть x, y --- координаты точки на плоскости, a d --- длина перемещения. Мы должны сначала проверить принадлежность r соответсвующему отрезку и выполнить действие. Когда ответ на вопрос если положительный (true), мы выполняем действие. Если ответ отрицательный (false), мы обрабатываем вопрос иначе если и т.д. Последний тест может обрабатываться как иначе, так как уже обработаны все варианты.

Код на Python для такой проверки выглядит следующим образом

# -*- coding: utf-8 -*-

import random

x = 0.0; y = 0.0; d = 0.1

r = random.random()
if 0 <= r < 0.25:
	# перемещаемся на север
	y = y + d
elif 0.25 <= r < 0.5:
	# перемещаемся на восток
	x = x + d
elif 0.5 <= r < 0.75:
	# перемещаемся на юг
	y = y - d
else:
	# перемещаемся на запад
	x = x - d

print x, y

Python предоставляет зарезервированные слова if, elif (сокращенно от else if) и else. Эти инструкции работают по следующим правилам:

Как видим здесь используются булевские выражения, которые могут использовать следующие логические операторы: ==, !=, <, <=, > и >=.

Функции

Функции широко используются в программировании и являются одним из основных понятий, которые необходимо освоить. В простейшем случае понятие функция в программе очень близко к понятию математической функции: некоторое входное число \( x \) преобразуется в некоторое выходное число. Один из примеров функция \( \mathrm{arctg\,}x \), которой в Python соответствует функция atan(x). Функции в Python могут иметь ряд входных параметров и возвращать одно или несколько значений или ничего не возвращать. Цель использования функций состоит из двух частей:

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

Мы можем, например, модифицировать сценарий ball.py из раздела (Программа на Python с переменными), добавив функцию, как сделано в сценарии ball_func.py:

# -*- coding: utf-8 -*-
# Программа для вычисления положения мяча при вертикальном движении
# с использованием функции

def y(t): 
	v0 = 5       # Начальная скорость
	g = 9.81     # Ускорение свободного падения
	return v0*t - 0.5*g*t**2
	
t = 0.6      # Время
print y(t)
t = 0.9
print y(t)

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

Что на самом деле происходит, когда интерпретатором Python встречается такой код? Строка с ключевым словом def сообщает интерпретатору, что здесь определяется функция с именем y, которая имеет один входной параметр t.

Если функция содержит инструкции if-elif-else, каждый из блока может возвращать значение с помощью return:

def check_sign(x):
    if x > 0:
       return u'x --- положительное число'
    elif x < 0:
       return u'x --- отрицательное число'
    else:
       return u'x равно нулю'

В этой ситуации только один блок будет выполнен при вызове функции check_sign.

У функции могут отсутствовать аргументы, или быть много аргументов, которые просто перечисляются в круглых скобках, и разделены запятыми. Проиллюстрируем это на примере. Модифицируем пример с подбрасыванием мяча следующим образом. Предположим, что мы бросаем мяч не вертикально вверх, а под углом. В этом случае требуется две координаты для описания позиции мяча в каждый момент времени. Согласно закону Ньютона (сопротивлением воздуха пренебрегаем) вертикальная координата описывается формулой \( y(t) = v_{0y}t - 0.5g t^2 \), а горизонтальная координата --- \( x(t) = v_{0x}t \). Теперь можно включить оба эти выражения в новую версию сценария, который выводит позицию мяча для заданного момента времени. Допустим, мы хотим вычислить эти значения для двух моментов времени \( t= 0.6 \) с и \( t = 0.9 \) с. Мы можем задать значения компонент начальной скорости \( v_{0x} \) и \( v_{0y} \). Сценарий может выглядеть следующим образом ball2d.py

def y(v0y, t):
	g = 9.81
	return v0y*t - 0.5*g*t**2

def x(v0x, t):
	return v0x*t

initial_velocity_x = 2.0
initial_velocity_y = 5.0

time = 0.6
print x(initial_velocity_x, time), y(initial_velocity_x, time)
time = 0.9
print x(initial_velocity_x, time), y(initial_velocity_x, time)

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

1.2 -0.5658
1.8 -2.17305

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

def xy(v0x, v0y, t):
	g = 9.81
	return v0x*t, v0y*t - 0.5*g*t**2

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

print xy(initial_velocity_x, initial_velocity_y, time)

Два возвращаемых значения могут быть присвоены двум переменным, например, так

x, y = xy(initial_velocity_x, initial_velocity_y, time)

Теперь переменные x и y могут использоваться в коде.

Существует возможность варьировать число входных и выходных параметров (используя *args и **kwargs конструкции для аргументов). Однако, мы не будем рассматривать этот вариант.

Переменные, которые определены внутри функции (например, g в xy), являются локальными переменными. Это означает, что они видимы только внутри функции. Поэтому, если мы случайно попытаемся использовать переменную g вне функции, мы получим сообщение об ошибке. Переменная time определена вне функции и поэтому является глобальной переменной. Она видима как вне, так и внутри функции(ий). Если мы определим одну глобальную и одну локальную переменные с одним и тем же именем, то внутри функции будет видима только локальная переменная, при этом глобальная переменная не изменяется при изменении локальной переменной с тем же именем.

Аргументы перечисленные в заголовке определения функции, как правило, являются локальными переменными. Если нужно изменить значение глобальной переменной внутри функции, следует определить переменную внутри функции как глобальную, т.е., если глобальная переменная имеет имя x, то нам нужно написать global x внутри определения функции, прежде чем изменять её значение. После выполнения функции, x будет иметь измененное значение. Следует стараться определять переменные там, где они необходимы.

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

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

# -*- 4th Start
def xy_named(t, v0x = 0, v0y = 0):
	g = 9.81
	return v0x*t, v0y*t - 0.5*g*t**2

Здесь t --- обычный или позиционный аргумент, тогда как v0x и v0y --- именованные аргументы. В общем случае, может быть несколько позиционных и несколько именованных аргументов, но позиционные аргументы всегда должны следовать перед именованными в определении функции. Именованные аргументы имеют значение по умолчанию, в нашем примере vx0 и v0y по умолчанию равны нулю. В сценарии функция xy_named может быть вызвана разными способами. Например,

print xy_named(0.6)

выполнит вычисления с t = 0.6 и значениями,заданными по умолчанию (в нашем случае 0) для v0x и v0y. Два возвращаемых функцией xy_named значения будут выведены на экран. Если мы хотим использовать другое значение для переменной v0y, мы можем, например, написать

print xy_named(0.6, v0y=4.0)

т.е. выполнить функцию xy_named с t = 0.6, v0x = 0 (значение по умолчанию) и v0y = 4.0. В случае, когда есть несколько позиционных аргументов, они должны следовать в том порядке, в котором перечислены в определении функции, если мы не задаем явно имя переменной в вызове функции. Используя явное задание имени переменной в вызове функции, мы можем использовать любой порядок аргументов функции. Например, мы можем вызвать функцию xy_named следующим образом:

print xy_named(v0y=4.0, v0x = 1.0, t = 0.6)

При использовании любого языка программирования существует хорошая традиция включать небольшое пояснение о том, что делает функция, если это не очевидно. Такое пояснение называется строкой документации (doc string), которую в Python следует помещать в начале функции. Это пояснение предназначено для программистов, которые желают понять код, так что следует описать цель данного кода и, возможно, описать аргументы и возвращаемые значения. Например, мы можем написать

# -*- 5th Start
def xy_named(t, v0x = 0, v0y = 0):
	"""Вычисляются координаты x и y положения мяча в момент времени t """
	g = 9.81
	return v0x*t, v0y*t - 0.5*g*t**2

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

Функции прямо передаются как аргументы других функций, что проиллюстрировано в сценарии functions_as_args.py

# -*- coding: utf-8 -*-

def sum_xy(x, y):
	return x + y

def prod_xy(x, y):
	return x*y

def treat_xy(f, x, y):
	return f(x,y)

x = 2; y = 3
print treat_xy(sum_xy, x, y)
print treat_xy(prod_xy, x, y)

При запуске этот сценарий сначала выведет на экран сумму x и y, а затем произведение. Видно, что treat_xy принимает имя функции в качестве первого аргумента. Внутри treat_xy это имя используется для вызова соответствующей функции.

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

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

f = lambda x, y: x + 2*y

эквивалентно

def f(x, y):
    return x + 2*y

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

print treat_xy(lambda x, y: x*y, x, y)

Издержки при вызове функций

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

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

In [1]: import numpy as np

In [2]: a = np.zeros(1000000)

In [3]: def add(a, b):
   ...:     return a+b
   ...: 

In [4]: %timeit for i in range (len(a)): a[i] = add(i, i+1)
   10 loops, best of 3: 150 ms per loop

In [5]: %timeit for i in range (len(a)): a[i] = i + i+1
  10 loops, best of 3: 93.2 ms per loop

Циклы

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

Цикл for

Начнем с инструкции for. Предположим, мы хотим вычислить квадраты чисел от 3 до 7. Это можно сделать с помощью следующей инструкции:

for i in [3, 4, 5, 6, 7]:
    print i**2

Предупреждение

Обратите внимание на двоеточие и отступ!

Что происходит, когда интерпретатор Python обрабатывает данный код? Во первых ключевое слово for сообщает интерпретатору, что будет выполняться цикл. Python затем придерживается правил, определенных для таких конструкций, и понимает, что (в представленном примере) цикл должен быть выполнен 5 раз (т.е., следует выполнить 5 итераций), при этом переменная i принимает последовательно значения 3, 4, 5, 6, 7. Во время каждой итерации выполняется выражение внутри цикла (т.е. print i**2). После каждой итерации, значение i автоматически изменяется. При достижении последнего значения, выполняется последняя итерация и цикл завершается. При выполнении сценарий выведет последовательно 9, 16, 25, 36 и 49. Переменная i часто называется счетчик цикла, а выбор имени этой переменной (здесь i) остается за программистом.

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

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

range(start, stop, step)

Такой вызов генерирует массив целых чисел от (включая) start до (не включая!) stop с шагом step, т.е. stop-1 --- последнее целое значение. С использованием функции range предыдущий пример будет выглядеть так:

for i in range(3, 8, 1):
    print i**2

По умолчанию, если функция range вызывается с двумя параметрами, они будут приняты в качестве start и stop, при этом step=1. Если задан только один аргумент, он используется в качестве stop. При этом шаг по умолчанию равен 1, а значение start равно 0. Таким образом, следующий вызов

range(6)

вернет целые числа 0, 1, 2, 3, 4, 5.

Отметим, что можно сгенерировать убывающую последовательность целых чисел, задав start > stop и отрицательный шаг.

Модифициурем сценарий ball_plot.py из раздела Программа на Python с векторизацией и построением графиков для иллюстрации использования цикла for. В том примере мы вычисляли высоту мяча в каждую миллисекунду первой секунды его полета и строили график зависимости высоты от времени.

Предположим, мы хотим найти максимальную высоту в течение этого времени. Один из вариантов реализации этого может быть следующим: вычисляем все тысяча значений высоты, сохраняем их в массив, а затем пробегаем весь массив, чтобы найти максимальное значение. Сценарий (max_height.py) может выглядеть так:

# -*- coding: utf-8 -*-

import numpy as np
import matplotlib.pyplot as plt

v0 = 5.0
g = 9.81
t = np.linspace(0, 1, 1000)
y = v0*t - 0.5*g*t**2

max_height = y[0]
for i in range(1, 1000):
	if y[i] > max_height:
		max_height = y[i]

print u'Максимальная достигнутая высота равна %f м' % (max_height)

plt.plot(t, y)
plt.xlabel(u'Время (с)')
plt.ylabel(u'Высота (м)')
plt.show()

Запуск программы даст

Максимальная достигнутая высота равна 1.274210 м

что хорошо согласуется с построенным графиком.

Иногда используются вложенные циклы, например в линейной алгебре. Скажем, нам нужно найти максимум среди элементов матрицы A размера \( 4 \times 4 \). Фрагмент кода может выглядеть так:

max_number = A[0][0]

for i in range(4):
    for j in range(4):
        if A[i][j] > max_number:
	    max_number = A[i][j]

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

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

Цикл while

В языке Python имеется еще одна стандартная циклическая конструкция --- цикл while. Для иллюстрации использования этого цикла рассмотрим другую модификацию сценария ball_plot.py из раздела. Теперь мы изменим его так, чтобы сценарий находил время полета мяча. Предположим, что мы подбросили мяч с немного меньшей начальной скоростью 4.5 м/с. Так как мы все еще будем рассматривать первую секунду полета, то высота в конце этого интервала будет отрицательной. Это означает, что мяч упал ниже своего начального положения. В нашем массиве y мы будем иметь ряд отрицательных значений, которые расположены в конце массива. В сценарии ball_time.py находится момент времени, когда значение высоты становится отрицательным, т.е., когда мяч пересекается прямую \( y = 0 \). Сценарий может быть следующим:

# -*- coding: utf-8 -*-

import numpy as np

v0 = 4.5
g = 9.81
t = np.linspace(0, 1, 1000)
y = v0*t - 0.5*g*t**2

i = 0
while y[i] >= 0:
	i += 1

print u'y = 0 в момент времени ', 0.5*(t[i-1] + t[i])

import matplotlib.pyplot as plt

plt.plot(t, y)
plt.xlabel(u'Время (с)')
plt.ylabel(u'Высота (м)')
plt.show()

При выполнении сценария получим

y = 0 в момент времени  0.917417417417

В приведенном примере цикл while выполняется до тех пор, пока булевское выражение y[i] > 0 возвращает значение True. Отметим, что в этом случае счетчик цикла i введен и инициализирован (i = 0) до начала выполнения цикла и обновляется (i += 1) внутри цикла. Таким образом, для каждой итерации i явно увеличивается на 1.

В отличие от цикла for, программист не должен определять количество итераций при использовании цикла while. Он просто выполняется пока булевское выражение не вернет значение False. Таким образом, в этом случае счетчик цикла не обязателен. Кроме того, если в цикле while используется счетчик, то он не увеличивается автоматически, это нужно делать явно. Конечно, как и в цикле for и в инструкции if может быть несколько строк кода в теле цикла while. Любой цикл for может быть реализован с помощью while, но циклы while являются более общими и не все из них можно реализовать с помощью for.

Следует быть осторожным с так называемыми бесконечными циклами. Могут возникнуть (непреднамеренно) случаи, когда тест в инструкции while никогда не вернет значение False, и сценарий не сможет выйти из цикла. Если вы случайно зациклите сценарий то можно нажать комбинацию клавиш Ctrl-C, чтобы остановить работу сценария.

Списки и кортежи

Списки

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

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

Функция range на самом деле возвращает список. К элементу списка можно обращаться как к элементу массива x[i]. Как и для массивов индексы элементов списка пробегают значения от 0 до n-1, если n --- количество элементов списка. Можно преобразовать список в массив следующим образом: x = array(L).

Список можно создать, например, так:

x = ['hello', 3, 3.15, 4]

В этом случае, x[0] содержит строку hello, x[1] содержит целое число 3, x[2] содержит действительное (float) число 3.15 и т.д. Мы можем добавлять и удалять элементы массива, как показано в следующем примере

x = ['hello', 3, 3.15, 4]
x.insert(0, -2)    # x становится [-2, 'hello', 3, 3.15, 4]
del x[3]           # x становится [-2, 'hello', 3, 4]
x.append(3.15)     # x становится [-2, 'hello', 3, 4, 3.15]

Метод append добавляет элемент в конец списка. Если необходимо, можно создать пустой список x = [] а потом в цикле добавлять элементы к списку. Чтобы узнать длину списка, можно воспользоваться функцией len(x). Эта функция полезна, когда нам нужно обойти список по индексам, так как функция range(len(x)) возвращает все допустимые индексы.

Ранее мы видели обходить все элементы массива с помощью цикла for. Если нам нужно пробежать все элементы списка, мы можем сделать это, как показано в следующем примере:

x = ['hello', 3, 3.15, 4]
for e in x:
    print 'Элемент x: ', e
print `Это были все элементы списка x`

Как видно мы пробегаем элементы массива без использования индексов. Следует понимать, что, когда мы так используем цикл, мы не можем изменять значения элемента списка x, изменяя e. Это означает, что, запись e += 2 ничего не изменит в списке x, так как e используется только для чтения элементов списка.

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

List1 = [1, 2, 3, 4]
List2 = [e*10 for e in List1]

Этот фрагмент кода создает новый список List2, содержащий элементы 10, 20, 30 и 40. Выражение в квадратных скобках for e in List1 означает, что будет последовательно пробегать все элементы списка List1, и для каждого e будет создан новый элемент списка List2 со значением e*10. В более общем случае:

List2 = [E(e) for e in List1]

где E(e) означает любое выражение содержащее e.

В некоторых случаях требуется одновременно пробегать 2 или более списков. Python имеет удобную для этих целей функцию zip. Пример использования функции zip будет дан ниже в сценарии file_handle.py.

Кортежи

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

x = ('hello', 3, 3.15, 4)
for e in x:
    print 'Элемент x: ', e
print `Это были все элементы списка x`

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

Чтение из файлов и запись в файлы

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

# координат x и y
1.0  3.0
2.0  3.9
3.0  5.3
4.0  6.0

Соответствующий сценарий на Python дан в файле file_handle.py

# -*- coding: utf-8 -*-

filename = 'data.txt'
infile = open(filename, 'r')   # открытие файла для чтения
line = infile.readline()       # чтение первой строки

# Чтение x и y из файла и сохранение их в списки
x = []; y = []
for line in infile:
	words = line.split()       # разбиение строки на слова
	x.append(float(words[0]))
	y.append(float(words[1]))
infile.close()

# Преобразование координаты y
from math import log

def f(y):
	return log(y)

for i in range(len(y)):
	y[i] = f(y[i])

# Запись x и y в файл из двух колонок
filename = 'tmp.txt'
outfile = open(filename, 'w')   # открытие файла для записи
outfile.write('# координаты x и y\n')
for xi, yi in zip(x, y):
	outfile.write('%10.5f %10.5f\n' % (xi, yi))
outfile.close()

Такие файлы, содержащие строку комментариев и колонки чисел, достаточно часто используются при научных вычислениях. Поэтому в numpy реализован функционал облегчающий чтение и запись в такие файлы. Ниже представлен пример, реализующий ту же функциональность, что и предыдущий, с использованием функций loadtxt и savetxt из numpy file_handle_np.py:

# -*- coding: utf-8 -*-

filename = 'data.txt'
import numpy as np
data = np.loadtxt(filename, comments="#")
x = data[:,0]
y = data[:,1]
data[:,1] = np.log(y)
filename = 'tmp.txt'
outfile = open(filename, 'w')
outfile.write('# координаты x и y\n')
np.savetxt(outfile, data, fmt='%10.5f')