Программирование аркадных игр и обучение информатике

Программирование аркадных игр
и обучение информатике

Chapter 12: Введение в классы

12.1 Дефинирование и создание простых классов

Если программисту надо передать много взаимосвязанных частей данных функции, может быть сложным использовать только переменные. Например, каждый раз, когда нам нужно передать адрес, было бы утомительным передавать имя, улицу, город, страну и почтовый код. Лучше задать структуру данных, которая будет содержать всю информацию вместе, а затем присваивать данным имена, такие как Address. Этого можно легко достигуть в Python или любом другом современном языке используя классы, class.

Это пример кода класса, содержащего адрес:

class Address():
    name=""
    line1=""
    line2=""
    city=""
    state=""
    zip=""

В коде, приведённом выше, Address является именем класса. Переменные в классе, такие, как name и city, называются атрибутами или полями.

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

В отличие от функции, имена классов должны начинаться с большой буквы. Хотя можно начинать имена классов и с маленькой буквы, это не считается хорошим стилем.

Программисты часто рисуют диаграммы классов, а диаграмма этого класса будет смотреться следующим образом:

”

Имя класса вверху, атрибуты находятся под ним. Справа от атрибута написан тип данных, такой как string или integer.

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

# Создать инстанцию класса address
homeAddress=Address()

# Задать поля в address
homeAddress.name="John Smith"
homeAddress.line1="701 N. C Street"
homeAddress.line2="Carver Science Building"
homeAddress.city="Indianola"
homeAddress.state="IA"
homeAddress.zip="50125" 

Инстанция класса address была создана на 9й строке. Заметьте, что имя класса используется вместе со скобками. Именем переменной может быть любым до тех пор, пока оно придерживается нормальных правил наименования переменных.

Для задачи полей в классе, программа использует оператор-точку. Этот оператор - точка, между homeAddress и самим полем. Левое поле присваивание кажется нормальным. Линии с 12 по 17 задают значения полей.

Обычная ошибка при работе с классами: забывать задавать переменную. Если был создан один адресс, обычное дело предположить, что компьютер будет знать, что нужно использовать этот адрес при задачи города “Indianola”. Однако, это так не работает.

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

# Создать другой адресс
vacationHomeAddress=Address()

# Задать поля этого адреса
vacationHomeAddress.name="John Smith"
vacationHomeAddress.line1="1122 Main Street"
vacationHomeAddress.line2=""
vacationHomeAddress.city="Panama City Beach"
vacationHomeAddress.state="FL"
vacationHomeAddress.zip="32407"

print ("The client's main home is in "+homeAddress.city)
print ("His vacation home is in "+vacationHomeAddress.city)

Строка 19 создаёт второй адрес и присваивает его новой переменной. Строки 22-27 дают значения полям инстанции нового класса. Строка 29 выводит город домашнего адреса потому, что homeAddress появляется после оператора-точки. Строка 30 выводит отпускной адрес потому, что vacationHomeAddress появляется перед оператором-точкой.

В вышеприведённом примере, Address является классом class, потому что он задаёт новую классификацию объекту данных. Переменные homeAddress и vacationHomeAddress обращаются к объектам, потому что они ссылаются на настоящие инстанции класса Address. Объект - это просто интснция класса.

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

# Вывести адрес на экран
def printAddress(address):
    print (address.name)
    # If there is a line1 in the address, print it
    if( len(address.line1) > 0 ):
        print (address.line1)
    # If there is a line2 in the address, print it
    if( len(address.line2) > 0 ):
        print( address.line2 )
    print( address.city+", "+address.state+" "+address.zip )

printAddress( homeAddress )
print()
printAddress( vacationHomeAddress )

12.1.1 Проверка пройденного

  1. Напишите код для создания инстанции следующего класса и задайте его атрибуты:
    ”
    class Dog():
        age=0
        name=""
        weight=0
    
  2. Создайте код для созданиях двух разных инстанций этого класса и задайте атрибуты обоих объектов:
    class Person():
        name=""
        cellPhone=""
        email=""
    
  3. Для нижеприведённого кода, напишите класс, который содержит подходящее имя класса и атрибуты, которые позволят коду работать.
    myBird = Bird()
    myBird.color="green"
    myBird.name="Sunny"
    myBird.breed="Sun Conure"
    
  4. Создайте класс, который будет представлять из себя персонажа простой 2D игры. Включите атрибуты для расположения на экране, имени, силы.
  5. Следующий код запускается, но неправильно работает. Что программист сделал неправильно?
    class Person:
        name=""
        money=0
    
    nancy=Person()
    name="Nancy"
    money=100
    
  6. Ознакомьтесь с кодом. Он не запускается. Можете ли вы заметить ошибку?
    class Person:
        name=""
        money=0
    
    bob = Person()
    print (bob.name,"has",money,"dollars.")
    
  7. Даже с исправленной ошибкой, программа не покажет желаемый вывод:
    Bob has 0 dollars.
    Почему?

12.1.2 Методы

Помимо атрибутов, у класса могут быть методы. Метод - это функция, которая существует внутри класса. Расширяя предыдущий пример класса Dog, нижеприведённый код добавляет метод лая.

class Dog():
    age=0
    name=""
    weight=0

    def bark(self):
        print( "Woof" )	

Дефиниция метода показана на строках 6-7. Дефиниции метода в классе смотрятся так же, как и дефиниции функций. Большое отличие в том, что добавляется параметр self на строке 6. Первым параметром любого метода в классе должен быть self. Этот параметр требуется, даже если функция его не использует.

Вот важные пункты, которые следует держать в голове, создавая методы для классов:

Методы должны быть вызваны в схожем с присвоением атрибутов объекту стилем. Ознакомьтесь с нижеприведённым кодом.

myDog = Dog()

myDog.name="Spot"
myDog.weight=20
myDog.age=3

myDog.bark()

На 9й строке "создаётся" собака. Строки 11-13 задают атрибуты объекта. Строка 14 вызывает метод bark. Заметьте: несмотря на то, что у функции bark есть один параметр, self, вызов не передаёт ей ничего. Это потому, что первый параметр является ссылкой на сам объект dog. На самом деле, при обработке кода Python делает вызов вроде

# Просто пример, не работает
Dog.bark(myDog)

Если функции bark требуется сослаться на один из атрибутов, она делает это используя переменную self. Например, мы можем поменять класс Dog так, чтобы когда собака лает, он также будет выводить её имя. В нижеприведённом коде, доступ к атрибуту имени осуществляется через переменную self.

    def bark(self):
        print( "Woof says",self.name )

Атрибуты - прилагательные, методы - глаголы. Диаграмма класса выглядела бы следующим образом:

”

12.1.3 Пример: класс Ball

Это пример кода, который можно использовать в Python/Pygame для рисования мячика. Содержание всех параметров в классе делает управление данными проще.

”
class Ball():
    # --- Атрибуты класса ---
    # Ball position
    x=0
    y=0

    # Ball's vector
    change_x=0
    change_y=0

    # Ball size
    size=10

    # Ball color
    color=[255,255,255]

	# --- Методы класса ---
    def move(self):
        self.x += self.change_x
        self.y += self.change_y
            
    def draw(self, screen):
        pygame.draw.circle(screen, self.color, [self.x, self.y], self.size )

Ниже - код, который выполнится перед главным циклом программы для создания шара и задания его атрибутов:

theBall = Ball()
theBall.x = 100
theBall.y = 100
theBall.change_x = 2
theBall.change_y = 1
theBall.color = [255,0,0]

Этот код пойдёт внутрь главного цикла чтобы двигать и рисовать шар:

theBall.move()
theBall.draw(screen)

12.2 Ссылки

Ознакомьтесь с нижеприведённым кодом:

class Person:
    name=""
    money=0

bob = Person()
bob.name="Bob"
bob.money=100

Обычным заблуждением при работе с объектами является предположение, что переменная bob является объектом Person. Это не так. Переменная bob - ссылка на объект Person. Это показано дополняя наш пример кода и запуская его:

nancy = bob
nancy.name="Nancy"

print(bob.name,"has",bob.money,"dollars.")
print(nancy.name,"has",nancy.money,"dollars.")

Если bob действительно был бы объектом, то строка 9 могла бы создать копию объекта и у нас уже было бы два разных объекта. Вывод программы показал бы, что у Боба и Нэнси есть 100 долларов. Но, при запуске, программа выводит следующую строку:

Nancy has 100 dollars.
Nancy has 100 dollars.
”

То, что хранит bob - это ссылка на объект. Другим названием этого может быть адрес, указатель или handle. Это адрес в компьютерной памяти, где хранится сам объект. Адрес хранится в шестнадцатиричном формате, который при выводе будет выглядеть как-то вроде 0x1e504. Когда выполняется девятая строка, копируется адрес, а не сам объект, на который указывает адрес.

12.2.1 Функции и ссылки

Нижеприведённый код схож с примером, показаным раньше. Код на первой строке создаёт функцию, берущую число. Переменная money - копия того, что передаётся. Прибавить к этому число 100 не изменит значения, хранимого в bob.money на строке 10. Поэтому print на 13й строке выводит 100, а не 200.

def giveMoney1(money):
    money += 100

class Person:
    name=""
    money=0

bob = Person()
bob.name="Bob"
bob.money=100

giveMoney1(bob.money)
print(bob.money)

Посмотрите на дополнительный код снизу. Этот код действительно заставляет переменную bob.money увеличиться, а затем выражение print выведет 200.

def giveMoney2(person):
    person.money += 100

giveMoney2(bob)
print(bob.money)

Почему именно так? Потому, что person содержит адрес объекта в памяти, а не сам объект. Можно воспринимать это как номер своего банковского счёта. У функции есть копия номера банковского счёта, но не копия самого счёта. Так что после использования номера банковского счёта для того, чтобы разместить на нём 100 долларов, количество денег на счету Боба увеличивается.

Массивы работают по такому же принципу. Функция, которая берёт массив (list) как параметр и меняет его значения, будет менять значения того же самого массива, используемого в коде. Копируется адрес массива, но не сам массив.

12.2.2 Проверка пройденного

  1. Создайте класс Cat. Дайте ему аттрибуты name(имя), color(цвет) и weight(вес). Добавьте ему метод под названием meow.
  2. Создайте объект класса cat, установите атрибуты, вызовите метод meow.
  3. Создайте класс Monster. Дайте ему атрибуты name(имя) и целочисленный атрибут health(здоровье). Создайте метод decreaseHealth который берёт числовой параметр amount и уменьшает здоровье на переданное количество. Внутри метода, сделайте вывод строки, оповещающей о смерти монстра, если его здоровье упало ниже нуля.

12.3 Конструкторы

Классы в Python имеют особую функцию, вызываемую в момент создания инстанции класса. Например, ниже приведён класс Dog, продолжающийся кодом, создающим инстанцию этого класса:

class Dog()
    name=""

myDog = Dog()

Добавив функцию под названием constructor, программист может добавить код, автоматически вызываемый каждый раз во время создании инстанции класса. Посмотрите пример конструктора ниже:

class Dog():
    name=""

    # Конструктор
    # Вызывается в момент создания объекта этого типа
    def __init__(self):
        print("A new dog is born!")

# This creates the dog
myDog = Dog()

Конструктор начинается на 6й строке. Он должен быть назван __init__. Он должен брать self как первый параметр. Дополнительно можно задать ещё параметров. Когда программа запустится, она выведет:
A new dog is born!
Когда объект типа Dog создаётся на 10й строке, автоматически вызывается функция __init__, которая затем выводит сообщение на экран.

Конструктор может быть использован для инициализации и задании данных объекта. Например, в примере кода атрибут name оставлен пустым после создания объекта dog. Что, если программист захочет дать начальное значение одному из аттрибутов объекта? Некоторые объекты нуждаются в значениях атрибутов с самого начала, на момент их создания. Конструктор может быть использован для осуществления таких задач. Ознакомьтесь с кодом ниже:

class Dog():
    name=""

    # Конструктор
    # Вызывается на момент создания объекта этого типа
    def __init__(self, newName):
        self.name = newName

# Это создаёт собаку
myDog = Dog("Spot")

# Вывести имя собаки, убедиться, что оно было установлено
print(myDog.name)

# Эта строка выдаст ошибку, потому что
# конструктору не было передано имя
herDog = Dog()
На шестой строке программе даются дополнительные параметры newName. Значение этого параметра используется для установления значения атрибута name в классе Dog. Частая ошибка заключается в установлении имён параметров функции __init__ такими же как и имена атрибутов, предполагая, что атрибуты автоматически возьмут значение переданных параметров. Этого не случится.
class Dog():
    name="Rover"

    # Конструктор
    # Вызывается во время создания объекта
    def __init__(self, name):
        # This will print "Rover"
        print(self.name)
        # This will print "Spot"
        print(name)

# Это создаёт собаку
myDog = Dog("Spot")

В предыдущем примере, выводятся две переменных. Переменная name была создана как параметр метода на шестой строке. Эта переменная пропадёт как только метод закончит свою работу. Хотя у неё такое же имя, как и у атрибута name, (также называемого объектной переменной), она является абсолютно другой переменной. Переменная self.name ссылается на атрибут name этой конкретной инстанции класса Dog. Она будет существовать пока существует этот объект класса Dog.

12.3.1 Проверка пройденного

Скачайте рабочий лист для этой проверки: [Microsoft Word DOCX] [Microsoft Word DOC] [PDF]

  1. Должны ли имена класса начинаться с маленькой или большой буквы?
  2. Должны ли имена методов класса начинаться с маленькой или большой буквы?
  3. Should attribute names begin with an upper or lower case letter?
  4. Что в классе следует перечислить сначала - атрибуты или методы?
  5. Как иначе можно назвать ссылку?
  6. Какое другое название переменной инстанции?
  7. Как иначе можно назвать инстанцию класса?
  8. Создайте класс под названием Star, который будет выводить “A star is born!” каждый раз во время его создания.
  9. Создайте класс Monster с атрибутами для здоровья и имени. Добавьте конструктор к этому классу, устанавливающий здоровья и имя равными значениям, переданным как параметры.

12.4 Наследование

Другой мощной особенностью использования классов и объектов является возможность наследования. Можно создать класс и унаследовать все атрибуты и методы родительского класса. Например, программа может создавать класс Person, содержащий все атрибуты, нужные для описания человека. Затем, программа может создавать дочерние классы, наследующие эти поля. Дочерние классы могут добавлять свои поля и методы, соответствующие их нуждам.

Родительский класс должен быть более обобщённой, абстрактной версией дочернего класса. Например, родительский класс может быть Animal(животное), а дочерним классом Dog(собака), которая также может иметь свой, дочерний класс Poodle(пудель). Это отношение ребёнка-родителя объясняется с помощью является. Например дельфин является млекопитающим. Это не работает в обратном направлении, потому что млекопитающее не обязательно должно быть дельфином. Поэтому класс Dolphin никогда не должен быть родителем класса Mammal. Подобно этому, класс Table не должен быть родителем класса Chair, потому что стул не является столом.

”
class Person():
    name=""

class Employee(Person):
    job_title=""

class Customer(Person):
    email=""

johnSmith = Person()
johnSmith.name = "John Smith"

janeEmployee = Employee()
janeEmployee.name = "Jane Employee"
janeEmployee.job_title = "Web Developer"

bobCustomer = Customer()
bobCustomer.name = "Bob Customer"
bobCustomer.email = "send_me@spam.com"

Поставив Person между скобок на строках 4 и 7, программист сказал компьютеру, что Person является родительским классом как для Employee(работника), так и для Customer(клиента). Это позволяет программе задать атрибут name на строках 14 и 18.

Методы также наследуются. Нижеприведённый код выведет “Person created” три раза, потому что классы клиента и работника наследуют конструктор от родительского класса:

class Person():
    name=""

    def __init__(self):
        print("Person created")

class Employee(Person):
    job_title=""

class Customer(Person):
    email=""

johnSmith = Person()
janeEmployee = Employee()
bobCustomer = Customer()

Методы могут быть перекрытыми дочерним классом, предоставляя им новый функционал. В нижеприведённом коде, дочерние классы содержат свои конструкторы, поэтому родительский конструктор запущен не будет:

class Person():
    name=""

    def __init__(self):
        print("Person created")

class Employee(Person):
    job_title=""

    def __init__(self):
        print("Employee created")

class Customer(Person):
    email=""

    def __init__(self):
        print("Customer created")

johnSmith = Person()
janeEmployee = Employee()
bobCustomer = Customer()

Если программист захочет вызвать метод как родительского, так и дочернего класса, дочерний класс может вызвать родительский метод:

class Person():
    name=""

    def __init__(self):
        print("Person created")

class Employee(Person):
    job_title=""

    def __init__(self):
        Person.__init__(self)
        print("Employee created")

class Customer(Person):
    email=""

    def __init__(self):
        Person.__init__(self)
        print("Customer created")

johnSmith = Person()
janeEmployee = Employee()
bobCustomer = Customer()

12.4.1 Проверка пройденного

Создайте следующую программу. Прежде чем приступать к программированию, попробуйте описать её структуру. Скачайте форму с заданиями: [Microsoft Word DOCX] [Microsoft Word DOC] [PDF]

  1. Напишите код, описывающий класс Animal:
    • Добавьте атрибут имени животного.
    • Добавьте метод eat(), выводящий “Ням ням.”
    • Добавьте метод makeNoise(), выводящий “[animal name] говорит Гррр.”
    • Добавьте конструктор классу Animal, выводящий “Родилось животное.”
  2. Класс Cat:
    • Пусть Animal будет его родительским классом.
    • Метод makeNoise() класса Cat выводит “[animal name] говорит Мяу.”
    • Конструктор для Cat выводит “Родился кот”, а также вызывает родительский конструктор.
  3. Класс Dog:
    • Пусть Animal будет его родительским классом.
    • Метод makeNoise() для Dog выводит “[animal name] говорит Гав.”
    • Конструктор Dog выводит “Родилась собака.”, а также вызывает родительский конструктор.
  4. Основная программа:
    • Код, создающий кота, двух собак и одно простое животное.
    • Дайте имя каждому животному.
    • Код, вызывающий eat() и makeNoise() для каждого животного.

You are not logged in. Log in here and track your progress.