Лог №4
Контрольная работа
Диктант:
- Возьмём пустой список и назовём его result. Два раза добавим к нему в конец по еднице. Возьмём число n равное 7. Для каждого x из диапазона от 0 до n исключительно добавим в конец списка result новый элемент, являющийся суммой последних двух элеметов, бывших в нём. Напечатаем на экран текст: "n-ое число Фибоначчи есть x", где вместо n подставлено значение n, вместо x подставлено значение последнего элемента списка.
Перевод на питон:
Разбор ДЗ: папоротник
Задача графическая, начнём с пустого окошка:
Запускаем-проверяем. Вроде работает. Писать программы нужно всегда вот такими маленькими шажками: написали две-три строки, сразу запускаем-проверяем. Даже если строки никак не проявляются, мы хотя бы узнаем, не сделали ли мы ошибку в какой-нибудь запятой.
Чем раньше мы увидим, что что-то ведёт себя не так, как мы ожидаем, тем в меньшем числе строк кода нам искать ошибку.
Решение задачи про папоротник (как сказано в условии) состоит в том, чтобы выбирать одно из нескольких преобразований, описанных таблицей. Добавим эту таблицу в программу:
1 import tkinter as tk
2
3 transformation1 = [0, 0, 0, 0.16, 0, 0]
4 transformation2 = [0.85, 0.04, -0.04, 0.85, 0, 1.6]
5 transformation3 = [0.2, -0.26, 0.23, 0.22, 0, 1.6]
6 transformation4 = [-0.15, 0.28, 0.26, 0.24, 0, 0.44]
7 transformations = [transformation1, transformation2, transformation3,
8 transformation4]
9
10 root = tk.Tk()
11 root.mainloop()
И снова запустим, чтобы убедиться, что нет ошибок.
А ещё сразу хочется поменять заголовок окна. Для этого у объекта окна есть метод title():
1 import tkinter as tk
2
3 transformation1 = [0, 0, 0, 0.16, 0, 0]
4 transformation2 = [0.85, 0.04, -0.04, 0.85, 0, 1.6]
5 transformation3 = [0.2, -0.26, 0.23, 0.22, 0, 1.6]
6 transformation4 = [-0.15, 0.28, 0.26, 0.24, 0, 0.44]
7 transformations = [transformation1, transformation2, transformation3,
8 transformation4]
9
10 root = tk.Tk()
11 root.title("Fern") # в переводе на русский: папоротник
12 root.mainloop()
Запускаем-проверяем. Окно поменяло заголовок? Если да, то всё в порядке.
Снова привычное, которое у нас пока есть во всех графических программах: создать холст.
1 import tkinter as tk
2
3 width = 500
4 height = 500
5
6 transformation1 = [0, 0, 0, 0.16, 0, 0]
7 transformation2 = [0.85, 0.04, -0.04, 0.85, 0, 1.6]
8 transformation3 = [0.2, -0.26, 0.23, 0.22, 0, 1.6]
9 transformation4 = [-0.15, 0.28, 0.26, 0.24, 0, 0.44]
10 transformations = [transformation1, transformation2, transformation3,
11 transformation4]
12
13 root = tk.Tk()
14 root.title("Fern") # в переводе на русский: папоротник
15 canvas = tk.Canvas(root, width=width, height=height, background="black")
16 canvas.pack(fill="both", expand="yes")
17 root.mainloop()
Запускаем-проверяем. Оно стало как-то выглядеть, но всё ещё ничего не делает.
Приделываем правила перехода:
1 import tkinter as tk
2 import random
3
4 width = 500
5 height = 500
6
7 transformation1 = [0, 0, 0, 0.16, 0, 0]
8 transformation2 = [0.85, 0.04, -0.04, 0.85, 0, 1.6]
9 transformation3 = [0.2, -0.26, 0.23, 0.22, 0, 1.6]
10 transformation4 = [-0.15, 0.28, 0.26, 0.24, 0, 0.44]
11 transformations = [transformation1, transformation2, transformation3,
12 transformation4]
13
14 root = tk.Tk()
15 root.title("Fern")
16 canvas = tk.Canvas(root, width=width, height=height, background='white')
17 canvas.pack(fill='both', expand='yes')
18
19 x = 0
20 y = 0
21 for n in range(10000):
22 random_index = random.randint(0, len(transformations)-1)
23 transformation = transformations[random_index]
24
25 a = transformation[0]
26 b = transformation[1]
27 c = transformation[2]
28 d = transformation[3]
29 e = transformation[4]
30 f = transformation[5]
31
32 new_x = a*x + b*y + e
33 new_y = c*x + d*y + f
34 x = new_x
35 y = new_y
36
37 canvas.create_oval(x-1,y-1,x+1,y+1, fill='green', outline='green')
38
39 root.mainloop()
Что-то не так, рисуется только одна точка. Попробуем увеличить...
Но при этом нужно ещё, чтобы мы не вмешались в процесс пересчёта координат.
Поэтому введём ещё одну пару координат: sx, sy (от слова screen – экранные координаты).
1 import tkinter as tk
2 import random
3
4 width = 500
5 height = 500
6
7 transformation1 = [0, 0, 0, 0.16, 0, 0]
8 transformation2 = [0.85, 0.04, -0.04, 0.85, 0, 1.6]
9 transformation3 = [0.2, -0.26, 0.23, 0.22, 0, 1.6]
10 transformation4 = [-0.15, 0.28, 0.26, 0.24, 0, 0.44]
11 transformations = [transformation1, transformation2, transformation3,
12 transformation4]
13
14 root = tk.Tk()
15 root.title("Fern")
16 canvas = tk.Canvas(root, width=width, height=height, background='white')
17 canvas.pack(fill='both', expand='yes')
18
19 x = 0
20 y = 0
21 for n in range(10000):
22 random_index = random.randint(0, len(transformations)-1)
23 transformation = transformations[random_index]
24
25 a = transformation[0]
26 b = transformation[1]
27 c = transformation[2]
28 d = transformation[3]
29 e = transformation[4]
30 f = transformation[5]
31
32 new_x = a*x + b*y + e
33 new_y = c*x + d*y + f
34 x = new_x
35 y = new_y
36
37 sx = 50*x
38 sy = 50*y
39 canvas.create_oval(sx-1,sy-1,sx+1,sy+1,fill='green',outline='green')
40
41 root.mainloop()
Запускаем-проверяем. Что-то появилось, но перевёрнуто и сдвинуто.
Переворачиваем: заменяем sy = 50*y на sy = height - 50*y.
Запускаем-проверяем. Да, стало лучше, нужно сдвинуть, видимо, на половину экрана: заменяем sx = 50*x на sx = width + 50*x
Запускаем-проверяем. Теперь похоже на правду?
Есть пара мест, где решение можно упростить.
Во-первых, в питоне в модуле random среди многочисленных полезных способов генерировать случайные штуки есть уже готовая функция random.choice(список).
Во-вторых, в питоне есть конструкция под названием "tuple unpacking" или "распаковка кортежей". Если у нас есть список из ровно двух элементов, которые мы хотим разложить по переменным x и y, то мы можем написать так:
1 x, y = values
И заодно:
1 x, y = [1, 2]
Вообще, название "распаковка кортежей" происходит от названия структуры данных "кортежи", которые ведут себя в точности как списки, только их нельзя менять.
А обозначаются в питоне кортежи как просто несколько чисел через запятую.
И ещё одна приятность: в питоне можно в конце списка оставить лишнюю запятую, питон не будет жаловаться. Иногда это оказывается очень удобно.
Пока что нам кортежи не особо полезны. Большая польза от них возникает та, где есть словари и множества – мы до них через раз доберёмся. Пока что нам полезно понимать о такой опасности:
Мы можем применить эти знания к нашему примеру:
1 import tkinter as tk
2 import random
3
4 width = 500
5 height = 500
6
7 transformation1 = [0, 0, 0, 0.16, 0, 0]
8 transformation2 = [0.85, 0.04, -0.04, 0.85, 0, 1.6]
9 transformation3 = [0.2, -0.26, 0.23, 0.22, 0, 1.6]
10 transformation4 = [-0.15, 0.28, 0.26, 0.24, 0, 0.44]
11 transformations = [transformation1, transformation2, transformation3,
12 transformation4]
13
14 root = tk.Tk()
15 root.title("Fern")
16 canvas = tk.Canvas(root, width=width, height=height, background='white')
17 canvas.pack(fill='both', expand='yes')
18
19 x = 0
20 y = 0
21 for n in range(10000):
22 transformation = random.choice(transformations)
23 a, b, c, d, e, f = transformation
24 x, y = a*x + b*y + e, c*x + d*y + f
25 sx, sy = 50*x + width/2, height - 50*y
26 canvas.create_oval(sx-1,sy-1,sx+1,sy+1,fill='green',outline='green')
27
28 root.mainloop()
Пример явно стал сильно более коротким, но потерял в читаемости и понятности.
Функции
Предположим, мы хотим написать все простые числа, тем более, что мы научились проверять число на простоту.
Можно, конечно, написать сразу целиком всю программу для этого, но это довольно мучительно... А хочется, чтобы мы могли написать так:
Да вот только функции такой в питоне нет, которая проверяет, простое ли число. Зато питон даёт нам возможность определять свои функции, а мы знаем, как проверить число на простоту.
Для определения своей функции нужно сказать: def имя функции (названия аргументов): – и всё, что идёт с отступом будет являться реализацией этой функции. Ещё в теле функции мы можем использовать конструкцию return значение для того, чтобы объявить значение результатом функции.
Когда питон вызывает такую функцию, он первым делом создаёт по переменной на каждый аргумент функции (имена этих переменных – названия аргументов), затем исполняет то, что написано в теле функции, а когда доходит до return, то подставляет написанное там значение вместо вызова функции.
Пример:
Для каждого числа n питон кладёт это значение в переменную number, проверяет, делится ли это число на 2, и если да, то весь вызов is_prime(n) заменяет на False, а если нет, то заменяет его на True.
Конечно, это неправильное определение простоты числа, давайте сделаем правильное:
В конструкции return значение есть одна очень полезная тонкость. По существу эта конструкция является командой питону совершить два действия:
объявить значение результатом функции
- сказать, что всё, функция имеет результат, значит дальше в ней считать нечего
То есть всё, что стоит после return, питон уже не будет исполнять, если он исполнил return.
Это даёт нам возможность сильно упростить функцию is_prime. Ведь нам достаточно идти по числам, и как только мы нашли делитель, мы сразу знаем ответь: число составное. И только если мы перебрали _все_ числа, и дошли до конца перебора, то – и тут нам уже никаких проверок делать не нужно – мы сразу знаем, что число простое (с маленькой оговорочкой, конечно: числа меньше 2 простыми по определению быть не могут):
И ещё одна мелочь про функции: если в функции нет return, то это то же самое, как если бы в ней последней строкой добавили return None
Кнопки
Договоримся о новом термине. Словом "виджет" принято называть любой элемент графического интерфейса: кнопочку, надпись, холст (canvas), полосу прокрутки (scrollbar), текстовое поле, меню, и т.п.
Мы пока что из виджетов общались только с Canvas – холстом.
В tk очень легко создаются просто надписи, это делает виджет Label:
Любой виджет, после того, как мы его создали, нужно где-то как-то расположить. Затем мы и вызываем у него метод pack().
Но интереснее кнопки Button. С ними тоже всё просто:
Только это у нас какая-то очень тупая кнопка вышла, она ничего не делает.
Чтобы она что-то делала, мы должны питону сказать, какую функцию вызвать в тот момент, когда мы на кнопку нажали.
Тут важное замечание. Если f у нас функция, то эти две записи делают совсем разное:
f() обозначает вычислить функцию и получить результат (но при этом мы не дали функции никаких аргументов)
f обозначает "взять в руки функцию" (ну и можно её кому-нибудь передать).
Попробуем:
Здесь мы передали кнопке нашу функцию, чтобы кнопка могла её вызвать.
А если мы случайно поставили там пару лишних скобок, то мы вызвали функцию сразу, а кнопке дали None вместо функции:
Такая программа пишет Hello ещё до того, как нарисовалось окошко, а кнопка ничего не делает.
Ещё одна приятная мелочь в tk: мы можем у любого виджета вызвать метод configure и поменять любые параметры, с которыми мы его создавали (ну и вообще любые параметры, которые есть у виджета).
Упражнение: допишите эту программу так, чтобы нажатие на одну кнопку меняло текст другой кнопки.
Следующий тип виджетов: поле ввода (Entry). С ним тоже всё очень просто.
Нам от него интересно то, что из него можно методом get получить введённое в него содержимое. Теперь мы можем сделать программу, которая меняет текст кнопки на то, что введено в поле ввода:
1 import tkinter as tk
2
3 def change_text():
4 entry_text = entry.get()
5 but2.configure(text=entry_text)
6
7 root = tk.Tk()
8 entry = tk.Entry(root)
9 entry.pack(fill='x', expand='yes')
10 but2 = tk.Button(root, text="hello", command=change_text)
11 but2.pack(fill='x', expand='yes')
12 root.mainloop()
Если мы хотим сделать так, чтобы поле ввода реагировало на нажатие Enter, то нам нужен метод bind. bind (англ. привязать) говорит питону, какую функцию вызывать, когда случилось какое-то событие. Соответственно, у него есть два аргумента: описание случившегося события и функция.
Кроме того, bind вызывает функцию не просто так, а ещё и передаёт ей объект, содержащий подробное описание события. (Там куча информации, начиная от времени и окна, и заканчивая положением мыши и кодом нажатой кнопки). Нам этот объект пока не нужен, поэтому мы его просто проигнорируем: но питон-то не потерпит, чтобы мы вызвали с одним аргументом функцию, которой по описанию аргументов не положено! Так что придётся пока сделать маленькую функцию-обёртку, которая этот лишний аргумент выкинет.
Что же касается языка описания событий, то он большой и могучий, но пока нам из него хватит события <Return> – нажата клавиша Enter.
Попробуем:
1 import tkinter as tk
2
3 def change_text():
4 entry_text = entry.get()
5 but2.configure(text=entry_text)
6
7 def change_text2(event):
8 change_text()
9
10 root = tk.Tk()
11 entry = tk.Entry(root, text='text')
12 entry.pack(fill='x', expand='yes')
13 entry.bind('<Return>', change_text2)
14 but2 = tk.Button(root, text="hello", command=change_text)
15 but2.pack(fill='x', expand='yes')
16 root.mainloop()
Получилось?
Тогда пора играть в виселицу!