Kodomo

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

Лог №4

Контрольная работа

Диктант:

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

   1 result = []
   2 
   3 ##result.append(1)
   4 ##result.append(1)
   5 result = result + [1,1]
   6 
   7 n = 7
   8 
   9 for x in range(n):
  10     result.append(result[-1] + result[-2])
  11 
  12 x = result[-1]
  13 ##print(n, "-ое число Фибоначчи есть", x)
  14 print("{}-ое число Фибоначчи есть {}".format(n, x))

Разбор ДЗ: папоротник

Задача графическая, начнём с пустого окошка:

   1 import tkinter as tk
   2 
   3 root = tk.Tk()
   4 root.mainloop()

Запускаем-проверяем. Вроде работает. Писать программы нужно всегда вот такими маленькими шажками: написали две-три строки, сразу запускаем-проверяем. Даже если строки никак не проявляются, мы хотя бы узнаем, не сделали ли мы ошибку в какой-нибудь запятой.

Чем раньше мы увидим, что что-то ведёт себя не так, как мы ожидаем, тем в меньшем числе строк кода нам искать ошибку.

Решение задачи про папоротник (как сказано в условии) состоит в том, чтобы выбирать одно из нескольких преобразований, описанных таблицей. Добавим эту таблицу в программу:

   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 1,2 # это кортеж
   2 1.2 # это число
   3 1, # это кортеж
   4 1. # это число

Мы можем применить эти знания к нашему примеру:

   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()

Пример явно стал сильно более коротким, но потерял в читаемости и понятности.

Функции

Предположим, мы хотим написать все простые числа, тем более, что мы научились проверять число на простоту.

Можно, конечно, написать сразу целиком всю программу для этого, но это довольно мучительно... А хочется, чтобы мы могли написать так:

   1 for n in range(100):
   2         if is_prime(n):
   3                 print(n)

Да вот только функции такой в питоне нет, которая проверяет, простое ли число. Зато питон даёт нам возможность определять свои функции, а мы знаем, как проверить число на простоту.

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

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

Пример:

   1 def is_prime(number):
   2         if number % 2 == 0:
   3                 return False
   4         else:
   5                 return True
   6 
   7 for n in range(100):
   8         if is_prime(n):
   9                 print(n)

Для каждого числа n питон кладёт это значение в переменную number, проверяет, делится ли это число на 2, и если да, то весь вызов is_prime(n) заменяет на False, а если нет, то заменяет его на True.

Конечно, это неправильное определение простоты числа, давайте сделаем правильное:

   1 def is_prime(number):
   2     b = range(2, number)
   3     d = 0
   4     for x in b:
   5         c = number % x
   6         if c == 0:
   7             d = x
   8     if d == 0 and number >= 2:
   9         return True
  10     else:
  11         return False
  12 
  13 primes = []
  14 for n in range(1000):
  15     if is_prime(n):
  16         primes.append(n)
  17 print(primes)

В конструкции return значение есть одна очень полезная тонкость. По существу эта конструкция является командой питону совершить два действия:

То есть всё, что стоит после return, питон уже не будет исполнять, если он исполнил return.

Это даёт нам возможность сильно упростить функцию is_prime. Ведь нам достаточно идти по числам, и как только мы нашли делитель, мы сразу знаем ответь: число составное. И только если мы перебрали _все_ числа, и дошли до конца перебора, то – и тут нам уже никаких проверок делать не нужно – мы сразу знаем, что число простое (с маленькой оговорочкой, конечно: числа меньше 2 простыми по определению быть не могут):

   1 def is_prime(number):
   2     for x in range(2, number):
   3         remainder = number % x
   4         if remainder == 0:
   5             return False
   6     if number < 2:
   7         return False
   8     return True
   9     
  10 primes = []
  11 for n in range(1000):
  12     if is_prime(n):
  13         primes.append(n)
  14 print(primes)

И ещё одна мелочь про функции: если в функции нет return, то это то же самое, как если бы в ней последней строкой добавили return None

Кнопки

Договоримся о новом термине. Словом "виджет" принято называть любой элемент графического интерфейса: кнопочку, надпись, холст (canvas), полосу прокрутки (scrollbar), текстовое поле, меню, и т.п.

Мы пока что из виджетов общались только с Canvas – холстом.

В tk очень легко создаются просто надписи, это делает виджет Label:

   1 import tkinter as tk
   2 
   3 root = tk.Tk()
   4 label = tk.Label(root, text="hello")
   5 label.pack()
   6 root.mainloop()

Любой виджет, после того, как мы его создали, нужно где-то как-то расположить. Затем мы и вызываем у него метод pack().

Но интереснее кнопки Button. С ними тоже всё просто:

   1 import tkinter as tk
   2 
   3 root = tk.Tk()
   4 button = tk.Button(root, text="hello")
   5 button.pack()
   6 root.mainloop()

Только это у нас какая-то очень тупая кнопка вышла, она ничего не делает.

Чтобы она что-то делала, мы должны питону сказать, какую функцию вызвать в тот момент, когда мы на кнопку нажали.

Тут важное замечание. Если f у нас функция, то эти две записи делают совсем разное:

Попробуем:

   1 import tkinter as tk
   2 
   3 def when_clicked():
   4         print("Hello!")
   5 
   6 root = tk.Tk()
   7 button = tk.Button(root, text="hello", command=when_clicked)
   8 button.pack()
   9 root.mainloop()

Здесь мы передали кнопке нашу функцию, чтобы кнопка могла её вызвать.

А если мы случайно поставили там пару лишних скобок, то мы вызвали функцию сразу, а кнопке дали None вместо функции:

   1 import tkinter as tk
   2 
   3 def when_clicked():
   4         print("Hello!")
   5 
   6 root = tk.Tk()
   7 button = tk.Button(root, text="hello", command=when_clicked())
   8 button.pack()
   9 root.mainloop()

Такая программа пишет Hello ещё до того, как нарисовалось окошко, а кнопка ничего не делает.

Ещё одна приятная мелочь в tk: мы можем у любого виджета вызвать метод configure и поменять любые параметры, с которыми мы его создавали (ну и вообще любые параметры, которые есть у виджета).

   1 import tkinter as tk
   2 
   3 def change_text():
   4     but2.configure(text="I've been clicked")
   5 
   6 root = tk.Tk()
   7 but1 = tk.Button(root, text="hello")
   8 but1.pack()
   9 but2 = tk.Button(root, text="hello", command=change_text)
  10 but2.pack()
  11 root.mainloop()

Упражнение: допишите эту программу так, чтобы нажатие на одну кнопку меняло текст другой кнопки.

Следующий тип виджетов: поле ввода (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()

Получилось?

Тогда пора играть в виселицу!