Kodomo

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

Лог

break

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

   1 for x in range(10):
   2     print("Hello", x)
   3     if x == 4:
   4         break
   5 print("Goodbye")

вложенные циклы

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

   1 for x in range(3):
   2     for y in range(4):
   3         print(x, y)

break и вложенные циклы

Из вложенных циклов break выходит только из самого глубокого, содержащего break:

   1 for x in range(3):
   2     for y in range(3):
   3         if y > x:
   4             break
   5         print(x, y)

return возвращает значение

Команда return (её можно использовать только внутри функций) обозначает, что вот это (то, что мы ей указали) и есть результат исполнения функции. В том же смысле, в каком 2 есть результат исполнения функции math.sqrt(4):

   1 def f(x):
   2     return x + 1
   3 
   4 y = f(2)
   5 z = f(4)
   6 print(y)

Nota bene. return только объявляет значение результатом. Он ничего не пишет на экран. Результат return мы можем положить в переменную, например (как и показано выше) или использовать в выражении, или выкинуть и проигнорировать. Мы вольны!

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

Функции с несколькими результатами (tuple unpacking)

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

Единственный способ получить результат из этой функции – это при вызове разложить эти значения в несколько переменных, тоже через запятую:

   1 def g(x):
   2     return 1, 2, x + 2
   3 a, b, c = g(3)
   4 print(a, b, c)

Функции без аргументов

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

   1 def f():
   2     return "Hello"
   3 y = f()
   4 print(y)

Такая функция вполне может и возвращать разные вещи при каждом вызове, например:

   1 import random
   2 def f():
   3     return random.choice("Hello")
   4 print(f(), f(), f())

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

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)

Классы

Мы уже знаем, что такое объект:

Мы знаем, что объекты бывают разных классов. Мы уже знаем, что ощутимо по-разному ведут себя:

Но при этом всё это классы объектов. И любая конкретная строка (скажем "hello") – это объект класса строк.

Иногда мы хотим создавать новые классы объектов, которые будут вести себя совсем по-другому. Например, мы хотим сделать класс объектов "снаряд", чтобы он умел летать по нашим законам и рисовать себя на экране.

Сначала просто объявим питону, что мы хотим новый класс объектов: Missile, – и, например, что у объектов этого класса будет метод initiate:

   1 class Missile:
   2     def initiate(self):
   3         self.x = 1

Всё, что мы пишем в теле класса (то есть внутри констуркции class с отступом от начала строки) будет общим для всех объектов этого класса.

Как мы уже знаем, мы можем у объекта читать значения атрибутов. Точно так же мы их можем писать. Атрибут – это просто переменная, лежащая внутри объекта.

В свою очередь, метод – это просто функция, лежащая внутри объекта. А значит, когда мы её исполняем, ей нужно знать, внутри какого объекта она лежит. Для этого ей первым аргументом передают этот объект. (Жёсткая традиция гласит, что первый аргумент у каждого метода называется self). При этом надо заметить, что при вызове мы уже сказали питону "залезь в этот объект и достань из него метод", поэтому при вызове нам нужно в скобках писать на один аргумент меньше.

Соответственно, когда мы внутри метода класса хотим говорить про внутренности того объекта, из которого мы вынули метод, мы всегда обязаны использовать слово self: self.x = 1 или print(self.x) или self.initiate() и т.п.

   1 class Missile:
   2     def initiate(self):
   3         self.x = 1
   4 
   5 a = Missile()
   6 a.initiate()
   7 b = Missile()
   8 b.initiate()
   9 a.x = 2
  10 print(a.x, b.x)

Чтобы создавать объекты класса 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)

Или вот так, с парой координат:

   1 class Missile:
   2     color = 'red'
   3     def initiate(self, x, y):
   4         self.x = x
   5         self.y = y
   6     def update(self, delta_t):
   7         self.x = self.x + delta_t
   8 
   9 this_missile = Missile()
  10 this_missile.initiate(15, 30)
  11 other_missile = Missile()
  12 other_missile.initiate(400, 85)
  13 this_missile.update(0.01)

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

Когда питон создаёт новый объект, он первым делом смотрит, нет ли в этом классе метода __init__. Если метод __init__ есть, то тогда питон сразу, как создаст объект, вызывает созданный_объект.__init__(...), и даёт ему в качестве аргументов всё то, что мы решили дать в качестве аргументов функции Missile, которая создаёт объекты:

   1 class Missile:
   2     color = 'red'
   3     def __init__(self, x, y):
   4         self.x = x
   5         self.y = y
   6     def update(self, delta_t):
   7         self.x = self.x + delta_t
   8 
   9 this_missile = Missile(15, 35)
  10 other_missile = Missile(400, 85)
  11 this_missile.update(0.01)

Ещё один пример на тему того, какие бывают специальные имена методов у классов. Всякий раз, где бы и когда бы питон ни видел 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. Это зыбкое место будет ломаться и вести себя плохо, если мы попытаемся запустить больше одного снаряда. Почему? (1)