Лог
Содержание
break
Команда break в питоне выходит из цикла, то есть перепрыгивает сразу на следующую за ним (логически) строку. break можно использовать только синтаксически внутри цикла:
вложенные циклы
В питоне можно цикл исполнять в цикле. Цикл – это такое же действие, как и что угодно другое. Если у нас есть цикл, который проходит по y и печатает различные значения, то мы можем назвать весь этот цикл как бы одним действием (напечатай все y), которое можно исполнить несколько раз в цикле по x:
break и вложенные циклы
Из вложенных циклов break выходит только из самого глубокого, содержащего break:
return возвращает значение
Команда return (её можно использовать только внутри функций) обозначает, что вот это (то, что мы ей указали) и есть результат исполнения функции. В том же смысле, в каком 2 есть результат исполнения функции math.sqrt(4):
Nota bene. return только объявляет значение результатом. Он ничего не пишет на экран. Результат return мы можем положить в переменную, например (как и показано выше) или использовать в выражении, или выкинуть и проигнорировать. Мы вольны!
И наоборот print только печатает на экран, и не оставляет нам в программе никаких средств получить это значение обратно в какую-нибудь переменную или выражение.
Функции с несколькими результатами (tuple unpacking)
Если мы хотим, чтобы функция возвращала несколько значений, мы их должны все через запятую дать одному return.
Единственный способ получить результат из этой функции – это при вызове разложить эти значения в несколько переменных, тоже через запятую:
Функции без аргументов
Если наша функция нужна для того, чтобы что-то сделать, и ей для этого никаких значений на входе не нужно, то мы всё равно обязаны писать пару пустых скобок что при определении функции, что при вызове:
Такая функция вполне может и возвращать разные вещи при каждом вызове, например:
Упражнение. Придумайте ещё причины, почему функция без аргументов может возвращать разные значения, если мы её вызываем несколько раз. Поищите, нет ли Поищите, в питоне таких функций?
return выходит из функции
Действие команды return сотоит из двух:
во-первых, она говорит, что вот это, то, что мы ей дали, есть результат вычисления функции
- во-вторых, раз мы посчитали результат и он уже известен, то нам больше в этой функции делать нечего, и можно (а значит и нужно) из функции выйти
Отсюда мораль номер один: всё, что идёт в теле функции после return вообще никогда не будет исполнено.
Мораль номер два: return позволяет нам вывалиться из нескольких вложенных циклов сразу наружу:
1 words = ['hello', 'world', 'wonderful']
2 def is_in_words(word):
3 for other_word in words:
4 print("Looking at", other_word)
5 if word == other_word:
6 return True
7 print("Found nothing")
8 return False
9 print("Done, leaving")
10
11 result = is_in_words('hoohoo')
12 print("Result for hoohoo is", result)
13 result = is_in_words('world')
14 print("Result for world is", result)
Ещё один пример на ту же тему:
1 chess = [
2 [' ']*5 + ['K'] + [' ']*2,
3 [' ']*8,
4 [' ']*8,
5 [' ']*8,
6 [' ']*8
7 ]
8 def is_empty(field):
9 for line in field:
10 for cell in line:
11 print('cell is', cell, '.')
12 if cell != ' ':
13 return False
14 return True
15
16 answer = is_empty(chess)
17 print('Field in `chess` is empty?', answer)
18 answer = is_empty([[' ']*8]*8)
19 print('Empty field is empty?', answer)
Классы
Мы уже знаем, что такое объект:
- это то, что можно положить в переменную
это то, внутрь чего можно залезть через точку: объект.атрибут, объект.метод()
Мы знаем, что объекты бывают разных классов. Мы уже знаем, что ощутимо по-разному ведут себя:
- числа
- строки
- списки
- холст (и прочие примитивы tkinter)
- модули
Но при этом всё это классы объектов. И любая конкретная строка (скажем "hello") – это объект класса строк.
Иногда мы хотим создавать новые классы объектов, которые будут вести себя совсем по-другому. Например, мы хотим сделать класс объектов "снаряд", чтобы он умел летать по нашим законам и рисовать себя на экране.
Сначала просто объявим питону, что мы хотим новый класс объектов: Missile, – и, например, что у объектов этого класса будет метод initiate:
Всё, что мы пишем в теле класса (то есть внутри констуркции class с отступом от начала строки) будет общим для всех объектов этого класса.
Как мы уже знаем, мы можем у объекта читать значения атрибутов. Точно так же мы их можем писать. Атрибут – это просто переменная, лежащая внутри объекта.
В свою очередь, метод – это просто функция, лежащая внутри объекта. А значит, когда мы её исполняем, ей нужно знать, внутри какого объекта она лежит. Для этого ей первым аргументом передают этот объект. (Жёсткая традиция гласит, что первый аргумент у каждого метода называется self). При этом надо заметить, что при вызове мы уже сказали питону "залезь в этот объект и достань из него метод", поэтому при вызове нам нужно в скобках писать на один аргумент меньше.
Соответственно, когда мы внутри метода класса хотим говорить про внутренности того объекта, из которого мы вынули метод, мы всегда обязаны использовать слово self: self.x = 1 или print(self.x) или self.initiate() и т.п.
Чтобы создавать объекты класса Missile мы должны, собственно, вызывать функцию Missile.
Ещё примеры про то, что первый аргумент self – это просто тот объект, внутри которого лежит метод, и, поэтому, при вызове метода питон его передаёт за нас автоматически:
1 class Missile:
2 color = 'red'
3 def initiate(self):
4 self.x = 1
5 def update(self, delta_t):
6 self.x = self.x + delta_t
7
8 this_missile = Missile()
9 this_missile.x = 99
10 this_missile.initiate()
11 other_missile = Missile()
12 other_missile.initiate()
13 other_missile.x = 42
14 this_missile.update(0.01)
Или вот так, с парой координат:
Но вообще, программисты ужасно не любят повторяться, а у нас вечно выходило, что на создание объекта нам нужно две строки: собственно объект создать и наполнить его каким-то начальным содержимым.
Когда питон создаёт новый объект, он первым делом смотрит, нет ли в этом классе метода __init__. Если метод __init__ есть, то тогда питон сразу, как создаст объект, вызывает созданный_объект.__init__(...), и даёт ему в качестве аргументов всё то, что мы решили дать в качестве аргументов функции Missile, которая создаёт объекты:
Ещё один пример на тему того, какие бывают специальные имена методов у классов. Всякий раз, где бы и когда бы питон ни видел a + b, он всегда это заменяет на a.__add__(b). Таким образом, мы можем для наших объектов изменять поведение операции +. (И ровно так же устроен тот факт, что 1 + 2 и "as" + "best" ведут себя по-разному).
Например, хотим мы сделать такой класс объектов: пара координат (или точка). Этот объект умеет хранить пару координат, а ещё мы хотим уметь две пары координат между собой складывать. Пишется это так:
1 class Point:
2 def __init__(self, x, y):
3 self.x = x
4 self.y = y
5 def __add__(self, other):
6 return Point(self.x + other.x, self.y + other.y)
7
8 location = Point(32, 50)
9 velocity = Point(5, 2.5)
10 print(location.x, location.y)
11 new_location = location + velocity
12 print(new_location.x, new_location.y)
13 other_new_location = location.__add__(velocity)
14 print(other_new_location.x, other_new_location.y)
Пьяный снаряд
Итак, всю теорию мы знаем, осталось довести пример до ума.
Две мелочи. 1) В tk у разных виджетов есть метод after(milliseconds, func), который говорит, что через столько-то миллисекунд нужно вызвать функцию func. (Рекомендую не вызывать его со значением меньше 1 в первом аргументе, это может вызвать странное поведение). 2) У холста есть метод delete(what), который говорит, что мы хотим с холста что-то стерерь. В этом месте есть богатый язык указания того, что именно мы хотим стирать, но нам хватит того, что мы можем попросить его стереть с холста всё: canvas.delete("all").
Мы хотим сделать снаряд, который помнит про себя свои координаты: x и y. (Описываем соответствующим образом __init__, чтобы у нас при создании снаряда атрибуты x и y в нём сразу были).
Ещё мы хотим иногда (по таймеру) говорить снаряду: прошло чуть-чуть времени, пересчитай своё положение и перерисуйся. Для этого мы делаем метод update. В нём мы: пересчитываем новые значения координат, стираем всё с холста (потому, что когда мы вызовем update второй-третий и далее разы, у нас на холсте уже будет нарисован снаряд, и его нужно стереть прежде, чем рисовать что-то новое)1, рисуем новый снаряд. И последнее, что мы делаем, это говорим: разбуди меня ещё раз через 100 миллисекунд. (То есть через 0.1 секунды).
Последнее неочевидное место в этой программе состоит в том, что нам нужно создать объект снаряда и вызвать из него метод update в первый раз, чтобы потом его снова и снова стал вызвать tk.
1 import tkinter as tk
2 import math
3
4 class Missile:
5 def __init__(self, x, y):
6 self.x = x
7 self.y = y
8 def update(self):
9 self.x = self.x + 1
10 self.y = self.y + math.sin(self.x)*10
11 canvas.delete('all')
12 canvas.create_oval(self.x-50,self.y-50,
13 self.x+50,self.y+50)
14 canvas.after(100, self.update)
15
16 root = tk.Tk()
17 canvas = tk.Canvas(root, width=500, height=500)
18 canvas.pack()
19 this_missile = Missile(10, 200)
20 this_missile.update()
21 root.mainloop()
Это зыбкое место будет ломаться и вести себя плохо, если мы попытаемся запустить больше одного снаряда. Почему? (1)