Программирование и научные вычисления на языке Python/§7
Иногда, ввиду того что человек чаще всего останавливается на задачах определенного круга, бывает желательно неоднократно в разных программах использовать одну и ту же функцию. Простейший способ это сделать — скопировать и вставить старый код в новую программу. Однако, это не относится к хорошему стилю программирования, поскольку вы будете иметь дело с идентичными копиями, а значит обнаружив ошибку, или просто захотев усовершенствовать код, вам придется вносить изменения во все эти копии. Скорее всего, в какой-то момент вы просто запутаетесь во всех версиях вашей изначальной программы и вовсе забросите дело. Отсюда золотое правило — иметь одну и только одну версию любого куска кода. Все программы, которые к нему хотят обратиться, должны находить его лишь в одном месте, где он всегда хранится — в его модуле. Уже на первых уроках мы узнали, как импортировать что-то из модулей. Теперь мы научимся создавать свои собственные.
Пример: прибыль от вклада
правитьКлассическая формула для определения прибыли по вкладу в банке выглядит так:
, | (7.1) |
где A0 начальная сумма денег, A — сумма после n дней с процентом годовых p. Выражение (7.1), таким образом, включает четыре параметра, определим три оставшихся:
, | (7.2) |
, | (7.3) |
. | (7.4) |
Далее мы представили формулы (7.1)-(7.4) в виде четырех функций:
from math import log as ln
def present_amount(A0, p, n):
return A0*(1 + p/(360.0*100))**n
def initial_amount(A, p, n):
return A*(1 + p/(360.0*100))**(-n)
def days(A0, A, p):
return ln(A/A0)/ln(1 + p/(360.0*100))
def annual_rate(A0, A, n):
return 360*100*((A/A0)**(1.0/n) - 1)
Теперь мы хотим сделать эти функции доступными из модуля, который мы, скажем, назовем interest (прибыль), так чтобы мы могли импортировать эти функции и вычислять требуемые значения. Например, так:
from interest import days
A0 = 1; A = 2; p = 5
n = days(A0, A, p)
years = n/365.0
print 'Money has doubled after %.1f years' % years
Как создать модуль описывается, ниже.
Собираем функции в файле модуля
правитьДля того, чтобы сделать модуль для четырех функций present_amount, initial_amount, days и annual_rate, мы просто открываем новый файл и копируем программный код выше для всех четырех функций. И тут же наш файл превращается в модуль Python с тем именем, под которым мы его сохранили. Файл должен обязательно иметь расширение .py, но именем модуля является его основная часть. В нашем случае файл называется interest.py, а модуль поэтому будет вызываться как interest. Для того, чтобы использовать, например, функцию annual_rate мы просто пишем в программе, где она нужна:
from interest import annual_rate
Также доступны и все остальные способы, описанные раньше. Например, конструкция
from interest import *
импортирует все четыре функции. Можем мы и просто импортировать сам модуль:
import interest
и вызывать функции с приставками, например, interest.annual_rate. Стоит сказать, что модуль и программу, использующую его, надо размещать в одной папке. Это не единственный возможный вариант, но для начала так будет проще. Этот вопрос мы уточним далее в этом уроке.
Test block
правитьРекомендуется в файле модуля определять только сами функции, без инструкций, выходящих за их пределы. Это условие желательно выполнять, чтобы при импортировании модуля не происходило ненужных вычислений. Однако, Python позволяет конструкцию, которая дает возможность использовать файл программы и как модуль, содержащий описания функций и как исполняющую программу. Такая двухсторонняя магия заключается в добавлении следующего if-блока:
if __name__ == '__main__':
<block of statements>
Имена с двумя нижними подчеркиваниями являются служебными. Переменная __name__ автоматически определяется в любом модуле как имя модуля, если он импортируется в другую программу или как строка '__main__', если файл модуля запущен как программа. Это подразумевает, что в указанной выше конструкции <block of statements> выполняются тогда и только тогда, когда мы запускаем файл модуля как самостоятельную программу. И, собственно, этот <block of statements> и называют test block.
Зачастую, когда модуль создается из обычной программы, оригинальная программа используется в качестве test block. Новый файл модуля работает как старая программа, но с новой возможностью быть импортированной в другую программу. Давайте, напишем маленькую программу, которая проверит способности нашего модуля interest. Идея в том, что мы присвоим четырем параметрам приемлемые значения и проверим, что дают остальные, то есть правильно ли мы нашли и записали функции:
if __name__ == '__main__':
A = 2.2133983053266699
A0 = 2.0
p = 5
n = 730
print 'A=%g (%g)\nA0=%g (%.1f)\nn=%d (%d)\np=%g (%.1f)' % \
(present_amount(A0, p, n), A,
initial_amount(A, p, n), A0,
days(A0, A, p), n,
annual_rate(A0, A, n), p)
Этот блок мы вставляем после записанных ранее функций, а далее сохранив, видим, что наш модуль может работать и как программа:
interest.py
A=2.2134 (2.2134)
A0=2 (2.0)
n=730 (730)
p=5 (5.0)
И здесь мы видим, что значения после знака равенства и в скобках совпадают. Значит модуль и в качестве программы работает правильно. Теперь посмотрим как он исполняет свои прямые обязанности. Для этого в интерактивном режиме Python импортируем из него какую-нибудь функцию и рассчитаем с помощью нее значение:
>>> from interest import present_amount
>>> present_amount(2, 5, 730)
2.2133983053266699
Теперь у нас есть все доказательства, что файл interest.py верно работает и как программа, и как модуль.
Гибкий test block
правитьХорошей практикой программирования является возможность test block осуществлять одно или более из трех действий: 1) давать информацию о том как модуль или программа используются, 2) проверять верно ли работают функции модуля, 3) позволять взаимодействие с пользователем.
Вместо того, чтобы иметь множество инструкций в тестовом блоке, гораздо лучше собирать их в отдельные функции, которые затем в нем вызываются. Существует соглашение давать таким тестовым функциям имена, начинающиеся с подчеркивания, поскольку такие имена не импортируется в другие программы во время действия инструкции from module import * (это и естественно, ведь за пределами модуля они нам обычно не нужны). В нашем примере мы соберем все проверяющие инструкции в отдельной функции _verify:
def _verify():
A = 2.2133983053266699; A0 = 2.0; p = 5; n = 730
A_computed = present_amount(A0, p, n)
A0_computed = initial_amount(A, p, n)
n_computed = days(A0, A, p)
p_computed = annual_rate(A0, A, n)
print 'A=%g (%g)\nA0=%g (%.1f)\nn=%d (%d)\np=%g (%.1f)' % \
(A_computed, A, A0_computed, A0,
n_computed, n, p_computed, p)
Мы можем создать специальный аргумент командной строки verify, с помощью которого запускается проверка. В test block добавляем соответствующее условие:
if __name__ == '__main__':
if len(sys.argv) == 2 and sys.argv[1] == 'verify':
_verify()
Чтобы сделать программу полезной, сделаем так, что в командной строке можно вводить три параметра, а программа рассчитывает остающийся. Как добиться желаемого? После того как переменные введены в командной строке, мы можем взять этот текст как код Python (через ранее рассмотренную функцию exec). А сделать это несложно:
init_code = ''
for statement in sys.argv[1:]:
init_code += statement + '\n'
exec(init_code)
Например, для запуска с A0=2 A=1 n=1095 в command line, init_code становится строкой
A0=2
A=1
n=1095
Особо отметим, что здесь в командной строке нельзя ставить пробелы вокруг выражений, иначе это воспримется как три разных аргумента и возникнет исключение SyntaxError в exec(init_code). Для того, чтобы рассказать пользователю о потенциальной ошибке, поместим (init_code) в блок try-except:
try:
exec(init_code)
except SyntaxError, e:
print e
print init_code
sys.exit(1)
Может случиться, что пользователь присвоил значение параметру с неверным именем или вовсе его забыл. В таких случаях мы вызываем функцию с не инициализированными аргументами, что, как известно, приводит к исключению NameError, в то время как неверное значение возбудит ValueError. Так у нас выросла большая, но нужная проверяющая функция:
def _compute_missing_parameter(init_code):
try:
exec(init_code)
except SyntaxError, e:
print e
print init_code
sys.exit(1)
# find missing parameter:
try:
if 'A=' not in init_code:
print 'A =', present_amount(A0, p, n)
elif 'A0=' not in init_code:
print 'A0 =', initial_amount(A, p, n)
elif 'n=' not in init_code:
print 'n =', days(A0, A , p)
elif 'p=' not in init_code:
print 'p =', annual_rate(A0, A, n)
except NameError, e:
print e
sys.exit(1)
except ValueError:
print 'Illegal values in input:', init_code
sys.exit(1)
Не поленитесь разобраться что к чему.
Если пользователь вообще не вводит аргументов командной строки (что скорее всего означает первый запуск), выводится инструкция об использовании (usage) программы. Далее, если вводится один аргумент и это строка 'verify', то мы запускаем проверку. В оставшихся случаях мы собственно обрабатываем подаваемые значения. Далее приводится реализация сказанного. Отметьте, что начальные подчеркивания применяются и в именах переменных, локальных для модуля:
_filename = sys.argv[0]
_usage = """
Usage: %s A=10 p=5 n=730
Program computes and prints the 4th parameter'
(A, A0, p, or n)""" % _filename
if __name__ == '__main__':
if len(sys.argv) == 1:
print _usage
elif len(sys.argv) == 2 and sys.argv[1] == 'verify':
_verify()
else:
init_code = ''
for statement in sys.argv[1:]:
init_code += statement + '\n'
#вообще более элегантно:
#init_code = '; '.join(sys.argv[1:])
_compute_missing_parameter(init_code)
Также хорошо привычкой является включать строки документации в самом начале модуля. Эти doc strings обычно поясняют цель модуля и как его использовать. Ими можно пользоваться и не открывая файл модуля, их можно вывести, например, с помощью инструкции print имя_модуля.__doc__.
Итак, давайте посмотрим на то, каким получился наш модуль:
"""
Module for computing with interest rates.
Symbols: A is present amount, A0 is initial amount,
n counts days, and p is the interest rate per year.
Given three of these parameters, the fourth can be
computed as follows:
A = present_amount(A0, p, n)
A0 = initial_amount(A, p, n)
n = days(A0, A, p)
p = annual_rate(A0, A, n)
"""
import sys
_filename = sys.argv[0]
_usage = """
Usage: %s A=10 p=5 n=730
Program computes and prints the 4th parameter'
(A, A0, p, or n)""" % _filename
from math import log as ln
def present_amount(A0, p, n):
return A0*(1 + p/(360.0*100))**n
def initial_amount(A, p, n):
return A*(1 + p/(360.0*100))**(-n)
def days(A0, A, p):
return ln(A/A0)/ln(1 + p/(360.0*100))
def annual_rate(A0, A, n):
return 360*100*((A/A0)**(1.0/n) - 1)
def _verify():
A = 2.2133983053266699; A0 = 2.0; p = 5; n = 730
A_computed = present_amount(A0, p, n)
A0_computed = initial_amount(A, p, n)
n_computed = days(A0, A, p)
p_computed = annual_rate(A0, A, n)
print 'A=%g (%g)\nA0=%g (%.1f)\nn=%d (%d)\np=%g (%.1f)' % \
(A_computed, A, A0_computed, A0,
n_computed, n, p_computed, p)
def _compute_missing_parameter(init_code):
try:
exec(init_code)
except SyntaxError, e:
print e
print init_code
sys.exit(1)
try:
if 'A=' not in init_code:
print 'A =', present_amount(A0, p, n)
elif 'A0=' not in init_code:
print 'A0 =', initial_amount(A, p, n)
elif 'n=' not in init_code:
print 'n =', days(A0, A , p)
elif 'p=' not in init_code:
print 'p =', annual_rate(A0, A, n)
except NameError, e:
print e
sys.exit(1)
except ValueError:
print 'Illegal values in input:', init_code
sys.exit(1)
if __name__ == '__main__':
if len(sys.argv) == 1:
print _usage
elif len(sys.argv) == 2 and sys.argv[1] == 'verify':
_verify()
else:
init_code = ''
for statement in sys.argv[1:]:
init_code += statement + '\n'
_compute_missing_parameter(init_code)
Использование модулей
правитьДавайте посмотрим как дальше использовать модуль interest в программах. Для этого мы создадим новый файл test.py, выполняющий некоторые расчеты:
from interest import days
# сколько лет потребуется для удвоения суммы вклада
# если процент p=1,2,3,...14?
for p in range(1, 15):
years = days(1, 2, p)/365.0
print 'With p=%d%% it takes %.1f years to double the amount' \
% (p, years)
Есть разные способы импорта функции из модуля и давайте рассмотрим их в интерактивном режиме. Функция dir() создает список всех определенных во время сеанса имен, включая импортируемые имена переменных и функций. Вызов dir(m) даст нам все имена, определенные в модуле m. Для начала запустим IDLE и вызовем просто dir()
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__']
Эти переменные, как видно по подчеркиваниям, являются служебными и всегда определены. Если же импортировать:
>>> from interest import *
>>> dir()
[ ..., 'annual_rate', 'days', 'initial_amount', 'present_amount', 'ln', 'sys']
мы видим, что наши четыре функции и все доступные сопутствующие импортировались. Можно заметить, что имена, определенные нами с нижнего подчеркивания, в списке отсутствуют. Это важное следствие — ненужные имена не загрязняют «пространство имен» и уменьшается возможность ошибок из-за совпадения имен, что может случиться при импортировании через *. Теперь заглянем в сам модуль:
>>> import interest
>>> dir(interest)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', '_compute_missing_parameter', '_filename',
'_usage', '_verify', 'annual_rate', 'days', 'initial_amount', 'ln', 'present_amount', 'sys']
И здесь мы видим и скрытые раньше локальные имена с префиксом. И, вообще говоря, мы их вполне можем использовать самостоятельно вызывая:
>>> interest._verify()
A=2.2134 (2.2134)
A0=2 (2.0)
n=730 (730)
p=5 (5.0)
>>> interest._filename
Как отмечалось в начале урока, программа test.py работает так долго, как долго она находится в одной папке с модулем interest.py. Однако, если она заблудится, то мы получим ошибку:
test.py
Traceback (most recent call last):
File "tmp.py", line 1, in <module>
from interest import days
ImportError: No module named interest
Программа не может найти модуль. Python по своему обыкновению ищет модуль среди папок, указанных в списке sys.path. Посмотрим содержимое этого списка:
>>> import sys
>>> print sys.path
Кроме наиболее простого способа — размещения модуля в одной папке с программой, можно в программе и прописать путь до папки:
modulefolder = '../../pymodules'
sys.path.insert(0, modulefolder)
Python ищет имена в sys.path именно в той последовательности, в которой они расположены в списке. Поэтому и путь мы ставим нулевым элементом.