Введение
Общие сведения о Python. Достоинства и недостатки
Достоинства языка
Недостатки языка
Обзор особенностей
Тьюпл
Список
Словарь
Описание языка. Управляющие конструкции
Обработка исключительных ситуаций
Объявление функций
Объявление классов
Операторы для всех типов последовательностей (списки, тьюплы, строки)
Операторы для списков (list)
Операторы для словарей (dictionary)
Файловые объекты
Другие элементы языка и встроенные функции
Cпециальные функции для работы со списками
Импортирование модулей
Стандартный модуль math
Модуль string
Заключение
Литература
В связи с наблюдаемым в настоящее время стремительным развитием персональной
вычислительной техники, происходит постепенное изменение требований, предъявляемых
к языкам программирования. Все большую роль начинают играть интерпретируемые
языки, поскольку возрастающая мощь персональных компьютеров начинает обеспечивать
достаточную скорость выполнения интерпретируемых программ. А единственным
существенным преимуществом компилируемых языков программирования является
создаваемый ими высокоскоростной код. Когда скорость выполнения программы
не является критичной величиной, наиболее правильным выбором будет интерпретируемый
язык, как более простой и гибкий инструмент программирования.
В связи с этим, определенный интерес представляет рассмотрение сравнительно
нового языка программирования Python (пайтон), который был создан его автором Гвидо
ван Россумом (Guido van Rossum) в начале 90-х годов.
Python является интерпретируемым, изначально объектно-ориентированным языком
программирования. Он чрезвычайно прост и содержит небольшое число ключевых
слов, вместе с тем очень гибок и выразителен. Это язык более высокого уровня
нежели Pascal, C++ и, естественно C, что достигается, в основном, за счет
встроенных высокоуровневых структур данных (списки, словари, тьюплы).
Достоинства языка.
Несомненным достоинством является то, что интерпретатор Python
реализован практически на всех платформах и операционных системах. Первым
таким языком был C, однако его типы данных на разных машинах могли занимать разное
количество памяти и это служило некоторым препятствием при написании действительно
переносимой программы. Python же таким недостатком не обладает.
Следующая немаловажная черта - расширяемость языка, этому придается
большое значение и, как пишет сам автор, язык был задуман именно как расширяемый.
Это означает, что имеется возможность совершенствования языка всеми всеми заинтересованными программистами.
Интерпретатор написан на С и исходный код доступен для любых манипуляций. В случае
необходимости, можно вставить его в свою программу и использовать как встроенную
оболочку. Или же, написав на C свои дополнения к Python и скомпилировав
программу, получить "расширенный" интерпретатор с новыми возможностями.
Следующее достоинство - наличие большого числа подключаемых к программе
модулей, обеспечивающих различные дополнительные возможности. Такие модули
пишутся на С и на самом Python и могут быть разработаны всеми достаточно
квалифицированными программистами.
В качестве примера можно привести следующие модули:
-
Numerical Python - расширенные математические возможности, такие как манипуляции
с целыми векторами и матрицами;
-
Tkinter - построение приложений с использованием графического пользовательского
интерфейса (GUI) на основе широко распространенного на X-Windows Tk-интерфейса;
-
OpenGL - использование обширной библиотеки графического моделирования двух-
и трехмерных объектов Open Graphics Library фирмы Silicon Graphics Inc.
Данный стандарт поддерживается, в том числе, в таких распространенных операционных системах
как Microsoft Windows 95 OSR 2, 98 и Windows NT 4.0.
Недостатки языка.
Единственным недостатком, замеченным автором, является сравнительно
невысокая скорость выполнения Python-программы, что обусловлено ее интерпретируемостью.
Однако, на наш взгляд, это с лихвой окупается достоинствами языка при написании
программ не очень критичных к скорости выполнения.
1. Python, в отличие от многих языков (Pascal,
C++, Java, и т.д.), не требует описания переменных. Они создаются в месте
их инициализации, т.е. при первом присваивании переменной какого-либо значения.
Значит, тип переменной определяется типом присваиваемого значения. В этом
отношении Python напоминает Basic.
Тип переменной не является неизменным. Любое присваивание для нее корректно
и это приводит лишь к тому, что типом переменной становится тип нового
присваиваемого значения.
2. В таких языках как Pascal, C, C++ организация
списков представляла некоторые трудности. Для их реализации приходилось
хорошо изучать принципы работы с указателями и динамической памятью. И
даже имея хорошую квалификацию, программист, каждый раз заново реализуя
механизмы создания, работы и уничтожения списков, мог легко допустить трудноуловимые
ошибки. Ввиду этого были созданы некоторые средства для работы со списками.
Например, в Delphi Pascal имеется класс TList, реализующий списки; для
С++ разработана библиотека STL (Standard Template Library), содержащая
такие структуры как векторы, списки, множества, словари, стеки и очереди.
Однако, такие средства имеются не во всех языках и их реализациях.
Одной из отличительных черт Python является наличие таких встроенных
в сам язык структур как тьюплы (tuple), списки (list) и словари
(dictionary), которые иногда называют картами (map). Рассмотрим
их поподробней.
-
Тьюпл. Он чем-то напоминает массив: состоит из элементов
и имеет строго определенную длину. Элементами могут быть любые значения
- простые константы или объекты. В отличие от массива, элементы тьюпла не
обязательно однородны. А тем, что отличает тьюпл от списка (list) является
то, что тьюпл не может быть изменен, т.е. мы не можем i-тому элементу тьюпла
присвоить что-то новое и не можем добавлять новые элементы. Таким образом,
тьюпл можно назвать списком-константой. Синтаксически тьюпл задается
путем перечисления через запятую всех элементов, и все это заключено в
круглые скобки:
(1, 2, 5, 8)
(3.14, ‘ string ’, -4)
Все элементы индексируются с нуля. Для получения i-го элемента необходимо
указать имя тьюпла затем индекс i в квадратных скобках. Пример:
t = (0, 1, 2, 3, 4)
print t[0], t[-1], t[-3]
Результат: 0 4 2
Таким образом, тьюпл можно было назвать вектором-константой, если бы
его элементы всегда были однородными.
-
Список. Хорошим, частным примером списка может служить строка (string)
языка Turbo Pascal. Элементами строки являются одиночные символы, ее длина
не фиксирована, имеется возможность удалять элементы или, напротив, вставлять
их в любом месте строки. Элементами же списка могут быть произвольные объекты
не обязательно одного и того же типа. Чтобы создать список, достаточно перечислить
его элементы через запятую, заключив все это в квадратные скобки:
[3, 5.14, ‘s’]
[‘string’, (0,1,8), [1,1]]
В отличие от тьюпла, списки можно модифицировать по своему желанию.
Доступ к элементам осуществляется также как и в тьюплах. Пример:
l = [1, ‘s’, (2,8), [0,3,4]]
print l[0], l[1], l[-2], l[-1][0]
Результат: 1 s (2,8) 0
-
Словарь. Напоминает тип запись (record) в Pascal или структуры
(structure) в С. Однако, вместо схемы "поле записи"-"значение" здесь применяется
"ключ"-"значение". Словарь представляет собой набор пар "ключ"-"значение".
Здесь "ключ" - константа любого типа (но преимущественно применяются строки),
он служит для именования (индексирования) некоторого соответствующего ему
значения (которое можно менять).
Словарь создается путем перечисления его элементов (пар "ключ"-"значение",
разделенных двоеточием), через запятую и заключения всего этого в фигурные
скобки. Для получения доступа к некоторому значению необходимо, после имени
словаря, в квадратных скобках записать соответствующий ключ. Пример:
d = {'a': 1, 'b': 3, 5: 3.14, 'name': 'John'}
d['b'] = d[5]
print d['a'], d['b'], d[5], d['name']
Результат: 1 3.14 3.14 John
Для добавления новой пары "ключ"-"значение" достаточно присвоить элементу
с новым ключом соответствующее значение:
d['new'] = 'new value'
print d
Результат: {'a':1, 'b':3, 5:3.14, 'name':'John', 'new':'new
value'}
3. Python в отличие от Pascal, C, C++ не поддерживает
работу с указателями, динамической памятью и адресную арифметику. В этом
он похож на Java. Как известно, указатели служат источником трудноуловимых
ошибок и работа с ними относится больше к программированию на низком уровне.
Для обеспечения большей надежности и простоты они небыли включены в Python.
4. Одним из особенностей Python является
то, как происходит присваивание одной переменной другой, т.е. когда по
обе стороны от оператора "=" стоят переменные.
Следуя Тимоти Бадду ([1]), будем называть семантикой указателей случай,
когда присваивание приводит лишь к присваиванию ссылки (указателя), т.е.
новая переменная становится лишь другим именем, обозначающим тот же участок
памяти, что и старая переменная. При этом изменение значения, обозначаемого
новой переменной, приведет к изменению значения старой, т.к. они, фактически,
означают одно и то же.
Когда же присваивание приводит к созданию нового объекта (здесь объект
- в смысле участка памяти для хранения значения какого-либо типа) и копированию
в него содержимого присваиваемой переменной, этот случай назовем семантикой
копирования. Таким образом, если при копировании действует семантика копирования,
то переменные по обе стороны от знака "=" будут означать два независимых
объекта с одинаковым содержанием. И здесь последующее изменение одной переменной
никак не скажется на другой.
Присваивание в Python происходит следующим образом: если присваеваемый
объект является экземпляром таких типов как числа или строки, то действует
семантика копирования, если же в правой части стоит экземпляр класса,
список, словарь или тьюпл, то действует семантика указателей. Пример:
a = 2; b = a; b = 3
print ' семантика копирования: a=', a, 'b=', b
a = [2,5]; b = a; b[0] = 3
print ' семантика указателей: a=', a, 'b=', b
Результат:
семантика копирования: a= 2 b= 3
семантика указателей: a= [3,5] b= [3,5]
Для тех из вас, кто хочет знать в чем тут дело, я приведу другой взгляд
на присваивание в Python. Если в таких языках как Basic, Pascal, C/C++ мы
имели дело с переменными-"емкостями", и хранимыми в них константами
(числовыми, символьными, строковыми - не суть важно), а операция присваивания
означала "занесение" константы в присваиваемую переменную, то в Python мы
уже должны работать с переменными-"именами" и именуемыми ими объектами.
(Замечаете некоторую аналогию с языком Prolog?) Что же такое объект в Python?
Это все то, чему можно дать имя: числа, строки, списки, словари, экземпляры
классов (которые в Object Pascal и называются объектами), сами классы (!),
функции, модули и т.д. Так вот, при присваивании переменной некоторого
объекта, переменная становится его "именем", причем таких "имен" объект
может иметь сколько угодно и все они никак не зависят друг от друга.
Теперь, объекты делятся на модифицируемые (мутируемые) и неизменные.
Мутируемые - те, которые могут изменить свое "внутреннее содержание",
например, списки, словари, экземпляры классов. А неизменные - такие как
числа, тьюплы, строки (да, строки тоже; можно переменной присвоить новую
строку, полученную из старой, но саму старую строку модифицировать не
получится).
Так вот, если мы пишем
a = [2,5]; b = a; b[0] = 3,
Python это интерпретирует так:
дать объекту "список [2,5]" имя a ;
дать этому объекту еще одно имя - b ;
модифицировать нулевой элемент объекта.
Вот и получилась "псевдо" семантика указателей.
И последнее, что стоит сказать насчет этого: хотя нет возможности
изменения структуры тьюпла, но содержащиеся в нем мутируемые компоненты
по-прежнему доступны для модификации:
t = (1, 2, [7,5], 'string')
t[0] = 6 # так нельзя
del t[1] # тоже ошибка
t[2][1] = 0 # допустимо, теперь третья компонента - список [7,0]
t[3][0] = 'S' # ошибка: строки не мутируемы
5. Весьма оригинальным является то, как
в Python группируются операторы. В Pascal для этого служат операторные
скобки begin-end, в C, C++, Java - фигурные скобки {}, в Basic применяются
закрывающие окончания конструкций языка (NEXT, WEND, END IF, END SUB).
В языке Python все гораздо проще: выделение блока операторов осуществляется
путем сдвига выделяемой группы на один или более пробелов или символов
табуляции вправо относительно заголовка конструкции к которой и будет относиться
данный блок. Например:
if x > 0:
print ‘ x > 0 ’
x = x - 8
else:
print ‘ x <= 0 ’
x = 0
Тем самым, хороший стиль записи программ, к которому призывают преподаватели
языков Pascal, C++, Java и т.д., здесь приобретается с самого начала, поскольку,
по-другому просто не получится.
if <условие1>: <оператор1>
[ elif <условие2>: <оператор2>]*
[ else: <оператор3> ]
|
Оператор "если". Часть в квадратных скобках является необязательной.
Следующий за скобками символ "*" означает, что заключенная в скобки часть
может быть записана неоднократно одна за другой.
Здесь, при истинности <условия1> будет выполнен <оператор1> и проигнорированы
ветки elif и else. В противном случае, если истинно <условие2>, то выполняется
<оператор2>, ветка else игнорируется. Иначе выполняется <оператор3>. |
while <условие>:
<оператор1>
[else: <оператор2>]
|
Цикл "пока". <Оператор1> будет выполняться все время, пока истинно
<условие>. При нормальном завершении цикла, т.е. без применения
break, выполнится <оператор2>. |
for <переменная> in <список>:
<оператор1>
[else: <оператор2>]
|
Цикл "для". <Переменная> пробегает все элементы <списка> и
для каждого текущего значения <переменной> выполняется <оператор1>.
При нормальном завершении цикла, т.е. без применения break,
выполнится <оператор2>. |
break |
Осуществляет немедленное завершение циклов while и for. |
continue |
Вызывает немедленное выполнение следующей итерации циклов while и for. |
return [<результат>] |
Осуществляет возврат из функции или метода класса, возвращая значение
<результат>. |
try :
<оператор1>
[except [<исключение> [,<переменная>] ]:
<оператор2>]
[else <оператор3>] |
Выполняется <оператор1>, если при этом возникла исключительная ситуация
<исключение>, то выполняется <оператор2>. Если <исключение> имеет
значение, то оно присваивается <переменной>.
В случае успешного завершения <оператора1>, выполняется <оператор3>. |
try :
<оператор1>
finally :
<оператор2>
|
Выполняется <оператор1>. Если не возникло исключений, то выполняется
<оператор2>. Иначе выполняется <оператор2> и немедленно инициируется
исключительная ситуация. |
raise <исключение> [<значение>] |
Инициирует исключительную ситуацию <исключение> с параметром <значение>.
|
Исключения - это просто строки (string). Пример:
my_ex = ‘bad index’
try:
if bad:
raise my_ex, bad
except my_ex, value:
print ‘ Error ’, value
def <имя_функции> ( [
<список_параметров>] ):
<тело_функции>
|
Здесь <тело_функции> - последовательность операторов, выровненных
по тексту правее слова "def".
<список_параметров> в самом общем виде выглядит так:
[ < id > [,< id >]* ] [ < id >=< v > [,< id >=< v
>]* ] [, *< id >]
Здесь < id > - идентификатор переменной; < v > - некое значение.
Параметры < id > за которыми следует "=" получают значения <
v > по умолчанию.
Если список заканчивается строкой " *< id > ", то id присваивается
тьюпл (tuple) из всех оставшихся аргументов, переданных функции. |
class <имя_класса> [( <предок1>
[,<предок2>]* )]:
<тело_класса>
|
Здесь <тело_класса> может содержать присваивания переменным (эти
переменные становятся атрибутами, т.е. полями класса) и определения функций
(являющихся методами класса).
Первым аргументом метода всегда является экземпляр класса который вызывает
данный метод (или, говоря иначе, к которому применяется метод). По соглашению,
этот аргумент называется "self". Специальный метод __init__() вызывается
автоматически при создании экземпляра класса. |
Пример:
class cMyClass:
def __init__(self, val):
self.value = val
#
def printVal (self):
print ‘ value = ’, self.value
#
# end cMyClass
obj = cMyClass (3.14)
obj.printVal ()
obj.value = " string now "
obj.printVal ()
Результат:
value = 3.14
value = string now
len (s) |
возвращает длину s. |
min (s), max (s) |
наименьший и наибольший элементы s соответственно. |
x in s |
истина (1), если s включает в себя элемент равный x, иначе -
ложь (0). |
x not in s |
ложь, если s включает x, иначе истина. |
s + t |
слияние s и t. |
s * n , n * s |
n копий s, слитых вместе (например, ‘*’ * 5 - это строка ‘*****’). |
s[i] |
i-тый элемент s, где i отсчитывается с 0. |
s[i:j] |
часть элементов s, начиная с i до j-1 включительно. Либо i, либо j,
либо оба параметра могут быть опущены (i по умолчанию равен 0, j - длине
s). |
s[i] = x |
i-тый элемент s заменяется на x. |
s[i:j] = t |
часть элементов s от i до j-1 заменяется на t (t может быть также списком). |
del s[i:j] |
удаляет часть s (также как и s[i:j] = []). |
s.append (x) |
добавляет элемент x к концу s. |
s.count (x) |
возвращает количество элементов s равных x. |
s.index (x) |
возвращает наименьший i, такой, что s[i]==x. |
s.insert (i,j) |
часть s, начиная с i-го элемента, сдвигается вправо, и s[i] присваивается
x. |
s.remove (x) |
то же, что и del s[ s.index(x) ] - удаляет первый элемент s, равный x. |
s.reverse () |
записывает строку в обратном порядке |
s.sort () |
сортирует список по возрастанию. |
len (a) |
количество элементов а. |
a[k] |
элемент с ключом k. |
a[k] = x |
присвоить элементу с ключом k значение x. |
del a[k] |
удалить a[k] из словаря. |
a.items () |
список тьюплов пар (ключ, значение). |
a.keys () |
список ключей а. |
a.values () |
список значений а. |
a.has_key (k) |
возвращает 1, если а имеет ключ k, иначе 0. |
Создаются встроенной функцией open()
(ее описание смотрите ниже).
Например: f = open (‘mydan.dat’,‘r’).
Методы:
f.close () |
закрыть файл. |
f.read ( [size] ) |
читает < size > байт из файла и возвращает в виде строки. Если
< size > отсутствует, то читает до конца файла. |
f.readline () |
читает целиком одну строку из файла. |
f.readlines () |
читает строки до конца файла и возвращает список прочитанных строк. |
f.seek (offset, mode)
|
устанавливает позицию в файле с которого будет произведено чтение.
< offset > - смещение относительно:
a) начала файла (при mode == 0 - по умолчанию);
b) текущей позиции (при mode == 1 );
c) конца файла (при mode == 2). |
f.tell () |
возвращает текущую позицию в файле. |
f.write (str) |
записывает строку < str > в файл. |
= |
присваивание. |
print [ < c1 > [,< c2 >]* [,] ] |
выводит значения < c1 >, < c2 > в стандартный вывод. Ставит
пробел между аргументами. Если запятая в конце перечня аргументов отсутствует,
то осуществляет переход на новую строку. |
abs (x) |
возвращает абсолютное значение x. |
apply (f, <аргументы>) |
вызывает функцию (или метод) f с < аргументами >. |
chr (i) |
возвращает односимвольную строку с ASCII кодом i. |
cmp (x, y) |
возвращает отрицательное, ноль, или положительное значение, если,
соответственно, x <, ==, или > чем y. |
divmod (a, b) |
возвращает тьюпл (a/b, a%b), где a/b - это a div b
(целая часть результата деления), a%b - это a mod b (остаток от деления).
|
eval (s)
|
возвращает объект, заданный в s как строка (string). S может содержать
любую структуру языка. S также может быть кодовым объектом, например:
x = 1 ; incr_x = eval ("x+1") . |
float (x) |
возвращает вещественное значение равное числу x. |
hex (x) |
возвращает строку, содержащую шестнадцатеричное представление числа x. |
input (<строка>) |
выводит <строку>, считывает и возвращает значение со стандартного
ввода. |
int (x) |
возвращает целое значение числа x. |
len (s) |
возвращает длину (количество элементов) объекта. |
long (x) |
возвращает значение типа длинного целого числа x. |
max (s), min (s) |
возвращают наибольший и наименьший из элементов последовательности
s (т.е. s - строка, список или тьюпл). |
oct (x) |
возвращает строку, содержащую представление числа x. |
open (<имя файла>, <режим>=‘r’) |
возвращает файловый объект, открытый для чтения. <режим> = ‘w’ -
открытие для записи. |
ord (c) |
возвращает ASCII код символа (строки длины 1) c. |
pow (x, y) |
возвращает значение x в степени y. |
range (<начало>, <конец>, <шаг>) |
возвращает список целых чисел, больших либо равных <начало> и меньших чем
<конец>, сгенерированных с заданным <шагом>. |
raw_input ( [ <текст> ] ) |
выводит <текст> на стандартный вывод и считывает строку (string)
со стандартного ввода. |
round (x, n=0) |
возвращает вещественное x, округленное до n-го разряда после запятой. |
str (<объект>) |
возвращает строковое представление <объекта>. |
type (<объект>) |
возвращает тип объекта.
Например: if type(x) == type(‘’): print ‘ это строка ’ |
xrange (<начало>, <конец>, <шаг>) |
аналогичен range, но лишь имитирует список, не создавая его. Используется
в цикле for. |
filter (<функция>, <список>) |
возвращает список из тех элементов <спиcка>, для которых <функция>
принимает значение "истина". |
map (<функция>, <список>) |
применяет <функцию> к каждому элементу <списка> и возвращает
список результатов. |
reduce (f, <список>,
[, <начальное значение> ] )
|
возвращает значение полученное "редуцированием" <списка> функцией
f. Это значит, что имеется некая внутренняя переменная p, которая инициализируется
<начальным значением>, затем, для каждого элемента <списка>, вызывается
функция f с двумя параметрами: p и элементом <списка>. Возвращаемый
f результат присваивается p. После перебора всего <списка> reduce возвращает
p.
С помощью данной функции можно, к примеру, вычислить сумму элементов
списка:
def func (red, el):
return red+el
sum = reduce (func, [1,2,3,4,5], 0)
# теперь sum == 15
|
lambda [<список параметров>] : <выражение>
|
"анонимная" функция, не имеющая своего имени и записываемая в месте
своего вызова. Принимает параметры, заданные в <списке параметров>,
и возвращает значение <выражения>. Используется для filter, reduce,
map. Например:
>>>print filter (lambda x: x>3, [1,2,3,4,5])
[4, 5]
>>>print map (lambda x: x*2, [1,2,3,4])
[2, 4, 6, 8]
>>>p=reduce (lambda r, x: r*x, [1,2,3,4], 1)
>>>print p
24
|
import <модуль1> [, <модуль2> ]* |
подключает внешние модули. |
from <модуль> import <имя1> [, <имя2> ]* |
импортирует имена (функций, классов, переменных и т.д.) из <модуля>. |
from <модуль> import * |
импортирует все имена из <модуля>, за исключением начинающихся символом
"_". |
Переменные: pi, e.
Функции (аналогичны функциям языка C):
acos(x) |
cosh(x) |
ldexp(x,y) |
sqrt(x) |
asin(x) |
exp(x) |
log(x) |
tan(x) |
atan(x) |
fabs(x) |
sinh(x) |
frexp(x) |
atan2(x,y) |
floor(x) |
pow(x,y) |
modf(x) |
ceil(x) |
fmod(x,y) |
sin(x) |
|
cos(x) |
log10(x) |
tanh(x) |
|
Функции:
index (s, sub, i=0) |
возвращает индекс первого вхождения подстроки sub в строку s, начиная
с позиции i. |
lower (s) |
возвращает строку s в нижнем регистре букв. |
splitfields (s, sep) |
возвращает список подстрок строк s разделенных символом sep. |
joinfields (<слова>, <разделитель>) |
сцепляет список или тьюпл <слова> используя <разделитель>. |
strip (s) |
возвращает строку, полученную из s путем исключения пробелов. |
upper (s) |
возвращает строку s в верхнем регистре букв. |
Благодаря простоте и гибкости языка Python, его можно рекомендовать
пользователям (математикам, физикам, экономистам и т.д.) не являющимся
программистами, но использующими вычислительную технику и программирование
в своей работе.
Программы на Python разрабатываются в среднем в полтора-два (а
порой и в два-три) раза быстрее нежели на компилируемых языках (С, С++,
Pascal). Поэтому, язык может представлять не малый интерес и для профессиональных
программистов, разрабатывающих приложения, не критичные к скорости выполнения,
а также программы, использующие сложные структуры данных. В частности,
Python хорошо зарекомендовал себя при разработке программ работы с графами,
генерации деревьев.
-
Бадд Т. Объектно-ориентированное программирование. - СПб.: Питер, 1997.
-
Guido van Rossum. Python Tutorial. (www.python.org)
-
Chris Hoffman. A Python Quick Reference. (www.python.org)
-
Guido van Rossum. Python Library Reference. (www.python.org)
-
Guido van Rossum. Python Reference Manual. (www.python.org)
-
Гвидо ван Россум. Семинар по программированию на Python. (http://sultan.da.ru)
|