суббота, 12 декабря 2015 г.

Объектно-ориентированный стиль, классы

Основные понятия


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

Вспомним, что мы знаем про инкапсуляцию? Это, так называемое, сокрытие реализации, что означает то, что вам, при использовании каких-то объектов и их методов, ненужно знать, как реализованы процессы, происходящие внутри объекта при взаимодействии с ним его методами. Все, что вам необходимо знать - это его интерфейсы, Т.Е. Что и какому методу мы должны передать и что нам вернется. Нет необходимости знать, как реализован строчный метод "split", чтобы при его помощи разбить строку по какому-то разделителю. Все, что нам необходимо - помнить, что мы, в качестве аргумента метода, передаем символ, по которому мы хотим разбить строку, а метод возвратит нам объект списка, где каждым элементом, будет часть строки, получившаяся в результате разделения строки.
Полиморфизм позволяет к разным объектам, применять одни и те же операции, которые дают нам результат в зависимости от того, к какому объекту мы ее применяем. Если мы сложим два числа через оператор "+", мы получим результат суммы этих чисел, если же мы применим оператор "+" к строкам, мы получим строку из соединенных через "+" строк(конкатенация строк). Полиморфизм позволяет нам, в ряде случаев, обрабатывать строки, кортежи и списки похожим образом. Мы можем узнать разменость этих объектов при помощи функции "len", можем воспользоваться индексацией и срезом, можем проитерировать каждый из этих объектов. Все это признаки полиморфизма, которым Python пропитан на сквозь, что делает его феноменально гибким в разработке инструментом. ПОмним утверждение про то, что не действие определяет результат, а объект определяет результат действия над ним.

Полиморфизм и инкапсуляция - это две из оставляющих объектно-ориентированной парадигмы программирования. Также мы познакомимся с такими вещами, как наследование, абстракция и композиция.
Для более глубокого понимания объектно-ориентированного стиля программирования, подходов и концепций, вам необходимо обратиться к соответствующей литературе, такой, как книга "объектно-ориентированное мышление", которая указана в разделе для дополнительного чтения, мы же сосредоточимся на непосредственной реализации объектно-ориентированного программирования, далее ооп, в Python.

Объявление и описание класса


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

Описание класса начинается с инструкции class, за которым следует название класса. Для начала, создадим класс, который будет содержать одну переменную(аттрибут):

class MyClass:
    attr = "It is my first class!"


По общепринятым соглашениям, название класса всегда начинается с большой буквы, часто составные слова записывают с больших букв, как в данном примере. Тело класса, как и любое тело любых конструкций, описывается с отступом по отношению к объявлению класса. Чтобы упростить понимание класса, можно утверждать, что каждый класс - это вложенное в глобальное, пространство имен. Насамом деле, актуально то, что каждый создаваемый объект, на базе описываемого класса, определяет свое пространство имен. Впринципе у "str", есть свое пространство имен, мы говорим о том, что это функция строчного типа данных, но она возвращает нам объект строки. Более важно то, что мы можем создать несколько строк при помощи этой функции и присвоить каждую строку какой-то переменной. Вот эти переменные и будут определять свое пространство имен для созданных объектов. Примерно также мы рассматривали пространства имен модулей, но если модули - это глобальные пространства, которые никак не касаются глобальной области видимости друг-друга, кроме точек, где мы импортируем один модуль в другой, то классы - это вложенные области видимости, которые позволяют породить объекты, которые будут иметь свои области видимости.
Теперь выполним наш код и обратимся к имени MyClass:

>>> MyClass
class __main__.MyClass at 0x01CD0D18
>>>


Это объект нашего класса. Да-да, классы - это тоже объекты. Помните, о чем мы говорили, что в Python все держится на именах, ссылках и объектах, на которые и ссылаются ссылки. Имя - это MyClass, которое имеет ссылку на объект нашего класса. Теперь создадим несколько объектов нашего класса:

>>> a = MyClass()
>>> b = MyClass()
>>> a
__main__.MyClass instance at 0x01DD4FD0
>>> b
__main__.MyClass instance at 0x01DD4FA8
>>>


Да, все привычно и просто, таким же образом мы создавали объекты встроенных типов данных. Наш объект при создании ничего не умеет получать, но в будущем мы это исправим. Вы помните, как импортируемый модуль содержит в себе объявленную переменную и мы можем обратиться к этой переменной? Таким же образом и созданные объекты позволяют нам обратиться к переменной "attr":

>>> a.attr
'It is my first class!'
>>> b.attr
'It is my first class!'
>>>


Аттрибут "attr", содержит в себе тот текст, который мы определили при описании класса. Мы можем спокойно изменить этот текст для каждого из объектов:

>>> a.attr = "it is first object"
>>> b.attr = "it is second object"
>>> a.attr
'it is first object'
>>> b.attr
'it is second object'
>>>


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

Методы и параметр "self"


Следующим важным шагом, следует объявление методов внутри класса. Опишем класс, который будет содержать в себе один аттрибут и 2 метода, один из которых позволит выводить на экран значение аттрибута, а второй присваивать новое значение аттрибуту. Это примерно те акссессоры, про которые мы говорили в теме про модули, но фактически, именно методы определяют способы взаимодействия с создаваемыми объектами. Также, как и с модулями, прямое обращение к переменным объектов, нежелательно.

class MyClass:
    string = None
    def prnt(self):
        print self.string
    def write(self, attr):
        self.string = attr


Этот код более чем прост, но я уверен, что вы споткнулись на неком "self", который появляется, как аргумент методов, а еще он появился перед обращением string в методах. Этот параметр выполняет такую же роль, как и параметр "this" в таких языках как c# и java. Его предназначение заключается в том, что он передает в метод ссылку на тот объект, для которого мы его вызываем. Мы можем утверждать, что при создании объектов, методы не размножаются, Т.Е. Созданный объект содержит только определенные данные(аттрибуты). Методы всегда остаются в пространстве имен класса, в котором они были описаны. Исходя из этого, мы можем логично предположить, что методы, которые находятся внутри класса, ничего не знают про созданые объекты до тех пор, пока мы не передадим им эту информацию. Именно эта информация и передается в аргумент "self". Само по себе слово не несет в себе какую-то магию и не является зарезервированным словом, мы можем использовать любое другое слово в качестве аргумента, но общепринятые условия требуют, чтобы мы пользовались именно этой формой записи, в противном случае, наш код станет гораздо запутаннее и более сложно сопровождаем. Если еще более упростить, представте, что в "self", автоматически передается имя того объекта, для которого вы вызываете какой-то метод.
Создадим несколько объектов на базе нашего класса:

>>> a = MyClass()
>>> b = MyClass()
>>> a
__main__.MyClass instance at 0x01E2B058
>>> b
__main__.MyClass instance at 0x01E2B080
>>> dir(a)
['__doc__', '__module__', 'prnt', 'string', 'write']
>>> dir(b)
['__doc__', '__module__', 'prnt', 'string', 'write']
>>> a.prnt()
None
>>> b.prnt()
None
>>> a.write(123)
>>> b.write("Hello")
>>> a.prnt()
123
>>> b.prnt()
Hello
>>>


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

>>> a = MyClass()
>>> a.prnt()
None
>>> MyClass.write(a, "Super")
>>> a.prnt()
Super
>>> MyClass.prnt(a)
Super
>>>


Был создан объект "a", но для присвоения значения аттрибуту этого объекта, я обратился к методу в классе "MyClass", куда руками, в качестве первого аргумента, передал ссылку на объект "a", Т.Е. Передал имя, которое содержит ссылку на объект. И все прекрасно заработало. Дальнейшие манипуляции, демонстрируют подобные обращения. Когда  же мы обращаемся к методу, вызывая его, как метод объекта, ссылка на этот объект передается в "self" автоматически.

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

string = "It is global variable"

class MyClass:
    string = "it is class variable"
    def prntglobal(self):
        print string
    def prntclass(self):
        print self.string
    def setglobal(self, s):
        global string
        string = s
    def setclass(self, s):
        self.string = s


Определены две переменные "string", одна в глобальной области видимости, вторая, как аттрибут класса и созданы соответствующие методы доступа для каждой из этих переменных:

>>> a = MyClass()
>>> string
'It is global variable'
>>> a.prntglobal()
It is global variable
>>> a.prntclass()
it is class variable
>>> a.setglobal("Test access to global variable")
>>> string
'Test access to global variable'
>>> a.prntglobal()
Test access to global variable
>>> a.prntclass()
it is class variable
>>> a.setclass("Test access to class variable")
>>> a.prntclass()
Test access to class variable
>>> a.prntglobal()
It is global variable
>>> string
'It is global variable'
>>>


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

Комментариев нет:

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