Kodomo

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

Несколько мелочей

Сегодняшний рассказ очень короткий – чтобы у вас было побольше времени на решение задач. И очень отрывочный: это маленькая свалка разной степени полезности сведений и внушений.

1. abs

Первым делом ссылка на документацию. Для решения задачи номер 6 (тем, кто до неё дойдёт), может пригодиться функция abs. (Это же заодно и намёк на подсказку к решению).

2. None, True, False

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

Поясняю.

В питоне есть три константы, которые как слышатся, так и пишутся – без кавычек и с большой буквы. Имя им: True, False, None.

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

С другой стороны, питон иногда сам говорит этими словами: если вы не указали, что возвращает функция, то её значение будет None; если вы написали верное логическое выражение (например 1 == 1), то его значением будет True, а если ложное, то False.

3. Операции сравнения: is, in

Повторю снова про операции сравнения.

Для начала, напомню то, на чём я не акцентировал ваше внимание: в питоне строго различается сравнение и присваивание. Некоторых это путает. Сравние пишется так: x == y или 1 == 2. Это выражение, значением которого является True или False. Присваивание пишется так: x = y. Это действие: записать в x то, что лежало в y.

Я уже упоминал о таких операциях сравнения: ==, != (не равно), >, <, >=, <=.

Новая операция сравнения: is. Оно похоже на ==, только == проверяет на равенство (например, для двух списков эта операция будет по очереди брать элемент из одного и из другого списка и сравнивать их), а операция is проверяет на совпадение. Если вспомнить рисунки про червяков и стрелочек, то x is y отвечает на вопрос: ведут ли стрелочки от x и y к одному и тому же объекту.

Все эти умные слова понимать особо не нужно. Основное и почти единственное практическое применение для is состоит в том, чтобы сравнивать значение с None.

Если где-то нужно проверить значение на None, то делать это нужно так и только так (это вопрос совместимости между разными версиями питона):

   1 if x is None:
   2     ...

Двойственное к is сравнение: is not. Оставляю вам в качестве упражнения на английский язык догадаться, что оно означает.

Ещё одна полезная операция сравнения: in – проверяет принадлежность элемента списку. Например:

   1 if x in [1, 2, 3]:
   2     print "Eks is a GOOD number."

(Для зануд: как правило, in сравнивает значения при помощи ==)

Наконец, последняя операция сравнения: not in. Снова оставляю вам на самостоятельное догадывание.

4. print vs. return

Так как мы начинали курс с диалогов с питоном, (суть которых состоит в том, что вы вводите выражение, а питон печатает его значение), то у некоторых из вас перепутались в голове ключевые слова print и return.

Посмотрим на два примера:

   1 def f1(x):
   2     print(x + 1)
   3 
   4 def f2(x):
   5     return x + 1

Разницу между ними можно увидеть даже в диалоге с питоном, хотя она и не будет очень уж понятной:

   1 >>> y = f1(2)
   2 3
   3 >>> print(y)
   4 None
   5 >>> y = f2(2)
   6 >>> print(y)
   7 3

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

Пока что же я буду просто занудствовать и требовать, чтобы там где в условии написано "...печатает...", вы использовали print, а там, где написано "...возвращает..." – соответственно return.

5. О побочных эффектах

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

В питоне на всё про всё используется одна и та же конструкция, но зато есть оговоренные традиции, что эти два понятия нужно различать. Традиции такие:

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

Если в функции, которая возвращает значение, в начале есть проверки, и нам хочется как-то обозначить, что ей не годятся аргументы, то мы напишем в этих проверках return None. (Пока что, покуда мы не знаем более хитрого подхода на этот случай).

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

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

6. Ещё о побочных эффектах

Вспомним описание функции min из прошлого задания.

Из этого описания нам казался бы логичным такой диалог с питоном:

   1 >>> x = [3, 1, 2]
   2 >>> min(x)
   3 1
   4 >>> x
   5 [3, 1, 2]

А теперь предположим, что функция min у нас определена так:

   1 def min(items):
   2     for i in range(len(items) - 1):
   3         if items[0] < items[1]:
   4             del items[1]
   5         else:
   6             del items[0]
   7     return items[0]

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

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

Отсюда правило: когда у нас аргументом идёт список (или какая-нибудь ещё мутируемая – так это приято называть – структура), мы действуем осторожно и стараемя его не попортить.

7. +=, ...

Наконец, пара просто полезных мелочей, которые можно использовать, а можно и не использовать – кому как нравится.

Мелочь первая.

Довольно часто у вас будет возникать конструкция вида x = x + 1. Для этой конструкции есть сокращённая запись: x += 1. Людям, знакомым с Си или Си-образными языками (в их числе перл и ява) эта конструкция уже знакома. Тем, кто видит такое в первый раз, выбор: или привыкать, или избегать.

Такие же сокращения есть и для кучи других операций: *=, /=, %= и так далее.

8. Tuple unpacking

Мелочь вторая.

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

   1 >>> coords = [1, 2]
   2 >>> x, y = coords
   3 >>> x
   4 1

Аналогично для трёх и более элементов.

Есть такая же конструкция и для одного элемента, но она выглядит несколько неказисто:

   1 >>> v = [1]
   2 >>> x, = v
   3 >>> x
   4 1

Если число элементов списка не равно числу переменных, в которые мы пытаемся список разобрать, питон будет ругаться. (Словами вроде too few или too many values to unpack).

9. Длинные записи в darcs

Как все, надеюсь, помнят, ваши решения задач я принимаю только через darcs. И более того, придираюсь к тому, как вы подписываете ваши изменения.

Некоторые из вас уже столкнулись с вопросом: а как подписывать изменения, если их много и сразу и разных.

Хороший ответ такой: их нужно записывать по чуть-чуть, так чтобы по истории изменений можно было всё понять. Ровно для этого darcs про каждый чих спрашивает: "а это записать? А это записать?"

Но есть и плохой, негодный, но всё же ответ на случай, если всё-таки хочется записать всё скопом, а я не очень придирался бы к описанию: когда darcs спросит "do you want to add a long comment?", отвечаете ему "y". darcs вас выбросит в текстовый редактор, где до строки со звёздочками вам предлагается описать изменения, а после неё идёт несколько подсказок со стороны darcs. Примерно неделю назад я перенастроил kodomo и kodomo-count таким образом, чтобы после этого darcs вас выбрасывал не в vi (который на вас всех навёл, надеюсь, ужас, тем, что даже выйти из него непонятно, как), а в nano, который суть очень простой и примитивнейший редактор, но в нём всё работает, как ожидается и внизу экрана всегда есть подсказка.

Если вы всё-таки стали писать многострочный комментарий к изменению в репозитории, придерживайтесь следующей традиции:

Первая строка с коротким описанием сути всего

* вторая строка пустая
* третья и далее строки начинаются со звёздочек
* в них пишется про каждое отдельное изменение

10. Задачка

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

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

Например:

   1 >>> diff([1, 2, 3])
   2 [-1, -1]
   3 >>> diff([2, 12, 85, 6])
   4 [-10, -73, 79]

План