Игра "Пушка"
Содержание
Общая информация
При процедурном программировании программа разбивается на части в соответствии с алгоритмом: каждая функция является составной частью алгоритма. При объектно-ориентированном программировании программа строится как совокупность взаимодействующих объектов. Что же такое объект? С точки зрения объектно-ориентированного подхода, объект - это нечто, обладающее состоянием и поведением. По сути, состояние - это данные, соответствующие объекту, например рост, вес и громкость гавканья собаки. Поведением называется набор операций, которые можно производить с объектом. Например, в случае с собакой: покормить, погладить, погулять. Эти операции, которые можно выполнять над объектом обычно называют методами (по крайней мере в контексте языка Python).
Часто приходится работать с объектами одной природы. Например, если у нас несколько собак, то у них у всех одинаковые наборы данных (хотя значения могут различаться) и одинаковые методы. Для определения такой "общей природы" вводятся классы. Класс, по сути, есть шаблон объектов - базовое состояние и общее поведение для всех объектов этого класса. Объекты одного класса называют экземплярами этого класса. Также, класс является типом данных для объектов.
В языке Python для определения класса используется оператор class. Рассмотрим следующий пример:
class Dog:
def say_gaw(self): # имя self для первого аргумента метода это общепринятое но не обязательное правило
print('Gaw-gaw')
my_dog = Dog()
another_dog = Dog()
my_dog.say_gaw() # вызовется функция Dog.say_gaw с параметром self = my_dog
another_dog.say_gaw()
Здесь мы описали класс Dog, который задает один метод. При описании методов класса первый аргумент есть ссылка на экземепляр, для которого этот метод вызывается. Далее, мы создали пару собак и позвали для каждой метод say_gaw. Для создания объектов используется имя класса со скобками. Методы вызываются через точку после имени объекта. Заметте, что первый аргумент метода - self - при вызове указывать не нужно, т.к. им становится сам объект (тот для которого зовем метод, его имя перед точкой).
Для хранения данных в объектах испальзуются атрибуты. Это те самые "свойства" объекта - рост, вес и т.п. Атрибуты могут иметь любой тип данных. Так же как и с обычными переменными в Python, объявлять атрибуты неким специальным образом не нужно, они появляются автоматически, при первом приваивании, следующим образом:
class Dog:
def say_gaw(self):
if self.angry:
print('GAW-GAW')
else:
print('Gaw-gaw')
def ping(self):
self.angry = True
def feed(self, food_count):
if food_count > 10:
self.angry = False
my_dog = Dog()
my_dog.feed(20)
my_dog.say_gaw() # напечатает Gaw-gaw
my_dog.ping()
my_dog.say_gaw() # напечатает GAW-GAW
Часто для атрибутов хочется иметь некоторое начальное значение. В предыдущем примере есть проблема - если собака попытается гавкнуть до того как ее пнули или покормили, она навернется с ошибкой "AttributeError: 'Dog' object has no attribute 'angry'". Для решения этой проблемы используется метод со специальным именем - __init__, который вызывается автоматически при создании объекта:
class Dog:
def __init__(self):
self.angry = False
def say_gaw(self):
if self.angry:
print('GAW-GAW')
else:
print('Gaw-gaw')
my_dog = Dog()
my_dog.say_gaw() # ошибки нет, напечатает Gaw-gaw
Метод __init__
называется конструктором. Собственно, конструктор зовется при выполнении конструкции вида ИмяКласса()
, в нашем случае - Dog()
. Аргументом self
для конструктора становится вновь созданный объект. Конструктор, также как и обычные методы, может иметь дополнительные аргументы кроме self
. Эти аргументы передаются при создании объекта, следующим образом:
class Dog:
def __init__(self, angry, count):
self.angry = angry
self.count = count
def say_gaw(self):
if self.angry:
print('GAW-' * self.count)
else:
print('gaw-' * self.count)
my_dog = Dog(True, 3)
my_dog.say_gaw() # ошибки нет, напечатает Gaw-gaw
Класс в Python также является объектом. Объект создается с помощью ключевого слова class, как в примерах выше. Таким образом, в предыдущем примере вызов my_dog.say_gaw()
эквивалентен вызову Dog.say_gaw(my_dog)
. Разобраться, какой объект какому классу принадлежит помогут встроенные функции type
и isinstance
:
>>> class A:
... pass
...
>>> a = A()
>>> type(a)
<class '__main__.A'>
>>> type(A)
<class 'type'>
>>> type(type)
<class 'type'>
>>> type(1)
<class 'int'>
>>> type(int)
<class 'type'>
>>>
>>> isinstance(1, int)
True
>>> isinstance(1, A)
False
>>> isinstance(a, A)
True
>>> isinstance(type, type)
True
>>> isinstance(A, type)
True
Примечание: здесь был объявлен, в тестовых целях, пустой класс A - в нем нет никаких методов.
Нужно заметить также, что методы, которые класс определяет, не будут методами для него (как для объекта), а будут просто атрибутами типа function
. Действительно, при вызове Dog.say_gaw(my_dog)
никакой дополнительный self
уже не передается, функция запускается в том виде, в котором мы ее написали. Это также можно показать следующим образом:
>>> class A:
... def f(self):
... print('hello')
...
>>>
>>> type(A.f)
<class 'function'>
>>> a = A()
>>> type(a.f)
<class 'method'>
То есть, A.f - это функция, а a.f - метод. Метод здесь это объект, который содержит в себе ссылку на объект, за которым этот метод закреплен (в нашем случае это объект a
) и ссылку на функцию, которую надо вызывать. Соответственно при вызове метод завет эту функцию, передавя ссылку на свой объект как первый аргумент и прокидывая остальные аргументы.
>>> m = a.f
>>> m is A.f
False
>>> m.__func__ is A.f
True
>>> m.__self__ is a
True
>>> m.__func__(m.__self__)
hello
>>> m()
hello
>>> a.f()
hello
>>> A.f(a)
hello
Стандартные методы
Кроме __init__ есть и другие стандартные методы, которые можно определить в описании класса.
Метод __repr__ должен возвращать текстовую строку, содержащую код (на языке Python), создающую объект, равный данному. Естественно, метод __repr__ должен содержать вызов конструктора, которому передаются в качестве параметров все строки исходного объекта, то есть он должен возвращать строку вида "Person('Иванов', 5)"
Пример метода __repr__ (для экономии места опустим описание конструктора __init__):
class Dog:
def __repr__(self):
return "Dog('" + self.angry + "', " + self.count + ")"
Таким образом, метод __repr__ возвращает строку с описанием объекта, которое может быть воспринято итерпретатором языка Питон.
Метод __str__ возвращает строку, являющуюся описанием объекта в том виде, в котором его удобно будет воспринимать человеку. Здесь не нужно выводить имя конструктора, можно, например, просто вернуть строку с содержимым всех полей:
class Dog
def __str__(self):
return self.name + ' ' + str(self.score)
Метод __str__ будет вызываться, когда вызывается функция str от данного объекта, например, str(Vasya). То есть создавая метод __str__ вы даете указание Питону, как преобразовывать данный объект к типу str.
Поскольку функция print использует именно функцию str для вывода объекта на экран, то определение метода __str__ позволит выводить объекты на экран удобным способом: при помощи print.
Упражнения - игра "Пушка"
Упражнение №1
Команда студентов начала разрабатывать игру "Пушка". Для тестирования использовался обфусцированный исходный файл, который позволяет увидеть процесс, но скрывает исходный код: obfuscated
В результате празднования окончания сессии компьютер, на котором лежали работающие исходники, был испорчен. На флешке была найдена только промежуточная версия. Помогите восстановить работоспособность программы используя имеющийся исходный код: gunsource
Подсказка: для начала, исправте код так, чтобы он запускался, и поправте все 'FIXME'.
Упражнение №2
Улучшите программу из №1 добавив 2 цели.
Упражнение №3
Улучшите программу из №2 сделав цели движущимися.