Kodomo

Пользователь

Собственно, Python

Оффтопик

Прежде, чем переходить к питону (вот-вот, я сейчас о нём начну рассказывать), нужно ещё закрыть несколько хвостов.

Во-первых. По второму заданию я получил результаты от одиннадцати человек, а по первому только от шести. При этом я знаю, что делали первое задание вы почти все, и сделали довольно много. Почему так? Я сам знаю и сам отвечу: потому, что в первом задании отправка результатов – это был такой отдельный шаг, который вам самим нужно было сделать, а во втором задании оно получалось в качестве побочного эффекта от процесса его выполнения. И мне думается, что те из вас, кто мне ничего не прислал по первому заданию, не хотели присылать не целиком выполненное задание. Это неправильно! Присылать мне ответы нужно обязательно: именно по тому, кто сколько чего решил и как, я вижу, кто чего понял, и о чём рассказывать в следующий раз.

Во-вторых. Про darcs. Я не знаю, какое впечатление он произвёл на вас, но на меня бы оно произвело такое впечатление: оно непонятно зачем, им неудобно пользоваться, оно на каждый чих гневно и непонятно ругается, и плюс ко всему ещё и не работает! Я бы не захотел такой штукой пользоваться. Но вам им пользоваться придётся:

/!\ Начиная с этого момента я принимаю решения задач только через darcs. Но тут есть хорошая новость: ближайшие несколько занятий вам нужно делать только команды darcs init, darcs add и darcs record. Если вы помните, на них никаких проблем не случается. Есть новость и плохая: я буду очень гневно придираться к тому, что вы будете писать в имени автора (оно должно быть содержательно: либо имя и фамилия, либо логин, либо почтовый адрес + одно из предыдущих двух), и в описании изменений. Изменения нужно описывать по-английски и содержательно.

(!) Если у вас не уложилось в голове, чего я от вас требую и как проверить, увижу ли я ваши решения, делайте так: положим, создали вы репозиторий answers, и что-то в нём сделали (решили n задачек). Сделайте рядом с ним его копию: darcs get answers test. Вот то, что в ней находится, я и увижу. (Можете после очередных изменений в answers заходить в test и говорить darcs pull – до полного просветления. И ещё в сочетании с ними советую попробовать команды darcs show files, darcs changes -v, darcs whatsnew -s, darcs whatsnew -l).

Про причины проблем с darcs. Значительная часть проблем произошла из-за того, что я некоторые вещи недонастроил (могу даже сказать, какие именно: я не сделал, чтобы вы не могли лезть в центральный репозиторий в обход darcs, и я не сделал, чтобы вы не могли сохранять в репозиторий разметку конфликтов; как ни странно, эти две мелочи очень сильно попортили вам же жизнь) – мои извинения за это. Вторая часть была умышленная – это проблемы с кодировками. Я надеюсь, я продемонстрировал вам тем самым, что до тех пор, пока вы не будете понимать достаточно много тонкостей, с русским языком в программах лучше завязать (то, что я требую, чтобы язык при этом был именно английским – тоже существенно, и если среди вас будет достаточно людей, которые будут пользоваться латиницей, я и на эту тему вам устрою демонстрационную головную боль). Третья часть была организационная. Представьте себе, что репозиторий – это человек, которого я попросил записать ваши фамилии. Тогда то, чем занимались вы, выглядело, как будто вы все взяли и начали хором кричать ему каждый со своей стороны свою фамилию. Разумеется, из этого ничего выйти не должно было бы (ну вот у шести человек он за время занятий успел услышать фамилию в периоды случайно образовавшейся тишины). Были среди вас люди, которые предлагали устроить между вами какую-то дисциплину, чтобы писать по очереди – это бы сработало, если бы эти предложения вышли за рамки просто предложений.

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

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

Сейчас я хочу ещё раз отдельно описать правила хорошего тона при работе с репозиториями. Во-первых, сохранять в репозитории нужно только файлы с расширением *.py (у вас будет образовываться всякий другой мусор – бэкапы, скомпилированные файлы (*.pyc), и кто знает, что ещё – этот мусор в репозиторий попадать не должен). Во-вторых, изменения нужно подписывать по-английски. В-третьих, им нужно давать содержательные описания: вы потратили полчаса на написание какой-нибудь функции (или на отловку какой-нибудь ошибки), потратьте ещё минуту или две на то, чтобы осознать, что же именно вы сделали. Не обязательно описание втискивать в одну строку.

Документация

Ещё раз напоминаю, откуда добывать документацию по питону. (Я буду занудствовать и напоминать об этом ещё пару лекций).

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

Конец оффтопика

Итак, мы завершили со всеми хвостами. Я надеюсь за это занятие рассказать вам почти весь язык. Слушайте внимательно.

Списки

Список – это несколько штук, собранных вместе. (Точная формулировка: это упорядоченный набор объектов). Списки записываются так:

   1 >>> l = [1, "hello",2]  # пример списка; квадратные скобки показывают, что это список
   2 >>> print(l)
   3 [1, "hello", 2]

Элементы списка нумеруются с нуля:

   1 >>> l = [1, "hello", 2]
   2 >>> print(l[0])         # элементы списка нумеруется с нуля
   3 1

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

   1 >>> l = [1, "hello", 2]
   2 >>> print(l[-1])       # последний элемент имеет номер -1, предпоследний -2, и так далее -- потому, что не бывает числа -0
   3 2

Мы можем дописать новый элемент в конец списка:

   1 >>> l = [1, "hello", 2]
   2 >>> l.append(3)        # добавить в конец списка
   3 >>> print(l)
   4 [1, "hello", 2, 3]

Мы можем изменить любой элемент списка:

   1 >>> l = [1, "hello", 2]
   2 >>> l[1] = 3           # записать в l по индексу 1 (т.е. во второй элемент) число 3
   3 >>> print(1)
   4 [1, 3, 2]

/!\ Когда мы говорим о списке по-русски, нам удобнее использовать прилагательные "первый", "второй" и так далее. Давайте договоримся, что у нас первый элемент списка имеет номер ноль. Второй элемент списка имеет номер один. Предпоследний элемент списка имеет номер (или индекс) минус один. Будем всегда придерживаться имменно такой терминологии.

Так же, как и для строк1, операция + склеивает списки (вообще, в питоне принято, чтобы похожие по сути вещи вели себя похожим образом):

   1 >>> l = [1] + [2]     # склеиваем два списка
   2 >>> print(l)
   3 [1, 2]

Внутри списков могут храниться и другиие списки:

   1 >>> l = [1, [1, 2], 3]
   2 >>> print(l[1])
   3 [1, 2]
   4 >>> m = l[1]           
   5 >>> print(m)               # m -- это снова обычный список
   6 [1, 2]
   7 >>> print(m[0])            # в котором мы можем взять первый элемент
   8 1
   9 >>> print(l[1][0])         # мы можем записать то же самое без помощи m
  10 1

/!\ Ещё одно очень важное замечание: что происходит на самом деле, когда мы что-либо куда-либо присваиваем? Давайте представим себе, что у нас список – это такой червяк (см. рисунок), а число – это такой квадратик, внутри которого написано это число:

   1 >>> l = [1, [1, 2], 2]     #   l ----> [.|.|.]
   2                            #            | | `-> [2]
   3                            #            | `-> [.|.]
   4                            #      [1] <-'      | `-> [2]
   5                            #                   `-> [1]  

Когда мы присваиваем в список (или в переменную) что-то, что у нас уже на этой картинке было, мы добавляем стрелочку к соответствующему рисунку:

   1 >>> l = [1, [1, 2], 2]
   2 >>> l[2] = l[1]            #   l ----> [.|.|.]
   3                            #            | | `--v        [2]
   4                            #            | `-> [.|.]      :
   5                            #      [1] <-'      | `-> [2] :
   6                            #                   `-> [1]   :
   7                            #                             :
   8                            #       на это [2] стрелочек нет,
   9                            #             питон его выбросит

Что из этого получается?

   1 >>> l = [1, [1, 2], 2]
   2 >>> l[2] = l[1]
   3 >>> m = l[1]               # на картинке появилась буква m, от которой стрелочка указывает на того же червяка, что и 2-й и 3-й элементы списка
   4 >>> m[0] = 3
   5 >>> print(l)               # казалось бы, мы меняли только m, а изменилось l, притом в двух местах
   6 [1, [3, 2], [3, 2]]

Слайсы

Английское слово slice обозначает дольку или кусок. Что такое кусок списка? Это тоже список, только размером, наверное, поменьше.

   1 >>> l = [5, 6, 7, 8]
   2 >>> s = l[0:2]             # s -- тоже список, как и l
   3 >>> print(s)               # первый и предпоследний элемент списка,т.е [...)
   4 [5, 6]

Запись l[n:m] означает: создать список из элементов l, начиная с элемента с номером n (включая его), и завершая элементом с номером m (исключая его). Это будет вас поначалу путать, и вам придётся к этому привыкать. Ещё один способ уложить это в голову: питон всегда работает с полуоткрытыми интервалами, в математической нотации: [n,m).

В записи слайса можно опускать номер правой или левой границы, тогда питон будет идти в этом направлении до конца списка:

   1 >>> l = [5, 6, 7, 8]
   2 >>> print(l[:2])
   3 [5, 6]
   4 >>> print(l[2:])
   5 [7, 8]

Ещё один способ уложить в голове нотацию: l[:m] обозначает "взять первые m элементов списка".

Приглядевшись к этому примеру, можно понять, почему Гвидо выбрал именно такое определение слайса: так становится очень просто разрезать список пополам, как бы по запятой перед третьим элементом. l[:m] + l[m:] при любом m есть точная копия l.

range

Функция range – одна из самых фундаментальных функций для питона: создать список целых чисел в заданном диапазоне. Диапазон задаётся так же, как и для слайсов:

   1 >>> l = range(0, 2)            # создаём список целых чисел в дипазоне [0, 2)
   2 >>> print(l)
   3 [0, 1]

Если нам нужно просто список длин n: range(n) создаёт список из чисел в диапазоне [0, n).

Циклы

Давайте считать (это почти правда), что в питоне есть ровно один вид циклов – это циклы for.

for имя_переменной in список: для каждого элемента списка присваивает переменной его значение и в таком раскладе выполняет тело цикла.

Например. Хотим мы распечатать в столбик числа от 1 до 10. Пишем:

   1 >>> for x in range(1, 11):     # интервал [1, 10] совпадает с интервалом [1, 11)
   2 ...     print(x)
   3 ...

Как питон узнаёт, где начинается, где кончается тело цикла? Тело цикла имеет больше уровень отступа. Помните, в школе вас заставляли выравнивать программу лесенкой? (А ещё нужно было придумать, как относительно этой лесенки расставлять фигурные скобочки или begin и end). В питоне именно выравнивание лесенкой является признаком, что куда вложено. И только оно.

Сразу заметка по стилю: в питоне принято на каждый уровень вложенности делать отступ в 4 пробела. И Idle (редактор питона) будет сам вам всегда этот отступ таким делать, и я надеюсь, с этим у вас проблем возникнет мало (теоретически вообще не должно возникнуть). (Кстати, лично мне эта традиция не нравится, и в своих проектах я делаю отступы знаками табуляции. Это второй, менее официально приветствуемый, но всё же с благословения Гвидо кое-как, со скрипом оного Гвидо, допустимый стиль. Но это лирика. Заставить Idle использовать табуляции почти невозможно, поэтому у вас даже и выбора такого не будет).

А что если мы хотим пройти список в обратном порядке, например? Или распечатать каждый второй элемент списка?

Тогда нам нужно создать из нашего списка соответствующий. Например:

   1 >>> for x in reversed(range(1, 11)): # функция reversed(x) возвращает список, такой же как x, только задом-наперёд
   2 ...     print(x)
   3 ...

Эти строки распечатают нам числа от 10 до 1 по одному на строке.

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

Ещё один пример напоследок:

   1 >>> import math
   2 >>> l = ["world", "%.2f diggers" % math.pi]
   3 >>> for name in l:
   4 ...     print("Hello, %s!" % name)
   5 ...
   6 Hello, world!
   7 Hello, 3.14 diggers!

Условный оператор

Проверка условий делается конструкцией if. Отступы и двоеточия так же, как у циклов. Условия записываются довольно похожим образом на остальные популярные языки программирования:

   1 >>> for i in range(10):
   2 ...     if i == 2:
   3 ...         print("There is two!")
   4 ...
   5 There is two!

Как водится во всех языках программирования, где есть if, есть и else:

   1 >>> for i in range(4):
   2 ...     if i == 2:
   3 ...         print("There is two!")
   4 ...     else:
   5 ...         print("%s is not two..." % i)
   6 0 is not two...
   7 1 is not two...
   8 There is two!
   9 3 is not two...

Если мы хотим проверять много условий подряд, чтобы не выстраивалась лестница до небес, есть ещё и конструкция elif – их можно втыкать в любых количествах между if и else. (elif есть сокращение от else if; питон по очереди проверяет условия if и всех последующих elif и выполняет тело того из них, которое окажется верным; если ни одно условие не выполнилось, питон выполняет тело else, если таковое найдётся; идти они должны именно в таком порядке: if elif elif ... elif else). Пример:

   1 >>> x = 33
   2 >>> for n in range(100):
   3 ...     if x % 2 == 0:
   4 ...         x = x / 2
   5 ...     elif x % 3 == 0:
   6 ...         x = x / 3
   7 ...     else:
   8 ...         x = x * 3 + 1
   9 ...     print x

(Это что-то вроде "чисел-градин" – есть такие странные математические последовательности с очень простыми описаниями, которые себя очень непредсказуемо ведут).

До сих пор я всё время использовал сравние на равенство: ==. Есть ещё операции с самоочевидными значениями: <, <=, >, >=. Неравенство пишется как в Си-образных языках: !=. Логические операции пишутся словами and, or, not. И плюс к тому, части логических выражений можно брать в скобки, как и для арифметических. Пример:

   1 >>> if x > 1 and not (x > 3 or x != 9):
   2 >>>     print "x is weird"

Функции

Затрудняюсь определить понятие функции в программировании. Скажем так: функция – это такая штука, которой можно передать несколько значений (синонимы: аргументов, параметров) и получить от неё обратно результат. Между ними функция что-то делает – последовательность питоновских команд.

   1 >>> def fact(n):
   2 ...     result = 1
   3 ...     for i in range(n):
   4 ...         result = result * n
   5 ...     return result 
   6 >>> x = fact(5)
   7 >>> print(x)            # можно было бы написать >>> fact(5) или >>> print(fact(5))
   8 120

Разбираем по частям: def имя_функции(имя_аргумента, имя_аргумента, и т.п.): – заголовок фукнции. Говорит, как её назвать и как у неё внутри будут называться значения, которые ей передали. (Фактически, перед исполнением тела питон тупо создаёт переменные с именами аргументов, и присваивает в них переданные значения. В эти переменные можно потом присвоить что-нибудь другое – но этого как правило не следует делать). Далее идёт тело функции. Обычный знакомый нам питонский код. Команда return говорит, что будет являться значением функции, и завершает исполнение тела функции (return можно воткнуть в середину тела, тогда всё, что после return, просто не будет выполнено).

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

Примеры

Что делает и что вернёт на заданных примерах каждая из этих функций?

   1 >>> def f(items):
   2 ...     result = items[0]
   3 ...     for item in items:
   4 ...         if result < item:
   5 ...             result = item
   6 ...     return result
   7 ...
   8 >>> print(f([5, 4, 7, 2, 8]))

Приведу пример рассуждений, как понять точный ответ:

И так далее.

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

   1 >>> def f(items):
   2 ... result = []
   3 ...     for item in items:
   4 ...          result = result + [item]
   5 ...     return result
   6 ...
   7 >>> f(range(2, 8))
   8 [2, 3, 4, 5, 6, 7]

   1 >>> def f(items):
   2 ...     result = []
   3 ...     for item in items:
   4 ...          result = [item] +result
   5 ...     return result
   6 ...
   7 >>> f(range(2, 8))
   8 [7,6,5,4,3,2]

   1 >>> def f(items):
   2 ...     result = 0
   3 ...     for item in items:
   4 ...          result = item +result
   5 ...     return result
   6 ...
   7 >>> f(range(2, 8))
   8 27

   1 >>> def f(items):
   2 ...     result = []
   3 ...     for item in items:
   4 ...          result = [item] + [result]
   5 ...     return result
   6 ...
   7 >>> f(range(2, 8))
   8 [7, [6, [5, [4,[3,[2,[]]]]]]]

   1 >>> def f(items):
   2 ...     result = []
   3 ...     for item in items:
   4 ...          result = [item]
   5 ...     return result
   6 ...
   7 >>> f(range(2, 8))
   8 [7]

   1 >>> def f(items):
   2 ...     result = []
   3 ...     for item in items:
   4 ...          result = [item] + result + [item]
   5 ...     return result
   6 ...
   7 >>> f([2, 4, 6])
   8 [6, 4, 2, 2, 4, 6]

Как собственно писать програмы?

Всё, что я писал выше – диалоги с интерпретатором. Диалог с интерпретатором включает в себя наш ввод (т. е. питонскую программу) и его ответ. И чтобы их различать принято сохранять все ёлочки и точки в начале строки, как их пишет нам питон. (Кстати, Idle точек не печаетает, что не очень хорошо с его стороны). Несколько позже мы увидим ещё одну пользу от этого формата, кроме учебной.

Сейчас же мы хотим писать программы на питоне. Приведу пример, как это делать:

   1 def fib(n):
   2     if n == 1 or n == 2:
   3         return 1
   4     elif n == 0:
   5         return 0
   6     else:
   7         return fib(n - 1) + fib(n - 2)

   1 >>> import numbers
   2 >>> help(numbers)       # да! мы для этого ничего не делали, но питон уже считает, что про наш модуль есть хелпы!
   3 ...
   4 >>> numbers.fib(4)
   5 3

С этими знаниями можно приступать к решению задач.

  1. это намёк! (1)

  2. items тоже является частью состояния, но он не меняется в ходе работы функции, что легко увидеть при беглом просмотре, поэтому мы не будем его упоминать в состоянии; питон же его будет хранить здесь наравне с остальными переменными (2)