Объектно-ориентированное программирование на Python

Python – это новый язык программирования. Он был разработан в 1991 году программистом Гвидо ванн Россумом. В отличие от многих других языков, в Python все сущности являются объектами. Также язык имеет динамическую типизацию и автоматическое управление памятью. Авторы языка старались сделать его максимально удобным для разработчиков, поэтому синтаксис языка довольно минимальный. К недостаткам Python можно отнести низкую скорость работы и большое потребление памяти в отличие от компилируемых языков.

Сегодня Python занимает 3 место в рейтинге TIOBE среди самых популярных языков мира. Его используют в разных сферах: от машинного обучения и Big data до разработки игр и приложений. Благодаря простому синтаксису язык используют в процессе обучения программированию. Python позволяет сосредоточиться на изучении алгоритмов и принципов программирования, а не на изучении синтаксиса.

Принципы объектно-ориентированного программирования

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

Принципы ООП, реализованные в Python:

  • Всё, что есть в языке – это объекты, даже простые числа представлены в виде объектов. Объект имеет определённый тип, по-другому – класс, реализующий логику объекта.
  • Помимо объектов, в ООП есть ещё и классы. Они обозначаются зарезервированным словом – class и представляют собой абстрактный набор методов и полей.
  • Внутри каждого объекта находиться слоя логика, которая позволяет как-то общаться с этим объектом. Такой принцип называется инкапсуляция. В рамках инкапсуляции программист может ограничить доступ к логике объекта.
  • Все объекты, созданные программистом, должны общаться между собой.
  • Объект может включать в себе другой объект, представленный в виде поля.
  • Одна из базовых концепций ООП – наследование. Оно позволяет устанавливать отношение родитель-потомок между объектами.
  • Полиморфизм – вторая концепция ООП – позволяет объекту самому определять реализацию методов.
  • Инкапсуляция – ограничивает доступ к данным.

Основы ООП

Чтобы успешно писать код на Python придётся разобраться с концепцией объектно-ориентированного программирования.

Классы

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

classMyClass(object):

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

Пример класса с наследованием:

classMyClass(parent1, parent2):

Каждый класс имеет свойства и методы. Они задаются после «:». Пример класса со свойствами:

classMyClass(object):
x = 5
str = ‘Hello’

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

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

classMyClass(object):
defMyMethod(self, x):

После «:» указывается код метода.

Объекты класса

Объект класса (экземпляр) – это конкретная реализация класса. В Python работать с экземплярами довольно просто. Пример:

classSomeClass(object):
defSomeMethod(self, x):
returnx*x

Для того, чтобы создать объект этого класса, нужно:

someObj = SomeClass()
y = someObj.SomeMethod(6)
print(y)

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

Чтобы создать объект, нужно создать переменную и присвоить ей имя класса. Далее проиллюстрирован механизм доступа к членам объекта при присваивании значения переменной y. С помощью точки можно не только вызывать методы и функции, но и обращаться к полям объекта.

В Python есть возможность создать объект с некоторым набором параметров. Пример:

class Point(object):
Def __init__(self, x, y, z):
self.coord(x, y, z)
p1 = Point(13, 14, 15)

В этом примере мы создали класс Point, который будет хранить в себе 3 координаты. Затем, создали экземпляр этого класс p1. Этот экземпляр представляет конкретную точку с координатами 13, 14 и 15. Обратиться к этим координатам можно так: p1.coord

Динамический изменяемые классы

В отличии от многих других языков, где класс – это неизменяемая структура, в Python программист может добавлять методы в класс прямо во время его работы. Для этого используется зарезервированное слово pass. Вот так создаётся динамический класс:

classSomeClass(object) :pass

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

classSomeClass(object) :
pass
def method(self, x) :
return x*x
SomeClass.square = method
someObj = SomeClass
someObj.square(5)

Результат работы программы: 25. В этом примере мы дополняем класс новым методом, который будет доступен из любых объектов.

Статические и классовые методы

Статические методы используются во многих языках программирования. Внешне они похожи на другие методы, но с одним отличием. Вызывать статические методы можно как из класса, так и из его экземпляра. Для того чтобы их создать, нужно воспользоваться специальным декоратором: @staticmethod. Пример:

classSomeClass(object) :
@staticmethod
def Hello() :
print(‘Hello’)
SomeClass.Hello() #В результате вызовется метод Hello класса SomeClass
someObj = SomeClass()
someObj.Hello() #В этой строке мы обращаемся к методу Hello, но не класса, а экземпляра

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

Ещё существуют классовые методы, которые выполняются в контексте класса. Чтобы создать классовый метод, нужно указать декоратор @classmethod. Важной особенностью таких методов является обязательный параметр-ссылка на класс – cls. Пример:

classSomeClass(object) :
@classmethod
def Hello(cls) :
print(‘Hello, класс{}’.format(cls.__name__))
SomeClass.Hello()

В результате на кран выведется «Hello, класс SomeClass». Программистам-новичкам не всегда бывает понятно применение классовых методов. Самый яркий пример использования – фабрика объектов.

Объекты и их методы

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

Жизнь объекта

Жизненный цикл объекта включат 3 этапа — создание, работа и удаление их из памяти.Всеми этапами программист может управлять. Например, от разработчика зависит, когда объект будет проинициализирован. Для этого можно использовать метод __init__ либо __new__

Пример использования инициализатора __init__ и __new__:

classSomeClass(object):
def __new__(cls):
print(‘new’)
return super(SomeClass, cls).__new__(cls)
def __init__(self):
print(‘init’)
obj = SomeClass();

В результате работы программы выведется new init.

На первый взгляд,__new__ может показаться бесполезным, но это не так. Метод полезен, например, при реализации паттерна «Одиночка». Пример реализации:

classSingleton(object):
obj = None # единственный экземпляр класса
def __new__(cls, *args, **kwargs):
if cls.obj is None:
cls.obj = object.__new__(cls, *args, **kwargs)
return cls.obj
single = Singleton()
single.attr = 42
newSingle = Singleton()
newSingle.attr # 42
newSingleissingle # true

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

classSomeClass(object):
def __init__(self, name):
self.name = name
def __del__(self):
print(‘удаляется объект {} классаSomeClass’.format(self.name))
obj = SomeClass(«John»);
delobj

Результат работы программы: удаляется объект John класса SomeClass. Однако, часто использовать деструкторы – не очень хорошо. Python сам определяет ненужные объекты и удаляет их.

Объект в виде функции

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

class Multiplier:
def __call__(self, x, y):
return x*y
multiply = Multiplier()
multiply(19, 19) # 361
multiply.__call__(19, 19) # то же самое, что и в предыдущей строке

Имитация контейнера

Многие знакомы с функцией len(). Она позволяет узнать длину списка из каких-то объектов, но это работает только для просты типов. Пример:

class Collection:
def __init__(self, list):
self.list = list
collection = Collection(list)
len(collection)#в этой строке возникнет ошибка «Object of type has no len()»

Это означает, что интерпретатор Python просто не понимает, как ему считать длину. Для решения этой проблемы, был придуман метод __len__. Пример без ошибки:

class Collection:
def __init__(self, list):
self.list = list
def __len__(self):
returnlen(self.list)
collection = Collection([1, 2, 3])
len(collection) # 3

Другие специальные методы

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

Проблема с доступом к атрибутам

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

Для того чтобы разобраться с этим вопросом, рассмотрим запрос obj.field.

  1. В начале, компилятор вызывает специальный метод, пусть в нашем случае это будет __getattribute__(field). В качестве параметра он принимает field
  2. Затем, компилятор читает все пользовательские атрибуты, которые записаны в __dict__ .
  3. Если второй шаг не дал результата, то компилятор пытается найти атрибут в obj.__class__.__slots__
  4. С помощью рекурсии атрибут ищется у родительских классов в поле __dict__. Если объект имеет не одного родителя, то порядок поиска соответствующего атрибута такой же, как и порядок определения родительских классов.
  5. Вызывается метод __getattr__, если он определён.
  6. Если предыдущие шаги не дали никакого результата, то компилятор выбрасывает исключение: AttributeError, которое говорит он том, что нужного атрибута нет.

Если атрибут всё-таки был найден, то вызывается метод __get__, __set__ или __del__.

Принципы ООП в Python

Существует 3 основных концепции работы с ООП: инкапсуляция, наследование и полиморфизм. В разных языках они реализуются по-разному.

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

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

classSomeClass(object) :
def _Hello(sefl) : #Перед именем метода стоить «_», то есть метод приватный
print(‘Hello’)
obj = SomeClass()
obj._Hello()#Метод на самом деле не приватный

В данном случае мы можем вызвать метод Hello у объекта, так как на самом деле он не является приватным. Ограничение доступа происходит лишь на уровне соглашения между разработчиками. Если другой программист Python увидит метод, название которого начинается с нижнего подчёркивания, то он будет знать, что этот метод приватный и вызывать его не стоит.

Существует возможность полностью ограничить доступ к методу. Делается это с помощью двух нижних подчёркиваний. Пример:

classSomeClass(object) :
def __Hello(sefl) :
print(‘Hello’)
obj = SomeClass()
obj.__Hello() #метод недоступен

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

Пример работы со специальными методами:

classSomeClass():
def __init__(self, value):
self._value = value
defgetvalue(self): # получение значения атрибута
returnself._value
defsetvalue(self, value): # установка значения атрибута
self._value = value
defdelvalue(self): # удаление атрибута
delself._value
value = property(getvalue, setvalue, delvalue, «Свойство value»)

В данном примере показаны три специальных метода: setvalue–используется для присваивания значения свойству value, getvalue–используется для чтения и delvalue–деструктор, которые удаляет свойство.

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

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

Пример одиночного наследования:

classMammal():
className = ‘Mammal’
class Dog(Mammal):
species = ‘Canis lupus’
dog = Dog()
dog.className#Поле className доступно

В данном примере родительским классом является класс – Mammal, а дочерним – Dog. При этом из объекта dogмы можем вызвать поля и методы родительского класса.

Помимо одиночного, Python поддерживает и множественное наследование. Пример:

class Horse():
isHorse = True
class Donkey():
isDonkey = True
class Mule(Horse, Donkey):
mule = Mule()
mule.isHorse #Поля доступны
mule.isDonkey

Механизм наследования необходим для разделения логики между классами и повторного её использования. Например, есть класс Person, содержащий поле Age и метод Sleep. Также есть класс Pety, в котором доступны поля из Person, но Pety содержит ещё метод Work. Суть такого разделения заключена в том, что каждый человек может спать, но не каждый может работать. Петя может и спать, и работать.

Ассоциация

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

Пример композиции:

classSalary:
def __init__(self,pay):
self.pay = pay
defgetTotal(self):
return (self.pay*12)
class Employee:
def __init__(self,pay,bonus):
self.pay = pay
self.bonus = bonus
self.salary = Salary(self.pay)
defannualSalary(self):
return «Total: » + str(self.salary.getTotal() + self.bonus)
employee = Employee(100,10)
print(employee.annualSalary())

Пример агрегации:

class Salary(object):
def __init__(self, pay):
self.pay = pay
defgetTotal(self):
return (self.pay * 12)
class Employee(object):
def __init__(self, pay, bonus):
self.pay = pay
self.bonus = bonus
defannualSalary(self):
return «Total: » + str(self.pay.getTotal() + self.bonus)
salary = Salary(100)
employee = Employee(salary, 10)
print(employee.annualSalary())

Полиморфизм

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

classMammal:
defmove(self):
print(‘Двигается’)
class Hare(Mammal):
def move(self):
print(‘Прыгает’)
animal = Mammal()
animal.move() # Здесь вызывается метод move, класса Mammal
hare = Hare()
hare.move() # А здесь уже вызывается метод класса Hare

Как видно из примера, дочерний класс Hareпереопределил метод move, то есть добавил в него свою логику.
Чтобы получить доступ к методу базового класса, можно использовать зарезервированное слово super.
Пример с доступом к методу базового класса:

class Parent():
def __init__(self):
print(‘Parent init’)
def method1(self):
print(‘Parent method’)
class Child(Parent):
def __init__(self):
Parent.__init__(self)
def method1(self):
super(Child, self).method()
child = Child()
child.method1()

Несмотря на то, что в дочернем классе переопределён method1, вызовется всё равно метод базового класса.
Подобные способы – не единственные варианты реализации полиморфизма. В Python есть так называемая утиная типизация. Она позволяет классам иметь одинаковый интерфейс, но с разной реализацией. Под интерфейсом здесь понимается одинаковое название методов и полей.

Пример:

class English:
def greeting(self):
print («Hello»)
class French:
def greeting(self):
print («Bonjour»)

def intro(language):
language.greeting()
john = English()
gerard = French()
intro(john) # Hello
intro(gerard) # Bonjour

В данном примере реализованы два класса English и French. Оба они содержат метод с одинаковым названием. Также в самой программе прописан метод intro, который в качестве параметра принимает объект language. Не зависимо от типа объекта, который мы передали вintro, у переданного параметра будет вызван метод greeting. Главное, чтобы объект language содержал в себе реализацию метода с таким названием.

Множественная диспетчеризация

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

Множественная диспетчеризация доступна в Python благодаря мультиметодам. К сожалению, разработчики не добавили поддержку мультиметодов в язык, но существует большое количество библиотек, которые позволяют их использовать. Одна из таких библиотек – multimethods.py.

Метаклассы

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

Пример работы с метаклассами:

classMetaClass(type):
# выделение памяти для класса
def __new__(cls, name, bases, dict):
print(«Создание нового класса {}».format(name))
returntype.__new__(cls, name, bases, dict)
# инициализация класса
def __init__(cls, name, bases, dict):
print(«Инициализация нового класса {}».format(name))
return super(MetaClass, cls).__init__(name, bases, dict)
# порождение класса на основе метакласса
SomeClass = MetaClass(«SomeClass», (), {})
# обычное наследование
class Child(SomeClass):
def __init__(self, param):
print(param)
# получение экземплярак ласса
obj = Child(«Hello»)

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

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *