Консалтинг и автоматизация в области управления
эффективностью банковского бизнеса

Журнал ВРМ World

А теперь поговорим о другом... Краткий курс языка Python

Это минимальный вводный курс по программированию на языке Python. Для более глубокого изучения взгляните на документацию на сайте www.python.org; особенно рекомендую Руководство (Tutorial). А если вам любопытно, почему вы должны интересоваться языком Python, взгляните на страничку Сравнение, где Python сравнивается с другими языками.

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

(Главная страничка по языку Python)

Примечание: Чтобы примеры нормально работали, пишите коды программ в текстовом файле, а потом запускайте с помощью интерпретатора; не пытайтесь запускать их прямо в интерактивном интерпретаторе - некоторые из них не будут работать. (Пожалуйста, не спрашивайте меня, почему так. Лучше почитайте документацию). [1]

1. Основы

Для начала представляйте себе Python в виде псевдокода. Это почти так и есть. Переменные не имеют типов, поэтому их не надо объявлять. [2] Они появляются, когда вы присваиваете им значения, и исчезают, когда вы их больше не используете. Присваивание производится оператором =. Проверка на равенство делается оператором ==. Вы можете присваивать значения нескольким переменным сразу: [3]

x,y,z = 1,2,3
first, second = second, first
a = b = 123

Блоки определяются посредством смещения и только таким образом (никаких BEGIN/END или скобок). Наиболее распространенными управляющими структурами являются следующие:

if x < 5 or (x > 10 and x < 20):
     print "Значение подходит."

if x < 5 or 10 < x < 20:
     print "Значение подходит."

for i in [1,2,3,4,5]:
     print "Это счетчик итераций", i

x = 10
while x >= 0:
     print "x все еще не отрицательно."
     x = x-1

Первые два примера эквивалентны.[4]

Индексная переменная, стоящая в цикле for, проходит по элементам списка (он пишется так же, как в примере). Для создания "обычного" цикла for (т.е. цикла со счетчиком), используйте встроенную функцию range().[5]

# Выведем значения от 0 до 99 включительно.
for value in range(100):
     print value

(Строка, начинающаяся с "#", представляет собой комментарий и игнорируется интерпретатором).

Хорошо; теперь вы знаете достаточно для того, чтобы (теоретически) реализовать любой алгоритм на языке Python. Давайте добавим несколько простейших понятий, касающихся взаимодействия с пользователем. Для получения ввода от пользователя (из текстового приглашения), используйте встроенную функцию input.

x = input("Пожалуйста, введите число: ")
print "Квадрат этого числа равен", x*x

Функция input отображает указанное в ней приглашение (оно может быть пустым) и дает пользователю возможность ввести любое корректное для языка Python значение. В данном случае мы предполагали ввод числового значения - если бы было введено что-либо другое (например, строка), программа рухнула бы. Чтобы избежать этого, нам нужна какая-нибудь проверка ошибок. Я не буду углубляться в это сейчас; достаточно сказать, что если вы хотите, чтобы значение, введенное пользователем, хранилось буквально в виде строки (чтобы можно было ввести любое значение), используйте функцию raw_input. Если вы затем захотите преобразовать введенную строку s в целое число, вы сможете использовать выражение int(s).

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

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

name = ["Клиз", "Джон"]
x = [[1,2,3],[y,z],[[[]]]]

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

print name[1], name[0] # Печать словосочетания "Джон Клиз"
name[0] = "Смит"

Срез напоминает индексирование, за исключением того, что вы указываете и стартовый и конечный индексы результата через двоеточие(":"):

x = ["фарш","фарш","фарш","фарш","фарш","яйца","и","фарш"]
print x[5:7] # Печать списка ["яйца","и"]

Заметьте, что последний элемент (в данном случае с индексом 7) не включается в срез. Если один из индексов опущен, подразумевается, что вы хотите получить все элементы в этом направлении. То есть list[:3] означает "все элементы с начала списка (list) до элемента 3, не включая этот элемент ".[6] (Можно возразить, что на самом деле это элемент 4, поскольку подсчет начинается с 0... Ну, пусть так.) С другой стороны, list[3:] будет означать "любой элемент из списка (list), начиная с элемента 3 (включительно) и до последнего (включая его!)". Интересно то, что вы можете использовать отрицательные числа в качестве индексов: list[-3] представляет собой третий элемент с конца списка.

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

Ну, а что же насчет словарей? Говоря просто, они похожи на списки, только их содержимое неупорядоченно. Как же тогда их индексировать? А очень просто - каждый элемент имеет ключ, или "имя", которое используется для поиска элемента так же, как в настоящем словаре. Вот пара примеров словарей:

{ "Алиса" : 23452532, "Борис" : 252336, "Кларисса" : 2352525, "Дорис" : 23624643}
person = { 'фамилия': "Робин", 'имя': "Гуд", 'профессия': "Разбойник" }

Теперь, чтобы получить профессию некоторого лица (person), мы можем использовать выражение person["профессия"]. Если мы хотим изменить его фамилию, мы можем написать:

person['фамилия'] = "Локсли"

Просто, не правда ли? Как и списки, словари могут содержать другие словари (или списки). И, естественно, списки также могут содержать словари. Таким образом, вы можете с легкостью создавать весьма сложные структуры данных.

2. Функции

Следующий шаг: Абстракция. Мы хотим присвоить некоторое имя фрагменту кода и вызвать его с парой параметров. Другими словами, мы хотим определить функцию (или "процедуру"). Это просто. Используйте ключевое слово def, как в следующем примере:

def square(x):
return x*x
print square(2) # Вывод числа 4

Для тех из вас, кто разбирается в этом: Все параметры в языке Python передаются по ссылке (как, например, в языке Java). Для тех, кто не разбирается: Не волнуйтесь :)

Python содержит все положенные примочки вроде именованных аргументов и аргументов по умолчанию и поддерживает функции с переменным числом аргументов. Чтобы разобраться с этим подробнее, загляните в Руководство по языку Python раздел 4.7.

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

Кроме того полезно знать, что в языке Python функции являются значениями. То есть если у вас есть функция типа square, вы можете сделать что-нибудь вроде:

queeble = square
print queeble(2) # Вывод числа 4

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

3. Объекты и прочее...

Думаю, вы знакомы с принципами объектно-ориентированного программирования. (Если нет, этот раздел, возможно, будет вам несколько непонятен. Ничего страшного... Вы можете начать писать на языке Python и без использования объектов :)). В этом языке вы определяете классы с помощью (сюрприз!) ключевого слова class, как это сделано в следующем примере:

class Basket:
    # Не забудьте аргумент *self*
    def __init__(self,contents=None):
        self.contents = contents or []

    def add(self,element):
        self.contents.append(element)

    def print_me(self):
        result = ""
        for element in self.contents:
            result = result + " " + `element`
        print "Содержит:"+result

Новым здесь является то, что:

  1. Все методы (функции в объекте) получают дополнительный аргумент в начале списка аргументов. Этот аргумент содержит сам объект. (В этом примере он назван self, как это обычно принято).
  2. Методы вызываются как: object.method(arg1,arg2).[7]
  3. Некоторые имена методов, наподобие __init__, предопределены и означают что-то специальное. __init__ - это имя конструктора класса, то есть функции, вызываемой, когда вы создаете объект этого класса.[8]
  4. Некоторые аргументы могут быть необязательными и им могут присваиваться значения по умолчанию (как упоминалось выше, в разделе, посвященном функциям). Это можно сделать, написав определение, как в следующем примере:

    def spam(age=32): ...

    Здесь spam может быть вызван с одним параметром или без параметров. При вызове без параметров, параметр age будет иметь значение 32.
  5. "Логика короткого замыкания" (short circuit logic). Пока немного слишком сложно... См. ниже.
  6. Обратные кавычки преобразуют объект в его строковое представление. (Так, если element содержит число 1, тогда 'element' - это то же самое, что и "1", в то время как 'element' является литеральной строкой).
  7. Символ сложения + используется также для конкатенации списков, и строки действительно представляют собой всего лишь списки символов (что означает, что вы можете использовать применительно к ним и индексацию, и срезы, и функцию len . Недурно, а?)

В языке Python не существует защищенных (protected) (или приватных (private), etc.) методов и членов класса. Выделение таких элементов - это в большей степени вопрос стиля программирования. (Если вам действительно это необходимо, существуют соглашения об именовании членов класса, которые допускают некоторую приватность :)).

Ну, а теперь о "логике короткого замыкания"...

Все значения в языке Python могут использоваться как логические. Некоторые из наиболее "пустых", вроде [], 0, "" и None представляют собой логическую "ложь", тогда как большинство остальных значений ([0], 1 или "Привет, мир") представляют собой логическую "истину".[9]

Логические выражения (такие, как a and b) вычисляются следующим образом: в первую очередь, проверяется на истинность значение a. Если нет, то она же и возвращается. Если да, то возвращается b (представляющая собой истинное значение выражения). Соответствующая логика для a or b такова: Если значение а - "истина", то вернуть a, а если нет, то вернуть b.[10]

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

if a:
     print a
else:
     print b

Может быть записано:

print a or b

В действительности, это что-то вроде идиомы языка Python, поэтому можете постепенно привыкать так и писать. Это то же, что мы делали в методе Basket.__init__. Аргумент contents имеет значение по умолчанию None (которое, между прочим, ложно). Следовательно, чтобы проверить, имеет ли он некоторое значение, мы могли бы написать:

if contents:
     self.contents = contents
else:
     self.contents = []

Безусловно, теперь вы знаете, лучший способ. А почему бы нам тогда не присвоить ему сразу значение по умолчанию []? А потому, что согласно принципам работы языка Python, в результате этого всем "Baskets" присвоился бы один и тот же пустой список значений, который задан в значении по умолчанию. Как только один из них начал бы заполняться, все остальные получали бы те же самые элементы, и значение по умолчанию больше не было бы пустым... Чтобы лучше разобраться в этом, прочитайте документацию и обратите внимание на различие между идентичностью и равенством.

Все вышеизложенное можно сделать и другим способом:

def __init__(self, contents=[]):
self.contents = contents[:]

Можете догадаться, как это работает? Вместо использования везде одного и того же пустого списка, мы задействовали выражение contents[:], чтобы сделать копию. (Просто срезали все целиком).

Так, чтобы действительно создать объект Basket и использовать его (то есть вызвать какие-либо его методы), мы можем сделать что-то вроде:

b = Basket(['яблоко','апельсин'])
b.add("лимон")
b.print_me()

Есть и другие волшебные методы, помимо __init__. Один из таких методов - __str__, определяющий, как объект хотел бы выглядеть в строчном представлении. Мы можем использовать на нашем примере вместо print_me:

def __str__(self):
     result = ""
     for element in self.contents:
           result = result + " " + `element`
     return "Contains:"+result

Теперь, если мы захотим напечатать Basket b, мы сможем просто указать:

print b

Неплохо, а?

Производные классы создаются так:

class SpamBasket(Basket):
      # ...

Python допускает множественное наследование, поэтому в скобках может быть указано несколько базовых классов, разделяемых запятыми. Классы воплощаются (т.е. объекты конструируются) следующим образом: x = Basket(). Конструкторы задаются, как уже упоминалось, путем определения специального метода __init__. Давайте предположим, что SpamBasket имеет коструктор __init__(self,type). Тогда вы можете создать корзину с фаршем следующим образом: y = SpamBasket("apples").

Если вам в конструкторе SpamBasket нужно вызвать конструктор одного или нескольких базовых классов, вы можете сделать это так: Basket.__init__(self). Заметьте, что в дополнение к обычным параметрам вам следует явно задать self, поскольку метод __init__ базового класса не знает, с каким объектом он имеет дело.

Чтобы лучше разобраться в замечательных возможностях объектно-ориентированного программирования в языке Python, загляните в Руководство, раздел 9.

4. Приемы Джедаев

(Этот раздел вставлен сюда только потому, что он мне нравится. Для того, чтобы начать знакомство с языком Python, он абсолютно не нужен).

Вам нравятся головокружительные концепции? Тогда, если вы так бесстрашны, вам, возможно, будет интересно ознакомиться с эссе Гвидо ван Россума (Guido van Rossum) по метаклассам. Если же вам не слишком хочется подвергать свой разум таким испытаниям, вероятно, вам вполне хватит и этого небольшого приемчика.

Python использует динамические пространства имен (dynamic namespaces) как противоположность лексическим пространствам имен (static namespaces). Это означает, что если у вас есть функция, наподобие следующей:

def orange_juice():
     return x*2

... где переменная (в данном случае x) не привязана к какому-либо аргументу и внутри функции ей не присваивается значение, Python будет использовать то значение, которое она имеет там и тогда, где и когда вызывается эта функция. В данном случае:

x = 3
y = orange_juice()
# y сейчас равен 6
x = 1
y = orange_juice()
# y сейчас равен 2

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

x = 4
def apple_juice(x=x):
     return x*2

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

x = 3
y = apple_juice()
# y сейчас равно 8
x = 1
y = apple_juice()
# y сейчас равно 8

То есть - значение x не изменилось. Если это все, чего мы хотели, то могли бы с тем же успехом написать:

def tomato_juice():
     x = 4
     return x*2

или даже:

def carrot_juice():
     return 8

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

Мы хотим создать функцию, которая будет работать как:

from math import sin, cos
sincos = compose(sin,cos)
x = sincos(3)

где compose представляет собой функцию, которую мы хотим создать, а x имеет значение -0.836021861538, что то же самое, что и sin(cos(3)). Ну, и как же мы это сделаем?

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

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

def compose(fun1, fun2):
     def inner(x):
          pass # ...
     return inner

Нам может захотеться поместить return fun1(fun2(x)) внутрь функции inner и так и оставить там. Нет, нет и нет. Такая конструкция поведет себя весьма странно. Представьте себе следующий сценарий:

from math import sin, cos
def fun1(x):
     return x + " мир!"

def fun2(x):
     return "Привет,"

sincos = compose(sin,cos) # Использование неверной версии
x = sincos(3)

Ну а теперь, каково значение x? Правильно: "Привет, мир". А почему так? Потому что когда ее вызывают, она берет значения fun1 и fun2 из окружения, а не те, что имеются рядом в момент ее создания. Все что мы должны сделать, чтобы получить нормально работающее решение, - это использовать тот прием, который описывался ранее:

def compose(fun1, fun2):
     def inner(x, fun1=fun1, fun2=fun2):
           return fun1(fun2(x))
     return inner

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

def compose(f1, f2):
     return lambda x, f1=f1, f2=f2: f1(f2(x))

Коротко, но ясно. Вам уже начинает нравится :)

(А если вы ничего из этого не поняли, не волнуйтесь. В конце концов, я думаю, что все это хотя бы убедило вас в том, что Python нечто большее, чем "просто-напросто всего лишь скриптовый язык"... :))

5. И наконец...

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

import string
x = string.split(y)

либо...

from string import split
x = split(y)

За дополнительной информацией по стандартным библиотечным модулям обращайтесь к сайту www.python.org/doc/lib. Там лежит множество весьма полезных вещей.

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

if __name__ == "__main__": go()

Это волшебный способ указать на то, что если этот модуль запускается как исполняемый скрипт (то есть не импортируется в другой скрипт), тогда должна вызываться функция go. Безусловно, вы можете написать что угодно после поставленного там двоеточия... :)

Вниманию тех, кто хочет создать исполняемый скрипт под UN*X - воспользуйтесь следующей первой строкой, чтобы сделать его самозапускающимся:

#!/usr/bin/env python

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

В случае, если возбуждено исключение и ничего не сделано для его перехвата, ваша программа завершается и выдает сообщение об ошибке. Вы можете избежать этого с помощью выражения try/except. Например:

def safe_division(a,b):
     try:
           return a/b
     except ZeroDivisionError:
           return None

ZeroDivisionError является стандартным исключением. В данном случае вы могли проверить, не имеет ли b нулевое значение, но во многих случаях эта стратегия не годится. А, кроме того, если бы у нас не было предложения try в safe_division, из-за чего довольно рискованно вызывать эту функцию, мы по-прежнему могли бы сделать что-то наподобие следующего:

try:
     unsafe_division(a,b)
except ZeroDivisionError:
     print "Something was divided by zero in unsafe_division"

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

Ну, в общем-то, все. Надеюсь, вы чему-нибудь научились. А теперь - идите и пробуйте. И помните девиз обучения языку Python: "Use the source, Luke!" (используй исходники, Люк!). (Перевод: Читайте все коды, до которых только можете добраться :)) Для начала, вот здесь лежит пример. Это общеизвестный алгоритм Быстрой сортировки (QuickSort) Хоара (Hoare's). Версия с выделенным цветным синтаксисом лежит здесь.

Об этом примере стоит сказать пару слов. Переменная done отслеживает, закончила ли partition движение по элементам. Так, когда один из двух внутренних циклов хочет завершить всю последовательность обмена, он присваивает done 1 и затем завершается через break. Почему внутренние циклы используют done? Потому что, когда первый вложенный цикл заканчивается через break, начнется или нет следующий цикл, зависит от того, закончился ли главный цикл, то есть, имеет ли done значение, равное 1:

while not done:
     while not done:
            # Производит итерации до остановки (break)

     while not done:
            # Выполняется, только если первый не установил "done" равный 1

Эквивалентной и, возможно, более понятной, но менее симпатичной версией будет следующая:

while not done:
     while 1:
            # Производит итерации до остановки (break)

     if not done:
           while 1:
                  # Выполняется, только если первый не установил "done" равный 1

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

Еще ряд примеров вы можете найти на страничке штучек Джоуи Страута (Joe Strout) .


[1] На самом деле, простые выражения, занимающие одну строку (наподобие print b + c) гораздо удобнее вводить в интерактивном интерпретаторе, и работать они будут всегда. Проблемы могут возникать с многострочными выражениями, поскольку управляющие блоки в Python задаются смещением текста; тем не менее, многие составные операторы также могут быть введены в одну строчку; более того, несколько операторов могут быть введены на одной строке, например:>

if x < 5: print x ; print "два оператора на одной строке"
(Прим. Перев.)

[2] Переменная получает тип при присваивании, например:

v = 10 # теперь v имеет тип Integer
v = "Hello, world!" # а теперь тип v поменялся на String

(Прим. Перев.)

[3] Список переменных пробегается слева направо и каждой переменной присваивается соответствующее значение из списка значений справа от оператора присваивания.
(Прим. Перев.)

[4] Обратите внимание на запись операторов сравнения в следующем примере:

if x < 5 or 10 < x < 20:
     print "Значение подходит."

В языке Python, в отличие от большинства других языков программирования, можно записывать сравнение так же, как в обычной математической записи (10 < x < 20).
(Прим. Перев.)

[5] Для больших диапазонов предпочтительно использовать функцию xrange().
(Прим. Перев.)

[6] Т.е., список, состоящий из элементов 0, 1, 2 исходного списка.
(Прим. Перев.)

[7] Для программистов на C++ - обратите внимание, что обращение к членам класса внутри функций-членов также всегда производится с явным указанием объекта, для которого вызывается функция-член. Это сделано не для того, чтобы затруднить вам жизнь, а потому, что в связи с динамическим разрешением пространства имен в языке Python логика "по умолчанию" может приводить к весьма неожиданным (и неприятным) последствиям. (C++ использует статические пространства имен).
(Прим. Перев.)

[8] Вообще, любой метод, имя которого начинается и заканчивается __ (двумя подчеркиваниями) - специальный.
(Прим. Перев.)

[9] В Python'е есть специальное "пустое" значение None, наподобие NULL в C.
(Прим. Перев.)

[10] Логика короткого замыкания замечательна еще и тем, что вычисляются только те подвыражения логического выражения, которые необходимы для того, чтобы получить результат. Это может играть существенную роль, если подвыражения обладают побочным эффектом, например:

def fn(x):
     print x, "<", "3 ==", x < 3
     return x < 3

fn(2) or fn(4) # напечатает 2 < 3 == 1
print # просто пустая строка
fn(2) and fn(4) # напечатает 2 < 3 == 1 затем 4 < 3 == 0
print # просто пустая строка
fn(4) and fn(2) # напечатает 4 < 3 == 0

(Прим. Перев.)