воскресенье, 13 декабря 2015 г.

Конструктор класса; Наследование

Конструктор класса


Мы рассмотрели синтаксис описания классов и объявление атрибутов и методов в классе. Продолжим углубляться в принципы и синтаксис расширения создаваемых классов, рассмотрев конструкцию, которая называется конструктор класса. Под этим гордым наименованием, кроется простейший метод, который будет вызываться автоматически при создании объекта(экземпляра класса). Метод __init__ объявляется в классе и служит для того, чтобы заполнить создаваемые на базе этого класса объекты необходимой структурой данных. Также этот метод позволяет перехватить аргументы, которые можно передать в круглых скобочках после названия класса при создании объекта. Это зарезервированный метод Python, он будет вызываться автоматически при создании объекта. Впринципе метод "__init__" присутствует всегда, даже если вы его явно не объявляли, но он всегда "пустой". При необходимости, его всегда можно объявить внутри создаваемого класса. Для демонстрации, мы будем создавать роботов и наполнять их каким-то функционалом. Собственно мы опишем один класс, на основании которого, будем создавать объекты роботов. Для начала опишем простейший класс, который позволит создать объект робота с определенным именем и методом, при помощи которого робот будет представляться:

#-*- coding:utf-8 -*-
class Robot:
    def __init__(self, name):
        self.name = name
    def about(self):
        print u"Я робот {0}".format(self.name)


Рассмотрим поведение конструктора класса. Метод "__init__" принимает в себя два аргумента, один из которых - обязательный "self", а второй - имя робота, которого мы будем создавать в виде объекта. Как я говорил, конструктор класса перехватывает аргументы, которые мы можем передать в круглых скобках после имени класса при создании объекта. Действие нашего конструктора сводится к тому, что он создает атрибут "name" для создаваемого объекта (self.name) и присваевает значение, которое будет передано в виде аргумента при создании объекта и перехвачено нашим конструктором, Т.Е. "name". Запустим наш код на исполнение и посмотрим, что у нас получилось:

>>> Robot

>>> rob1 = Robot("Impulse")
>>> rob2 = Robot("Worker")
>>> rob1
< __main__.Robot instance at 0x01D9C170 >
>>> rob2
< __main__.Robot instance at 0x01D9C198 >
>>> dir(rob1)
['__doc__', '__init__', '__module__', 'about', 'name']
>>> print rob1.name # Атрибут имени в объекте
Impulse
>>> rob1.about()
Я робот Impulse
>>> rob2.about()
Я робот Worker
>>>


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

#-*- coding:utf-8 -*-
class TestInit:
    def __init__(self, arg1, arg2, arg3):
        print u"Я конструктор класса и вызвался при создании нового объекта."
        print u"В меня передали следующие аргументы: {0}, {1}, {2}".format(arg1,arg2,arg3)


Запустим код на исполнение и посмотрим на результат:

>>> a = TestInit("test", 123, "super")
Я конструктор класса и вызвался при создании нового объекта.
В меня передали следующие аргументы: test, 123, super
>>>


Теперь мы отчетливо наблюдаем реакцию конструктора класса при создании объекта.

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


Один из основных "столбов" на котором держится ооп, является такое понятие, как наследование. Наследование применяется тогда, когда у нас есть готовый класс, который содержит в себе все необходимые составляющие, а нам необходимо создать класс, который имел бы все то, что имеет этот класс, но при этом, есть необходимость изменить или добавить часть функционала. Чтобы  не воссоздавать всю структуру кода какого-то класса и добавлять один новый метод, что было бы не рационально, мы можем просто наследовать всю функциональность одного класса для другого, а уже в нем дописать/изменить необходимую функциональность. Для того, чтобы наследовать какой-то класс, мы указываем его в круглых скобочках при создании нового класса:

class MyClass(Parent):

В этом случае, класс "MyClass", унаследует все то, что находится в классе "Parent". Мы можем наследовать, как классы созданные нами, так и классы, которые предоставляются сторонними библиотеками или непосредственно базовые классы Python. Кпримеру любой из типов данных Python, является классом, который мы можем наследовать и расширить/изменить часть его функциональности. Давайте создадим новый класс и наследуем его от класса "list", что создаст нам класс, который будет похож на встроенный тип данных списка:

#-*- coding:utf-8 -*-
class MyList(list):
    pass # Заглушка, которая просто ничего не делает


Запустим код на исполнение:

>>> MyList([1,2,3,4,5])
[1, 2, 3, 4, 5]
>>> dir(MyList)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__dict__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
>>> lst = MyList([1,2,3,4,5])
>>> type(lst)

>>> lst.append(20)
>>> lst
[1, 2, 3, 4, 5, 20]
>>> lst.pop()
20
>>> lst
[1, 2, 3, 4, 5]
>>>


Как видно, класс "MyList", полностью похож на встроенный тип списков Python. Он унаследовал всю функциональность класса "list" и позволяет создавать объекты списков, которые ведут себя также, как списки создаваемые через базовый класс "list" встроенного типа данных списков Python. Допустим, мы хотим сделать метод, который добавлял бы что-то в начало списка по умолчанию, Т.Е. Чтобы нам не приходилось вызывать метод "insert" и указывать индекс 0. Мы просто объявим метод в классе "MyList" и создадим необходимую функциональность:

#-*- coding:utf-8 -*-
class MyList(list):
    def appendleft(self, element):
        self.insert(0, element)


Мы объявили метод "appendleft", который будет добавлять переданный в него элемент в начало нашего списка:

>>> a = MyList([1,2,3,4,5])
>>> dir(a)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__dict__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute_
_', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__mul__',
 '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'append', 'appendleft', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
>>> a
[1, 2, 3, 4, 5]
>>> a.appendleft(20)
>>> a
[20, 1, 2, 3, 4, 5]
>>>


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

Теперь соединим все наши знания воедино. Вернувшись к нашим роботам, создадим один класс, который будет описывать абстрактного робота, а на его основе, мы сделаем двух роботов: робот-спамер, который будет заспамливать экран и робот-уборщик, который будет очищать экран. Базовый класс, будет содержать в себе конструктор класса, в который мы будем передавать количество условных зарядов для робота, мы сможем пользоваться услугами робота до тех пор, пока у него будут единицы заряда. Также будет метод, который будет сообщать нам количество зарядов робота:

#-*- coding:utf-8 -*-
class Robot:
    def __init__(self, e):
        self.energy = e
    def say_energy(self):
        print u"Осталось {0} энергии".format(self.energy)


А вот теперь создадим 2 класса, которые будут описывать робота-уборщика и робота-спамера, добавив в создаваемые классы метод "activate", который будет активировать функциональность робота:

class RobotSpamer(Robot):
    def activate(self):
        if self.energy != 0:
            print "Spam"*1000
            print u"Я заспамил весь экран!"
            self.energy -= 1
        else:
            print u"Моя энергия равна нулю и я не могу выполнить это действие!"

class RobotCleaner(Robot):
    def activate(self):
        if self.energy != 0:
            os.system('cls') # Функция модуля "os", который должен быть импортирован. Выполняем системную команду "cls", которая чистит консоль в windows
            self.energy -= 1
        else:
            print u"Моя энергия равна нулю и я не могу выполнить это действие!"


Мы используем функцию из модуля "os", и нам необходимо импортировать его в нашем приложении. Весь код будет выглядить следующим образом:

#-*- coding:utf-8 -*-

import os

class Robot:
    def __init__(self, e):
        self.energy = e
    def say_energy(self):
        print u"Осталось {0} энергии".format(self.energy)

class RobotSpamer(Robot):
    def activate(self):
        if self.energy != 0:
            print "Spam"*1000
            print u"Я заспамил весь экран!"
            self.energy -= 1
        else:
            print u"Моя энергия равна нулю и я не могу выполнить это действие!"

class RobotCleaner(Robot):
    def activate(self):
        if self.energy != 0:
            os.system('cls') # Функция модуля "os", который должен быть импортирован. Выполняем системную команду "cls", которая чистит консоль в windows
        else:
            print u"Моя энергия равна нулю и я не могу выполнить это действие!"


Теперь запускаем код на исполнение, создаем и тестируем работоспособность наших роботов:

>>> spamer = RobotSpamer(3)
>>> cleaner = RobotCleaner(3)
>>> spamer.say_energy()
Осталось 3 энергии
>>> cleaner.say_energy()
Осталось 3 энергии
>>> spamer.activate()
SpamSpamSpamSpamSpamSpamSpamSpamSpamSpamSpamSpamSpamSpamSpamSpamSpamSpamSpamSpam
... Остальная часть текста пропущена ...
Я заспамил весь экран!
>>> cleaner.activate()
>>>


Сложно продемонстрировать, но если вы воспользуетесь нашим роботом-уборщиком, экран в действительности очиститься:

>>> spamer.say_energy()
Осталось 2 энергии
>>> cleaner.say_energy()
Осталось 2 энергии
>>>


Если мы еще несколько раз заставим поработать наших роботов, в результате мы получим сообщения:

>>> spamer.activate()
Моя энергия равна нулю и я не могу выполнить это действие!
>>> cleaner.activate()
Моя энергия равна нулю и я не могу выполнить это действие!
>>>


Энергия в наших роботах закончилась и они прекратили выполнять возложенные на них обязанности. Отметте, что и у первого и у второго робота, мы сделали метод с одним названием "activate", что позволяло нам активировать робот, в независимости от его типа. Это еще одно из явлений такого понятия, полиморфизм. Нам нет нужны помнить специфические методы, которыми мы будем запускать каждого из наших роботов, мы знаем, что у любого робота, в независимости от его предназначения, есть один ключевой метод, который позволяет воспользоваться роботом по его прямому назначению.

Комментариев нет:

Отправить комментарий