Журнал LinuxFormat - перейти на главную

LXF78:Python

Материал из Linuxformat
Перейти к: навигация, поиск

Часть 4. Мы вплотную подошли к наиболее интересной теме – реализации объектно-ориентированного подхода в языке Python. Репортаж с места событий ведет Сергей Супрунов.

Содержание

Немного терминологии

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

Новые объекты создаются на базе классов, которые являются своего рода шаблонами, описывающими общие характеристики объектов (также называемых экземплярами класса).

Для ООП характерны следующие принципы:

  • инкапсуляция: экземпляр класса рассматривается как «чёрный ящик», когда его внутреннее устройство скрыто, а всё взаимодействие с объектом выполняется через предусмотренный для этого интерфейс, т.е. набор методов. Нужно заметить, что в Python программист, в принципе, имеет возможность непосредственно воздействовать на свойства объекта, минуя интерфейс – здесь инкапсуляция реализована не на уровне ограничений языка, а на уровне соглашений.
  • наследование: вы можете создавать новые классы на базе существующих, при этом они будут получать все атрибуты и методы, описанные для родительских классов. Наследование – это наиболее естественный путь разработки классов, подобных родительским, но с дополнительным набором свойств.

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

Реализация ООП в Python

В отличие от Perl, в Python объектный подход заложен в основу этого языка. Здесь почти всё является объектами, даже строки и числа. Не верите? Смотрите сами:

 >>> print 1.0.__add__(5)
 6.0

То есть мы применили к числу 1.0 (объекту) метод __add__(), который является «внутренней» реализацией операции сложения. Нужно заметить, что здесь из-за ограничений синтаксиса мы вынуждены использовать число с плавающей запятой, поскольку первая точка воспринимается интерпретатором именно как разделитель целой и дробной частей, а вот вторая уже отделяет объект от имени метода. Уже известная нам функция dir(3) вернёт ещё 52 метода, которые вы можете применять к числу.

Для определения класса используется специальный оператор class:

#!/usr/bin/python
# file: сtest.py
class User:
   def __init__(self, username):
     self.username = username
     self.password = ''
   def setpass(self, value=''):
     self.password = value
   def checkpass(self, typed):
     if typed == self.password:
       return 1
     else:
       return 0

Здесь мы определили класс, экземпляры которого будут хранить информацию о пользователях (имя и пароль). Специальный метод __init__(), играющий роль конструктора, автоматически исполняется при создании нового объекта. В нём мы присваиваем атрибуту username значение, переданное конструктору как параметр, и создаём атрибут password с пустым значением.

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

Описанные далее методы setpass() и checkpass() служат для того, чтобы установить значение атрибута password и сравнить с ним значение, введённое пользователем. На практике работа с нашим классом может выглядеть таким образом:

u1 = User('Vasya')
u1.setpass('qwerty')
userpass = raw_input('Enter your password:')
if u1.checkpass(userpass):
    print 'Password is OK'
else:
    print 'Password is wrong'
u1.password = 'sasdf'

В первой строке мы создали объект (экземпляр класса User). Затем мы задали ему пароль и чуть позже сравнили его с тем, который пользователь ввёл по запросу сценария.

Последняя строка демонстрирует, что пароль можно изменить и непосредственно. Это является нарушением принципа инкапсуляции. Язык Python предусматривает два соглашения для решения этой проблемы: во-первых, если имя атрибута или метода начинается с символа подчёркивания, это является признаком того, что они предназначены для «внутреннего потребления» и не должны использоваться непосредственно. Однако сам Python никак не ограничивает это. Если вы желаете получить более жёсткий контроль, используйте имена, начинающиеся двумя символами подчёркивания. Такое имя уже не будет доступно через пространство имён объекта (хотя обходной путь всё же есть).

Особое место занимают специальные методы (такие как показанный выше метод __init__()). Эти методы реализуют ряд «сервисных» функций (например, конструктор __init__(), деструктор __del__(), отвечающий за корректное удаление объекта, и т.п.), а также позволяют переопределить поведение, заложенное в интерпретаторе. Например, метод __add__() исполняется при обработке оператора сложения «+». Определим его для некоторого класса:

#!/usr/bin/python
# file: stest.py
class Test:
  def __init__(self, value):
    self.value = value
  def __add__(self, other):
    return self.value – other

И теперь, вместо ожидаемого сложения, мы увидим вычитание:

 >>> from stest import Test
 >>> a = Test(5)
 >>> print a + 3
 2

Естественно, этим не стоит злоупотреблять, но вы должны знать, что очень многие «стереотипы» становятся весьма условными, если речь заходит о Python.

Нужно заметить, что вы можете задавать атрибуты объекта динамически, что называется, «на лету». Продолжим предыдущий пример:

 >>> a.description = 'Описание'
 >>> print a.description 
 Описание

Хотя атрибут description отсутствует в классе Test, мы можем работать с ним, как с обычным. Таким образом можно задать и атрибуты, имена которых начинаются с двух подчёркиваний. Это создаёт видимость того, что никаких ограничений на эти имена нет, но на самом деле получится совершенно иной атрибут, нежели определённый в описании класса. Так что не увлекайтесь подчёркиваниями.

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

Несколько слов нужно сказать о наследовании. Рассмотрим пример:

 #!/usr/bin/python
 # file: ntest.py
 from ctest import User
 class ShellUser(User):
   def __init__(self, username):
    User.__init__(self, username)
    self.shell = '/bin/sh'
   def setshell(self, newshell):
    self.shell = newshell
 u2 = ShellUser('Petya')
 u2.setpass('12345')
 print u2.password, u2.shell

Как видите, дочерний класс ShellUser получает все свойства родительского (имена родительских классов перечисляются в скобках в определении class). Теперь мы можем расширить этот класс новыми атрибутами и методами, и в дальнейшем использовать их наряду с определёнными в родительских классах.

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

Классы в стандартных модулях

Классы очень широко используются в стандартных модулях Python. В качестве примера рассмотрим один достаточно полезный модуль –StringIO. С его помощью вы можете применять к строкам методы работы с файлами (read(), write() и т.д.). Это может быть необходимо в тех случаях, когда другой метод или функция может обрабатывать только файлы. Например, класс Message модуля rfc822, с помощью которого, в частности, можно разбирать сообщения электронной почты, умеет работать только с файлом, содержащим текст сообщения.

Рассмотрим пример:

 #!/usr/bin/python
 import rfc822, StringIO
 mailstr = """\
 From: user@domain.ru
 To: me@mysite.ru
 Subject: Test Message
 Hello!
 It is a test message.
 """
 fileobj = StringIO.StringIO(mailstr)
 message = rfc822.Message(fileobj)
 print "%s wrote:\n" % message.getheader('From')
 bodyfrom = message.startofbody
 fileobj.seek(bodyfrom)
 body = fileobj.read()
 print body

Итак, что здесь происходит? Переменная mailstr содержит текст в формате почтового сообщения. Поскольку модуль rfc822 умеет работать только с файловыми объектами, мы создаём экземпляр класса StringIO под именем fileobj. На его основе уже создаётся объект message класса Message, описанного в модуле rfc822.

С помощью метода getheader() мы распечатываем значение поля «From», а узнав из атрибута startofbody объекта message, где начинается тело сообщения (отделённое от заголовка пустой строкой), мы видим, что нашему объекту fileobj не чужды никакие методы настоящих файлов – можно позиционировать указатель (метод seek()), считывать содержимое от текущей позиции до конца файла (read()), и т.д.

Документируй это!

Хотя в языке Python инкапсуляция, т.е. сокрытие внутреннего устройства класса, не реализована в чистом виде (вы можете обращаться напрямую к любому атрибуту, за небольшим исключением), но всё же хорошим тоном считается работать с объектами классов только через предоставляемый ими интерфейс. И в этих условиях особое значение приобретает документация. В Python реализован очень удобный способ, скажем так, «онлайновой» документации – первая текстовая строка класса (или функции) воспринимается как его описание, которое может быть в любое время получено с помощью атрибута __doc__:

 >>> class Cla:
 ... "Документация класса"
 ... pass
 ...
 >>> print Cla.__doc__
 Документация класса

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

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

Персональные инструменты
купить
подписаться
Яндекс.Метрика