Python: классы и объектно-ориентированное программирование

Автор: | 07/09/2015
 

Python

Введение

Прежде, чем говорить о классах – было бы хорошо понимать основы пространства имён в Python и основы жизненного цикла данных в нём.

Имена

Для начала – поговорим об именах.

Имя – это просто идентификатор, который указывает на какой-то объект в памяти.

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

>>> i = 1
>>> id(i)
35049816
>>> id(1)
35049816

Имена могут указывать и на функции, которые так же являются объектами (как и всё в Python):

>>> def a():
...   print 'a'
...
>>> i = a()
a
>>> id(a())
a
7881056
>>> id(i)
7881056

Пространства имён

Теперь рассмотрим понятие пространства имён (или namespace).

Грубо говоря, пространство имён – это не более чем коллекция нескольких имён (или “указателей” на объекты).  Таких коллекций одновременно может существовать несколько, и каждая из них будет независима друг от друга. По сути – когда вы запускаете интерпретатор, и создаёте в нём функцию – вы уже используете как минимум два таких пространства, область видимости встроенных имён, которая создаётся при запуске интерпретатора. Именно поэтому нам сразу же доступны для использования встроенные переменные типа True или False и встроенные функции, таки как id(), print() и т.д. В глобальной области видимости – вы можете создавать свои объекты, такие как функции. А внутри функций – будет располагаться локальная область видимости самой функции.

Пространство имён хорошо проиллюстрировано на этой картинке:

python_namespace

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

>>> def a():
...   a = 1
...   print a
...
>>> def b():
...   a = 2
...   print a
...
>>> a()
1
>>> b()
2

Области видимости

Области видимости Python – ещё одно необходимое условие для понимая работы классов и принципов ООП.

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

  • область видимости текущей функции, которая содержит её локальные  переменные;
  • область видимости модуля, который содержит глобальные имена (такие как имена функций в нём);
  • внешняя область видимости с встроенными именами.

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

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

Лучше всего об этом рассказано в книге Марк Лутц – “Изучаем Python”, глава 16.

Создание класса

Если функция создаётся с помощью ключевого слова def – то класс создаётся с помощью ключевого слова class.

Вот пример простейшего класса:

>>> class ThisIsClass:
...   pass
  • class – указание на то, что далее последует объявление класса;
  • ThisIsClass – имя класса;
  • pass – оператор в классе.

Следует запомнить, что объект (или инстанс – instance) класса, и экземпляр класса – это две разные сущности.

Объект класса создаётся во время его объявления (грубо говоря – когда интерпретатор встречает слово class в коде).

Экземпляр класса – отдельный, новый объект класса, который создаётся во время присваивания класса какой-то переменной и вызова с оператором ().

Например:

>>> id(ThisIsClass)    // это объект класса
140249307055928
>>> cl = ThisIsClass() // экземпляр класса, новый объект в памяти
>>> id(cl)
140249307072992
>>> cl = ThisIsClass   // указатель на объект класса, но не экземпляр
>>> id(cl)
140249307055928

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

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

Каждый класс может содержать свой набор атрибутов (переменных, определённых в классе).

Например:

>>> class ThisIsClass:
...   attribute = 'This is attribute value'

Для получения доступа к атрибутам класса – используйте точку в качестве разделителя:

>>> ThisIsClass.attribute
'This is attribute value'
>>> cl = ThisIsClass()
>>> cl.attribute
'This is attribute value'

Главная идея в использовании классов, и вообще ООП – это многократное использование одного и того же кода.

Класс в ООП – является как бы конструктором, из которого вы можете далее создавать множество экземпляров:

>>> a = ThisIsClass()
>>> b = ThisIsClass()
>>> c = ThisIsClass()
>>> id(a)
140249307072992
>>> id(b)
140249307114528
>>> id(c)
140249307114600
>>> a.attribute
'This is attribute value'
>>> b.attribute
'This is attribute value'
>>> c.attribute
'This is attribute value'

Каждый из этих экземпляров имеет свою область видимости и собственное пространство имён.

По умолчанию – все атрибуты класса доступны “снаружи”, т.е. – из других областей видимости.

Что бы создать т.н. “приватный” атрибут – используйте символ одинарного или двойного подчёркивания:

>>> class ThisIsClass:
...   __attribute = 'This is attribute value'
...
>>> cl = ThisIsClass
>>> cl.attribute
...
AttributeError: ThisIsClass instance has no attribute 'attribute'
>>> cl.__attribute
...
AttributeError: ThisIsClass instance has no attribute '__attribute'

Мы рассмотрим “приватные” аргументы и методы далее, в Инкапсуляции.

Методы классов

Каждый класс может содержать несколько методов класса.

По сути – метод, это обычная функция, например:

>>> class ThisIsClass:
...   def method(self):
...     print 'This is class method'
...
>>> cl = ThisIsClass()
>>> cl.method()
This is class method

Методы могут принимать любые другие аргументы:

>>> class ThisIsClass:
...   def method(self, a, b):
...     print (a, b)
...
>>> cl = ThisIsClass()
>>> cl.method(1, 2)
(1, 2)

self

Тут мы встречаемся со специальным аргументом метода – self.

Его имя – необязательно, но общепринято использовать именно такое имя. Оно так же требуется для получения доступа к атрибутам класса.

self указывает на экземпляр класса, из которого вызывается метод или атрибут:

>>> class ThisIsClass():
...   def method(self):
...     print id(self)
...
>>> cl = ThisIsClass()
>>> cl.method()
140249307072920
>>> id(cl)
140249307072920

Кроме того, self потребуется для доступа к атрибутам класса:

>>> class ThisIsClass:
...   a = 1
...   b = 2
...   def method(self):
...     print self.a, self.b
...
>>> cl = ThisIsClass()
>>> cl.method()
1 2
>>> class ThisIsClass:
...   a = 1
...   b = 2
...   def method(self):
...     print a, b
...
>>> cl = ThisIsClass()
>>> cl.method()
__main__.ThisIsClass __main__.ThisIsClass

Свойства классов

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

До сих пор вы создавали класс так:

class ThisIsClass:

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

Для того, что бы указать, что у класса есть родительских класс – он указывается в скобках:

>>> class ParentClass:
...   parentclassattribute = 'parentclassattribute'
...
>>> class InheritingClass(ParentClass):
...   def method(self):
...     print self.parentclassattribute
...
>>> cl = InheritingClass()
>>> cl.method()
parentclassattribute

В этом примере InheritingClass наследует значение атрибута parentclassattribute  из родительского – ParentClass класса.

Тоже касается и методов классов:

>>> class ParentClass:
...   def paerntmethod(self):
...     print ('This is parent')
...
>>> class InheritingClass(ParentClass):
...   pass
...
>>> cl = InheritingClass()
>>> cl.paerntmethod()
This is parent

Дочерний класс InheritingClass использует метод paerntmethod родительского класса.

Кроме того, дочерний класс может переопределить метод (или атрибут) родительского класса:

>>> class ParentClass:
...   classattribute = 'classattribute'
...   def method(self):
...     print 'This is parent method'
>>> class InheritingClass(ParentClass):
...   classattribute = 'Inherited class attribute'
...   def method(self):
...     print ('This is child')
...
>>> cl = InheritingClass()
>>> cl.classattribute
'Inherited class attribute'
>>> cl.method()
This is child

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

Допустимо также множественное наследование:

>>> class FirstParentClass:
...   f_attr = 1
...
>>> class SecondParentClass:
...   s_attr = 2
...
>>> class InheritingClass(FirstParentClass, SecondParentClass):
...   def method(self):
...     print self.f_attr, self.s_attr
...
>>> cl = InheritingClass()
>>> cl.method()
1 2

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

Мы уже кратко касались этой темы выше. Инкапсуляция – это скрытие каких-то данных – атрибутов или методов – от внешней области видимости.

Например – публичный атрибут:

>>> class ThisIsClass:
...   public__attr = 'public_attr'
...
>>> cl = ThisIsClass()
>>> cl.public__attr
'public_attr'

И – скрытый, “инкапсулированный”, атрибут:

>>> class ThisIsClass:
...   __privat_attr = 'privat_attr'
...
>>> cl = ThisIsClass
>>> cl.__privat_attr
...
AttributeError: class ThisIsClass has no attribute '__privat_attr'

Пример с обычным публичным методом класса:

>>> class ThisIsClass:
...   def pub_method(self):
...     print 'This is public method'
...
>>> cl = ThisIsClass
>>> cl = ThisIsClass()
>>> cl.pub_method()
This is public method

И инкапсулированным:

>>> class ThisIsClass:
...   def __privat_method(self):
...     print 'This is privat method'
...
>>> cl = ThisIsClass()
>>> cl.__privat_method()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: ThisIsClass instance has no attribute '__privat_method'

При этом – эти атрибуты и методы будут доступны для использования внутри самого класса:

>>> class ThisIsClass:
...   def __privat_method(self):
...     return 'This is privat method'
...   def pub_method(self):
...     print self.__privat_method()
...
>>> cl = ThisIsClass()
>>> cl.pub_method()
This is privat method

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

>>> class ThisIsClass:
...   def __privat_method(self):
...     print 'This is privat method'
...
>>> cl = ThisIsClass()
>>> cl._ThisIsClass__privat_method()
This is privat method

Полиморфизм

Полиморфизм – возможность использовать одно и то же имя метода к разным объектам разных классов.

Например:

>>> class FirstClass:
...   def method(self, data):
...     return data + 10
...
>>> class SecondClass:
...   def method(self, data):
...     return data + 'string'
...
>>> cl1 = FirstClass()
>>> cl2 = SecondClass()
>>> cl1.method(10)
20
>>> cl2.method('string')
'stringstring'

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

Специальных методов классов в Python очень много, поэтому – рассмотрим только два основных.

Наиболее часто встречающийся – __init__.

Он является “конструктором класса“, и вызывается каждый раз при создании экземпляра класса.

Например:

>>> class ThisIsCLass:
...   def __init__(self):
...     print 'This is init method'
...
>>> cl = ThisIsCLass()
This is init method

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

>>> class ThisIsCLass:
...   def __init__(self, name):
...     self.name = name
...
>>> cl = ThisIsCLass('username')
>>> cl.name
'username'
>>> class ThisIsCLass:
...   def __init__(self, name):
...     self.name = name
...   def method(self):
...     print self.name
...
>>> cl = ThisIsCLass('username')
>>> cl.method()
username

Метод __doc__ – выводит строку документации (docstring) класса:

>>> class ThisIsCLass:
...   """This is doctring"""
...   pass
...
>>> cl = ThisIsCLass()
>>> cl.__doc__
'This is doctring'