Объектно-ориентированное программирование (ООП) может быть сложным для тех, кто только начинает изучать 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)
Большое спасибо за внимание, вторая часть материала не заставит себя долго ждать.
Thanks for your blog, nice to read. Do not stop.