В этом уроке:

На этом уроке вы познакомитесь с основными понятиями ООП в Python: 1) Классы в Python, 2) Объекты или экземпляры класса, 3) Определение и работа с методами, 4) Наследование в ООП.

Что есть объектно‑ориентированное программирование (ООП)?

Объектно-ориентированное программирование или коротко ООП есть парадигма программирования в которой программы строятся таким образом, что свойства и поведение объединяются в отдельные единые объекты.

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

Иными словами, объектно‑ориентированное программирование — это подход для моделирования конкретных реальных сущностей, например, автомобиль, а также отношений между такими сущностями, как компании и сотрудники, студенты и преподаватели и т.п. ООП моделирует реальные объекты программными единицами, которые имеют некоторые данные связаны с ними и могут выполнять определенные действия.

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

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

ПРИМЕЧАНИЕ:

Поскольку Python является языком программирования с множеством парадигм, вы можете выбрать ту парадигму, которая лучше всего подходит для решения задачи, смешать разные парадигмы в одной программе и/или переключаться с одной парадигмы на другую по мере развития своего решения.

Классы в Python

Первоначально ориентируясь на данные, каждая вещь или объект является экземпляром некоторого класса.

Примитивные структуры данных, доступные в Python, такие как числа, строки и списки, предназначены для представления простых вещей, таких как стоимость чего‑либо, название стихотворения и ваши любимые цвета соответственно.

Что если вы хотите представить что-то гораздо более сложное?

Допустим, вы хотите наблюдать за разными животными. Если вы используете список, первый элемент может быть его именем, а второй элемент может представлять возраст.

Как бы вы узнали, какой элемент должен быть? Что делать, если у вас было 100 разных животных? Вы уверены, что у каждого животного есть и имя, и возраст, и так далее? Что если вы захотите добавить другие свойства этим животным? Это уже требует некоторой организации, и это именно то, что нужно для классов.

Классы используются для создания новых пользовательских структур данных, которые содержат произвольную информацию о чем-либо. В случае с животным мы могли бы создать класс Animal() для описания таких свойства, как имя и возраст.

Важно отметить, что класс просто обеспечивает структуру — это образец того, как что-то должно быть определено, но на самом деле это просто шаблон, за которым нет реального контента. Класс Animal() может указывать, что имя и возраст необходимы для определения животного, но на самом деле он не содержит ни имени, ни возраста конкретного животного. Это просто описание, классификация.

Класс — это идея, наше представление о характеристиках какой‑либо сущности, о том, как эта сущность должна быть определена.

Объекты Python (экземпляры класса)

В то время как класс является шаблоном, экземпляр является реализацией класса с фактическими значениями, буквально объектом, принадлежащим определенному классу. Это больше не идея; это реальное животное, как собака по имени Роджер, которой восемь лет.

Иными словами, класс — это форма или шаблон. Он определяет необходимую информацию. После того, как вы заполните форму, ваша конкретная копия становится экземпляром класса; он содержит актуальную информацию, относящуюся к вам.

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

Как описать класс в Python

Вот простое описание класса в Python:

class Dog:
    pass

Вы начинаете с ключевого слова class, которым указываете, что создаете класс, а затем добавляете имя класса Dog (используя CamelCase, начинающееся с заглавной буквы).

Также мы использовали здесь ключевое слово Python pass, которое очень часто используется в качестве заполнителя, заглушки, где в конечном итоге код будет изменён и продолжен. Такая запись позволяет запускать этот код без выдачи сообщения об ошибке.

ПРИМЕЧАНИЕ:

Приведенный выше код корректен для Python 3. В Python 2.x («устаревший Python») вы использовали бы немного другое определение класса:

# Python 2.x Class Definition:
class Dog(object):
    pass

(object) в скобках указывает родительский класс, от которого вы наследуете свой класс (подробнее об этом ниже). В Python 3 это больше не требуется, поскольку это неявное значение по умолчанию.

Атрибуты экземпляра

Все классы создают объекты и все объекты содержат характеристики, называемые атрибутами (или свойствами в первом абзаце). Используйте метод __init__() инициализации (например, указать) начальных значений атрибутов объекта по умолчанию (или состояние). Этот метод должен иметь как минимум один аргумент, а также переменную self, которая ссылается на сам объект (например, Dog).

class Dog:
    # Атрибуты Инициализатора / Экземпляра
    def __init__(self, name, age):
        self.name = name
        self.age = age

В случае нашего класса Dog() каждая собака имеет определенное имя и возраст, что, безусловно, важно знать, когда вы начинаете создавать разных собак. Помните: класс предназначен только для определения собаки, а не для создания экземпляров отдельных собак с конкретными именами и возрастами; мы скоро к этому вернемся.

Точно так же переменная self является экземпляром класса. Поскольку экземпляры класса имеют различные значения, мы можем указать Dog.name = name, а не self.name = name. Но поскольку не все собаки имеют одно и то же имя, мы должны иметь возможность назначать разные значения для разных экземпляров. Отсюда необходимость специальной переменной self, которая поможет отслеживать отдельные экземпляры каждого класса.

ПРИМЕЧАНИЕ:

Вам никогда не придется вызывать метод __init__(); он вызывается автоматически при создании нового экземпляра Dog.

Атрибуты класса

Хотя атрибуты экземпляра являются специфическими для каждого объекта, атрибуты класса одинаковы для всех экземпляров — в данном случае это все собаки.

class Dog:
    # Атрибуты класса
    species = 'mammal'

    # Атрибуты Инициализатора / Экземпляра
    def __init__(self, name, age):
        self.name = name
        self.age = age

Таким образом, хотя каждая собака имеет уникальное имя и возраст, каждая собака будет млекопитающим.

Давайте создадим несколько собак …

Создание объектов

Instantiating — необычный термин для создания нового уникального экземпляра класса.

Например:

>>> class Dog:
...     pass
...
>>> Dog()
<__main__.Dog object at 0x1004ccc50>
>>> Dog()
<__main__.Dog object at 0x1004ccc90>
>>> a = Dog()
>>> b = Dog()
>>> a == b
False

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

Как вы думаете, тип экземпляра класса?

>>> class Dog:
...     pass
...
>>> a = Dog()
>>> type(a)
<class '__main__.Dog'>

Давайте посмотрим на пример немного посложнее…

class Dog:

    # Атрибуты класса
    species = 'mammal'

    # Атрибуты инициализатора/экземпляра
    def __init__(self, name, age):
        self.name = name
        self.age = age


# Instantiate the Dog object
philo = Dog("Philo", 5)
mikey = Dog("Mikey", 6)

# Access the instance attributes
print("{} is {} and {} is {}.".format(
    philo.name, philo.age, mikey.name, mikey.age))

# Is Philo a mammal?
if philo.species == "mammal":
    print("{0} is a {1}!".format(philo.name, philo.species))

ПРИМЕЧАНИЕ:

Обратите внимание, как мы используем точечную запись для доступа к каждому атрибуту объекта.

Сохраните файл dog_class.py после чего запустите программу. Посмотрите:

Philo is 5 and Mikey is 6.
Philo is a mammal!

Что тут происходит?

Мы создали новый экземпляр класса Dog() и присвоили его переменной philo. Затем мы передали ему два аргумента: "Philo" и 5, которые представляют имя и возраст этой собаки соответственно.

Эти атрибуты передаются методу __init__, который вызывается каждый раз, когда вы создаете новый экземпляр, прикрепляя имя и возраст к объекту. Вам может быть интересно, почему нам не пришлось передавать аргумент self.

Это магия Питона; когда вы создаете новый экземпляр класса, Python автоматически определяет, что такое self (в данном случае это Dog) и передает его методу __init__.

Упражнения (#1)

УПРАЖНЕНИЕ: «Самая старая собака»
РЕШЕНИЕ: «Самая старая собака»

Методы экземпляра

Методы экземпляра определены внутри класса и используются для получения содержимого экземпляра. Они также могут быть использованы для выполнения операций с атрибутами наших объектов. Как и метод __init__, где первым аргументом всегда является self:

class Dog:

    # Атрибуты класса
    species = 'mammal'

    # Атрибуты Инициализатора / Экземпляра
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # Метод класса
    def description(self):
        return "{} is {} years old".format(self.name, self.age)

    # Метод класса
    def speak(self, sound):
        return "{} says {}".format(self.name, sound)

# Создаем объект (экземпляр класса) Dog
mikey = Dog("Mikey", 6)

# Вызов методов класса
print(mikey.description())
print(mikey.speak("Gruff Gruff"))

Сохраните файл dog_instance_methods.py и запустите его:

Mikey is 6 years old
Mikey says Gruff Gruff

В последнем методе, speak(), мы определяем поведение. Какие другие виды поведения вы можете назначить собаке? Вернитесь к началу абзаца, чтобы увидеть примеры поведения других объектов.

Изменение атрибутов

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

>>> class Email:
...     def __init__(self):
...         self.is_sent = False
...     def send_email(self):
...         self.is_sent = True
...
>>> my_email = Email()
>>> my_email.is_sent
False
>>> my_email.send_email()
>>> my_email.is_sent
True

Здесь мы добавили метод для отправки электронного письма, который обновляет переменную is_sent до значения True.

Наследование объектов Python

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

Важно отметить, что переопределенные дочерние классы или расширяют функциональность (например, атрибуты и поведение) родительских классов. Другими словами, дочерние классы наследуют все атрибуты и поведение родителя, но могут также определять другое поведение, которому нужно следовать. Самый базовый тип класса — это object, который, как правило, все остальные классы наследуют как родительский.

Когда вы определяете новый класс, Python 3 неявно использует object в качестве родительского класса. Таким образом, следующие два определения эквивалентны:

class Dog(object):
    pass

# In Python 3, this is the same as:
class Dog:
    pass

ПРИМЕЧАНИЕ:

В Python 2.x существует различие между новый стиль и старый стиль классы. Здесь я не буду вдаваться в подробности, но вы, как правило, захотите указать object в качестве родительского класса, чтобы убедиться, что вы определили класс нового стиля, если вы пишете код Python 2.

Пример собачего парка

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

Как еще можно отличить одну собаку от другой? Как насчет породы собаки:

>>> class Dog:
...     def __init__(self, breed):
...         self.breed = breed
...
>>> spencer = Dog("German Shepard")
>>> spencer.breed
'German Shepard'
>>> sara = Dog("Boston Terrier")
>>> sara.breed
'Boston Terrier'

У каждой породы собак есть немного отличающиеся поведения. Чтобы принять это во внимание, давайте создадим отдельные классы для каждой породы. Это дочерние классы родительского класса Dog.

Расширение функциональности родительского класса

Создайте новый файл с именем dog_inheritance.py:

# Родительский класс
class Dog:

    # Атрибуты класса
    species = 'mammal'

    # Атрибуты инициализатора/экземпляра
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # Метод класса
    def description(self):
        return "{} is {} years old".format(self.name, self.age)

    # Метод класса
    def speak(self, sound):
        return "{} says {}".format(self.name, sound)


# Дочерний класс (наследникз класса Dog)
class RussellTerrier(Dog):
    def run(self, speed):
        return "{} runs {}".format(self.name, speed)


# Дочерний класс (наследникз класса Dog)
class Bulldog(Dog):
    def run(self, speed):
        return "{} runs {}".format(self.name, speed)


# Дочерние классы наследуют атрибуты и
# поведения из родительского класса
jim = Bulldog("Jim", 12)
print(jim.description())

# Дочерние классы имеют определенные атрибуты,
# а также и поведение
print(jim.run("slowly"))

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

Вы увидите:

Jim is 12 years old
Jim runs slowly

Мы не добавили никаких специальных атрибутов или методов, чтобы отличить RussellTerrier от Bulldog, но, поскольку они теперь представляют собой два разных класса, мы могли бы, например, дать им другие атрибуты класса, определяющие их соответствующие скорости.

Родительские и дочерние классы

Функция isinstance() используется для определения, является ли экземпляр также экземпляром определенного родительского класса.

Сохраните файл dog_isinstance.py:

# Родительский класс
class Dog:

    # Атрибуты класса
    species = 'mammal'

    # Атрибуты Инициализатора / Экземпляра
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # Метод класса класса
    def description(self):
        return "{} is {} years old".format(self.name, self.age)

    # Метод класса класса
    def speak(self, sound):
        return "{} says {}".format(self.name, sound)


# Дочерний класс (наследникз класса Dog)
class RussellTerrier(Dog):
    def run(self, speed):
        return "{} runs {}".format(self.name, speed)


# Дочерний класс (наследникз класса Dog)
class Bulldog(Dog):
    def run(self, speed):
        return "{} runs {}".format(self.name, speed)


# Дочерние классы наследуют атрибуты и
# поведения из родительского класса
jim = Bulldog("Jim", 12)
print(jim.description())

# Дочерние классы имеют определенные атрибуты
# а такжн поведение
print(jim.run("slowly"))

# Является ли jim примером Dog()?
print(isinstance(jim, Dog))

# Является ли julie примером Dog()?
julie = Dog("Julie", 100)
print(isinstance(julie, Dog))

# Является ли johnny walker примером Bulldog()
johnnywalker = RussellTerrier("Johnny Walker", 4)
print(isinstance(johnnywalker, Bulldog))

# Является ли julie and instance of jim?
print(isinstance(julie, jim))

Результат:

('Jim', 12)
Jim runs slowly
True
True
False
Traceback (most recent call last):
  File "dog_isinstance.py", line 50, in <module>
    print(isinstance(julie, jim))
TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types

Есть смысл? И jim и julie являются экземплярами класса Dog(), а johnnywalker не является экземпляром класса Bulldog(). Затем в качестве проверки работоспособности мы проверили, является ли julie экземпляром jim, что невозможно, поскольку jim является экземпляром класса, а не самого класса — отсюда и причина TypeError.

Переопределение функциональности родительского класса

Помните, что дочерние классы также могут переопределять атрибуты и поведение родительского класса. Например:

>>> class Dog:
...     species = 'mammal'
...
>>> class SomeBreed(Dog):
...     pass
...
>>> class SomeOtherBreed(Dog):
...     species = 'reptile'
...
>>> frank = SomeBreed()
>>> frank.species
'mammal'
>>> beans = SomeOtherBreed()
>>> beans.species
'reptile'

Класс SomeBreed() наследует species от родительского класса, в то время как класс SomeOtherBreed() переопределяет species , установив его в reptile.

Упражнения (#2)

УПРАЖНЕНИЕ: «Наследники Dog»
РЕШЕНИЕ: «Наследники Dog»
УПРАЖНЕНИЕ: «Голодные собаки»
РЕШЕНИЕ: «Голодные собаки»
УПРАЖНЕНИЕ: «Выгул собак»
РЕШЕНИЕ: «Выгул собак»
УПРАЖНЕНИЕ: «Проверка знаний»
РЕШЕНИЕ: «Проверка знаний»

Заключение

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

Помните, что ООП — это парадигма программирования, а не концепт Python. Большинство современных языков программирования, таких как Java, C#, C++, следуют принципам ООП. Итак, хорошая новость заключается в том, что изучение основ объектно‑ориентированного программирования будет полезно для вас в различных обстоятельствах — независимо от того, работаете вы на Python или нет.

Опубликовано Вадим В. Костерин

ст. преп. кафедры ЦЭиИТ. Автор более 130 научных и учебно-методических работ. Лауреат ВДНХ (серебряная медаль).

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

Ваш адрес email не будет опубликован.