Объектно-ориентированное программирование (ООП) может быть сложным для тех, кто только начинает изучать Python.

В этой статье мы будем продвигаться в освоении ООП в Python, постепенно.

Объекты языка Python

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

print(type(1))
print(type([]))
print(type(()))
print(type({}))

<class 'int'>
<class 'list'>
<class 'tuple'>
<class 'dict'>

Мы знаем, что эти элементы являются объектами. Как мы можем создавать свои собственные типы объектов? Для этого как раз пригодится ключевое слово class.

Классы

Объекты пользователя создаются с помощью ключевого слова class. Класс — это шаблон, описывающий будущий объект. Из классов мы создаем экземпляры (инстансы). Экземпляр — это конкретный объект, созданный на основе конкретного класса. Например, в примере выше мы создали объект lst, это экземпляр объекта list.

Посмотрим, как мы можем использовать class:

# Создаем новый тип объекта, под названием Sample
class Sample:
    pass

# Экземпляр класса Sample
x = Sample()

print(type(x))
<class '__main__.Sample'>

Согласно принятым соглашениям об именовании, имена классов начинаются с заглавной буквы. Обратите внимание, что x теперь ссылается на экземпляр нашего нового класса Sample.

Внутри класса у нас пока есть только слово pass. Но мы можем определить атрибуты и методы класса.

Атрибут — это характеристика объекта.

Метод — это операция, которую мы можем выполнять над объектом.

Например, мы можем создать класс под названием Dog — собака. Атрибут собаки это например её порода или её имя, а метод например может быть метод .bark() — лаять.

Рассмотрим атрибуты на примере.

Атрибуты

Синтаксис создания атрибута следующий:

self.attribute = something

Есть специальный метод, который называется так:

__init__()

Это метод используется для инициализации атрибутов объекта. Например:

class Dog:
    def __init__(self,breed):
        self.breed = breed
        
sam = Dog(breed='Lab')
frank = Dog(breed='Huskie')

Разберёмся, что здесь происходит. Специальный метод __init__() вызывается автоматически, сразу после создания объекта:

def __init__(self, breed):

Каждый атрибут в определении класса начинается со ссылки на экземпляр объекта. По соглашению об именовании, он называется self. Далее, breed это параметр. Значение передается при инициализации класса.

 self.breed = breed

Итак, мы создали два экземпляра класса Dog. У нас два разных типа породы — breed. Мы можем получить значения атрибутов вот так:

sam.breed
'Lab'
frank.breed
'Huskie'

Обратите внимание, что здесь нет скобок после breed; это потому, что атрибуты не принимают на вход никаких параметров.

В Python также есть атрибуты класса (class object attributes). Эти атрибуты одни и те же для всех экземпляров класса. Например, мы могли бы создать атрибут species (вид) для класса Dog. Собаки, вне зависимости от породы, имени и других атрибутов, всегда являются млекопитающими (mammal). Мы можем указать это следующим образом:

class Dog:
    
    # Class Object Attribute
    species = 'mammal'
    
    def __init__(self,breed,name):
        self.breed = breed
        self.name = name

sam = Dog('Lab','Sam')
sam.name
'Sam'

Обратите внимание, что атрибуты класса объекта определяются вне методов класса. По соглашению, мы помещаем их в начале, перед методом init.

sam.species
'mammal'

Методы

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

Методы можно представить себе как функции, которые работают с объектом, ссылаясь на этот объект с помощью параметра self.

Давайте рассмотрим пример создания класса Circle — круг:

class Circle:
    pi = 3.14

    # Circle инициализируется, используя радиус (по умолчанию 1)
    def __init__(self, radius=1):
        self.radius = radius 
        self.area = radius * radius * Circle.pi

    # Метод для указания радиуса
    def setRadius(self, new_radius):
        self.radius = new_radius
        self.area = new_radius * new_radius * self.pi

    # Метод для определения длины окружности
    def getCircumference(self):
        return self.radius * self.pi * 2


c = Circle()

print('Радиус: ',c.radius)
print('Площадь: ',c.area)
print('Длина окружности: ',c.getCircumference())
Радиус:  1
Площадь:  3.14
Длина окружности:  6.28

В методе __init__ выше, чтобы вычислить атрибут area, мы вызываем Circle.pi. Поскольку в объекте ещё нет своего атрибута .pi, мы вызываем атрибут класса объекта id.
Однако в методе setRadius мы уже работаем с существующим объектом класса Circle, в котором есть свой атрибут pi. Так что здесь мы можем использовать или Circle.pi, или self.pi.

Теперь давайте поменяем радиус и посмотрим, как это повлияет на наш объект Circle:

c.setRadius(2)

print('Радиус: ',c.radius)
print('Площадь: ',c.area)
print('Длина окружности: ',c.getCircumference())
Радиус:  2
Площадь:  12.56
Длина окружности:  12.56

Отлично! Обратите внимание, как мы использовали нотацию self., чтобы сослаться на атрибуты класса внутри методов. Изучите код выше, и попробуйте создать свой собственный метод.

Наследование (Inheritance)

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

В качестве примера давайте возьмём класс Dog и создадим наследование от класса Animal:

class Animal:
    def __init__(self):
        print("Animal created")

    def whoAmI(self):
        print("Animal")

    def eat(self):
        print("Eating")


class Dog(Animal):
    def __init__(self):
        Animal.__init__(self)
        print("Dog created")

    def whoAmI(self):
        print("Dog")

    def bark(self):
        print("Woof!")
d = Dog()
Animal created
Dog created
d.whoAmI()
Dog
d.eat()
Eating
d.bark()
Woof!

В этом примере у нас есть два класса: Animal и Dog. Animal является базовым классом, а Dog производным классом.

Производный класс наследует функциональность базового класса

  • это показано с помощью метода eat().

Производный класс меняет поведение базового класса.

  • показано с помощью метода whoAmI().

И наконец, производный класс расширяет функциональность базового класса, добавляя новый метод bark().

Полиморфизм (Polymorphism)

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

class Dog:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return self.name+' says Woof!'
    
class Cat:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return self.name+' says Meow!' 
    
niko = Dog('Niko')
felix = Cat('Felix')

print(niko.speak())
print(felix.speak())
Niko says Woof!
Felix says Meow!

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

Есть разные способы продемонстрировать полиморфизм. Прежде всего, с помощью цикла for:

for pet in [niko,felix]:
    print(pet.speak())
Niko says Woof!
Felix says Meow!

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

Более общей практикой является использование абстрактных классов и наследования. Абстрактный класс — это такой класс, для которого никогда не создаются экземпляры. Например, у нас никогда не будет объекта Animal, только объекты Dog и Cat, хотя оба эти класса наследуются от Animal:

def pet_speak(pet):
    print(pet.speak())

pet_speak(niko)
pet_speak(felix)
Niko says Woof!
Felix says Meow!

Примеры полиморфизма из реальной жизни:

  • открытие разных типов файлов — для отображения файлов Word, pdf и Excel нужны разные приложения
  • сложение разных объектов — оператор + выполняет и сложение чисел, и конкатенацию строк

Специальные методы

И наконец, давайте посмотрим специальные методы. Классы в Python могут выполнять определенные операции с помощью специальных методов. Эти методы вызываются не напрямую, а с помощью специального синтаксиса языка Python. Для примера давайте создадим класс Book:

class Book:
    def __init__(self, title, author, pages):
        print("Создаём книгу")
        self.title = title
        self.author = author
        self.pages = pages

    def __str__(self):
        return "Название: %s, Автор: %s, Кол-во страниц: %s" %(self.title, self.author, self.pages)

    def __len__(self):
        return self.pages

    def __del__(self):
        print("Книга удалена")
book = Book("Руководство по Python", "Вадик", 159)

#Special Methods
print(book)
print(len(book))
del book
Создаём книгу
Название: Руководство по Python, Автор: Вадик, Кол-во страниц: 159
159
Книга удалена
Методы __init__(), __str__(), __len__() and __del__() 

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

Инкапсуляция

Python предоставляет 3 уровня доступа к данным:

  • публичный (public, нет особого синтаксиса, publicBanana);
  • защищенный (protected, одно нижнее подчеркивание в начале названия, _protectedBanana);
  • приватный (private, два нижних подчеркивания в начала названия, __privateBanana).

Для краткости и простоты, только два базовых уровня (приватный и публичный) освещены в примере.

class Phone:
    username = "Kate"                # public variable
    __how_many_times_turned_on = 0   # private variable

    def call(self):                  # public method
        print( "Ring-ring!" )

    def __turn_on(self):             # private method
        self.__how_many_times_turned_on += 1 
        print( "Times was turned on: ", self.__how_many_times_turned_on )

my_phone = Phone()

my_phone.call()
print( "The username is ", my_phone.username )
# my_phone.turn_on()
# my_phone.__turn_on()
# print( “Turned on: “, my_phone.__how_many_times_turned_on)
# print( “Turned on: “, my_phone.how_many_times_turned_on)
# will produce an error
input( "Press Enter to exit" )

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

Свойства класса: геттеры и сеттеры.

Геттеры и сеттеры в Python отличаются от методов в других языках ООП. Основное использование методов получения и установки – обеспечение инкапсуляции данных в объектно-ориентированных программах. В отличие от других объектно-ориентированных языков, частные переменные в Python не являются скрытыми полями. Некоторые языки ООП используют методы получения и установки для инкапсуляции данных. Мы хотим скрыть атрибуты класса объекта от других классов, чтобы методы других классов случайно не изменили данные. В языках ООП геттеры и сеттеры используются для извлечения и обновления данных. Метод получения извлекает текущее значение атрибута объекта, тогда как средство установки изменяет значение атрибута объекта

В данном примере на Питоне показан базовый способ описания геттера и сеттера для свойства slug с помощью декораторов:

class Post(object):
    def __init__(self, id, title):
        self.id = id
        self.title = title
        self._slug = ''

    @property
    def slug(self):
        return self._slug

    @slug.setter
    def slug(self, value):
        self._slug = value

post = Post(1, 'Hello, World!')
post.slug = 'hello-world'
print(post.slug)

Большое спасибо за внимание, вторая часть материала не заставит себя долго ждать.

Группа ВК

Наше Телеграм