10-й час. Определение объектов

10-й час

Определение объектов

  В этой главе Вы научитесь создавать и использовать в программах объекты. Вы узнаете, как послать сообщение нашему простейшему объекту, какова внутренняя структура объекта и для решения каких задач необходимы объекты. Закончив чтение данной главы, Вы сможете определять классы; создавать объекты классов; разрабатывать методы классов; наследовать классы.

  Итак, продолжим рассмотрение класса now, который уже использовали в конце предыдущей главы.

Первый класс

В листинге 10.1 показан уже знакомый Вам код класса now.

Листинг 10.1. Класс now

class now:

def _init_(self):

  self.t = time.time()

  self.year, \

  self.month, \

  self.day, \

  self.hour, \

  self.minute, \

  self.second, \

  self.dow, \

  self.doy, \

  self.dst = time.localtime(self.t)

  При создании экземпляра класса в Python используется специальный метод __init__().

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

  Как Вы помните, переменные создаются вместе с присвоением им значений. Именно это и происходит, когда мы вызываем метод __init__() для класса now:

self.t = time.time()

  В этой инструкции явно создаётся переменная t, которой присваивается текущее значение машинного времени. Почему бы нам просто не создать переменную t, присвоить ей требуемое значение и использовать затем в программе обычным способом? Потому что в этом случае будет создана локальная переменная, а не переменная-член класса. Другими словами, если мы создаём переменную внутри функции, не определив её особым образом, то эта переменная будет видна только внутри функции. То же справедливо для класса. Так, если бы мы вместо конструкции с параметром self записали t=time.time(), переменная t была бы видима только внутри функции __init__(). Но эта переменная оставалась бы невидимой для всех объектов, создаваемых на базе класса now. Представьте себе, что функции связаны с закрытым чёрным ящиком. Чтобы поместить некую вещь внутрь ящика, нужно вызвать соответствующую функцию (или метод) и задать ей правильный набор аргументов. А чтобы взять что-то из ящика, нужно воспользоваться значением, возвращаемым этой функцией. Функции создают переменные внутри ящика, но для этого могут использоваться только значения, переданные с аргументами, и никакие другие внешние переменные. Для них внешний мир просто не существует. Хотя Вы можете вызывать методы и использовать возвращённые значения для присвоения другим переменным или создания новых переменных, сама функция не может ничего создавать вне пределов ящика, которому она назначена. Исключение из этого правила делается только для функций, использующих параметр self. Пример использования параметра self показан в листинге 10.1. Переменные-члены (терминология объектно-ориентированного программирования) в Python создаются так же, как и обычные переменные, т. е. путём присвоения им значений. Это и происходит в листинге 10.1: self.t, self.year, self.month и т.д. — все это переменные, которые создаются как члены класса now.

  Функция localtime() возвращает набор из 9 значений даты и времени. В рассмотренном выше листинге эти значения присваиваются отдельным переменным-членам. Как и при создании обычных переменных, имя переменной находится слева от оператора присвоения (=), а присваиваемое значение — справа. Переменной dow присваивается значение дня недели, doy записывает номер года и dst принимает значение 1 для зимнего времени или 0 — для летнего.

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

  Как Вы видели в исходном коде класса now, показанного в листинге 9.4, создать экземпляр класса очень просто:

n=now()

print "The year is", n.year

  Создание экземпляра класса во многом напоминает вызов функции. В действительности так оно и есть. По данной команде Python скрытно выполняет ряд действий: создаётся родовой объект, который передаётся в функцию вместо аргумента self. Таким образом, создание экземпляра класса можно представить следующим образом:

n = now.__init__(объект)

  После создания экземпляра класса появляется возможность напрямую обращаться к методу __init__(), как в следующем примере:

n=now()

о=now.__init__(n)

print "year", o.year, n is о

  Если запустить на выполнение данный код, то появится сообщение об ошибке AttributeError, сообщающее Вам о том, что переменная о не является экземпляром класса now (пустой объект). Ошибка произошла из-за того, что в действительности аргумент self, переданный в функцию __init__(), не является ссылкой на родовой объект. Связь аргумента с объектом устанавливает Python, но только при соблюдении стандартного синтаксиса создания экземпляра класса.

Модернизация класса

  Созданный нами класс now уже вполне применим на практике и эффективен, поскольку совмещает в одном объекте выполнение двух функций. Но, немного потрудившись, мы можем повысить эффективность нашего объекта. Этим мы сейчас и займёмся — создадим больший класс, который сможет делать для нас больше (извините за каламбур). Работу над классом мы начнём в этом разделе, затем продолжим в следующей главе и в главе, посвященной специальным методам классов Python.

  Что должен делать наш новый класс? Ниже представлены очередные требования к классу:

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

В листинге 10.2 показан слегка модифицированный класс now.

Листинг 10.2. Измененный класс now

#!C:\PYTHON\PYTHON.EXE

import time

class now:

  def __init__(self):

    self.t=time.time()

    self.storetime()

 

  def storetime(self):

    self.year, \

    self.month, \

    self.day, \

    self.hour, \

    self.minute, \

    self.second, \

    self.dow, \

    self.doy, \

    self.dst=time.localtime(self.t)

 

n=now()

print "The year is", n.year

print n

  Все, что мы сделали, — это добавили в класс простой метод, с помощью которого в любой момент можно сохранять текущее время в соответствующей переменной члене. Это означает, что после создания экземпляра класса now можно в любой момент обновить в нем значение текущего времени:

n=now()

n.t=time.time()

n.storetime()

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

Листинг 10.3. Альтернативный способ выполнения класса now

class now:

  def __init__(self):

    self.t=time.time()

    self.current=time.localtime(self.t)

 

  def year(self):

    return self.current[0]

 

  def month(self):

    return self.current[1]

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

n=now()

print n.уear()

 

  *Прим. В. Шипкова: если приведённые методы добавить в выше приведённую программу, то при попытке выполнить команду

print n.уear()

интерпретатор выругается, что тип 'int' "невызываем". В чём тут фишка? А в том, что до этого была определена переменная класса, с таким же именем. Именно её Питон и юзает. По другому говоря, классический конфликт имён. Грамотно будет делать так:

1) процедурку обозначить GetYear()

2) названия методов должны обозначать действие, например:

  GetYear():

  SetYear():

Впрочем, об этом речь ещё будет.

  Ho Python не столь традиционен, как другие современные языки программирования.

  При запуске программы now.py, код которой показан в листинге 10.2, при выполнении последней инструкции (print n) на экране появляется неожиданное сообщение:

<__main__.now instance at 7fa450>

  Эта строка сообщает нам, что n является экземпляром класса. Но мы это и так знаем. Нам нужно иметь возможность сообщить Python, что именно должно выводиться при печати экземпляра класса. К счастью, в Python предусмотрен метод выполнения этой задачи. Для этого нам потребуется так называемый специальный метод класса. Изучению этой темы полностью будет посвящена глава 13. Сейчас мы только вкратце рассмотрим эти методы.

  В действительности со специальными методами классов Вы уже встречались, например с __init__(). Каждый раз при создании экземпляра класса Python ищет данный метод в определении класса и автоматически выполняет его. Если данный метод явно не опредёлен, ничего страшного не происходит. Хотя непонятно, в чём может состоять преимущество такого определения класса, когда не происходит никакой инициализации при создании экземпляра класса. В действительности такие классы применяются, но на более высоком уровне программирования для создания сложных объектных конструкций, рассмотрение которых выходит за рамки этой книги.

  Когда встречается инструкция вроде print n, где n — экземпляр какого-либо класса, Python ищет в определении класса специальный метод __str__(). Этот метод требуется для возвращения строки, представляющей текущий экземпляр класса. Если явно не указано, что именно должно выводиться при печати экземпляра класса, то выводится простое стандартное описание экземпляра. В примере с нашим классом now импортированный модуль time предлагает стандартный набор функций для вывода требуемой информации. Для нас удобной будет функция time.ctime(). Измененный вариант класса now показан в листинге 10.4.

Листинг 10.4. Новые изменения в классе now

import time

class now:

  def __init__ (self):

    self.t=time.time()

    self.storetime()

 

  def storetime(self):

    self.year, \

    self.month, \

    self.day, \

    self.hour, \

    self.minute, \

    self.second, \

    self.dow, \

    self.doy, \

    self.dst=time.localtime(self.t)

 

  def __str__ (self) :

    return time.сtime(self.t)

 

n=now()

print "The year is", n.year

print n

print s

 

  *Прим. В. Шипкова: последняя инструкция вызовет ошибку. <s> никак не объявлялась до попытки напечатать.

  Запустим теперь программу now.py. Результат показан на рис. 10.1 (безусловно, текущее время на моём и на Вашем компьютерах не совпадает).

Рис. 10.1. Вывод измененной программы now.py

В инструкциях с print функцию str() можно выполнять явно:

n=now()

s=str(n)

print s

  Для выведения строк в Python используется ещё один специальный метод  __rерr__().

  Экземпляр класса, выводимый этим методом, заключается между символами обратного ударения (``) От метода __str__() данный метод отличается тем, что возвращаемая им строка затем может быть вновь преобразована в экземпляр того же класса. Например, строка, возвращённая функцией __repr__(n), где n — экземпляр класса now, может быть преобразована обратно в экземпляр класса now с помощью какого-нибудь метода или функции, которые следует разработать отдельно. Позже мы рассмотрим это подробнее, а сейчас просто добавим в определение класса следующие строки:

def __repr__(self):

  return time.ctime(self.t)

  Как Вы видите, определение метода __repr__() следует тому же синтаксису, что и определение метода __str__(). Это справедливо для большинства специальных методов классов Python. Но Python отчетливо различает методы __str__() и __repr__(), поэтому нам следует добавить в класс обе версии.

Производные класса

  В листинге 10.5 показана текущая версия класса now с добавленными двумя методами вывода строк и рядом других изменений, которые мы сейчас рассмотрим.

Листинг 10.5. Класс now с методами __str__() и __repr__():

#!C:\PYTHON\PYTHON.EXE

import time

class now:

  def __init__(self):

    self.t=time.time()

    self.storetimef)

 

  def storetime(self):

    self.year, \

    self.month, \

    self.day, \

    self.hour, \

    self.minute, \

    self.second, \

    self.dow, \

    self.doy, \

    self.dst=time.localtime(self.t)

 

  def __str__(self) :

    return time.ctime(self.t)

 

  def __repr__(self) :

    return time.ctime(self.t)

 

if __name__ == "__main__":

  n=now()

  print "The year is", n.year

  print n

  x=now()

  s='n'

  print s

  Благодаря добавленной инструкции if __name__... мы можем теперь импортировать класс now в другие модули и в интерпретатор, как показано на рис. 10.2.

Рис. 10.2. Импортирование класса now в интерпретатор

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

  Для повышения функциональности класса можно добавить в него другие специальные методы. Например, добавим следующие строки сразу после определения метода __rерr__():

def __call__(self,t=-1.0):

  if t < 0.0:

    self.t = time.time()

  else:

    self.t = t

    self.storetime()

Этот новый метод позволяет делать следующее.

  1. Обновлять текущее значение времени в экземпляре класса.

  2. Устанавливать значение времени в экземпляре класса в допустимом для данной системы диапазоне (для UNIX в пределах 1970-2038 гг.).

Использование этих возможностей показано на рис. 10.3.

Рис. 10.3. Выполнение класса now с методом__call__()

  Метод __call__() позволяет вызывать экземпляр класса таким образом, как если бы это была обычная функция. Как видно из рис. 10.3, реализация объекта класса now всегда возвращает текущее время. Затем мы вызываем объект n и сообщаем ему (в параметре t) новое значение времени — 0.0. Сообщение объекту n нулевого значения равносильно присвоению ему минимального значения времени в допустимом временном диапазоне — 1 января 1970 года, 00:00:00 часов. Но у меня на мониторе отобразилось время 03:00:00, поскольку на моём компьютере выбрана временная зона GMT+03:00 (Московское время), а для возвращения текущего времени используется функция local_time() из модуля time. Таким образом, время начала временного диапазона может меняться в зависимости от временной зоны и летнего-зимнего времени.

GMT означает Greenwich mean time (среднее время по Гринвичу). За нулевую была принята меридиана, проходящая через Королевскую обсерваторию в Гринвиче, Великобритания (Royal Observatory at Greenwich). От этой меридианы начинается отсчёт временных поясов во всем мире. Решение об этом было принято на Международной конференции по определению меридиан (International Meridian Conference) в 1884 г., и к нему присоединились все страны за исключением Франции. В 1970 г. состоялась новая конференция под эгидой Международного телекоммуникационного союза (International Telecommunication Union), на которой было принято решение об отсчете по Гринвичу мирового времени — Coordinated Universal Time. Для мирового времени была выбрана аббревиатура UTC. (Эта аббревиатура никак не расшифровывается ни на одном языке. По идее она должна использоваться вместо GMT.)

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

  Созданный нами простой класс можно использовать как основу для создания новых классов, наследуя их от базового класса. Как Вы помните из предыдущей главы, наследование — это один из атрибутов объектно-ориентированного программирования. Теперь Вы готовы к использованию наследования на практике. В листинге 10.6 показано наследование класса today от класса now.

#!C:\PYTHON\PYTHON.EXE

import time

import now

 

class today(now.now):

  def __init__(self,y=1970):

    self.t=time.time()

    self.storetime()

if __name__=="__main__" :

  n=today()

  print "The year is", n.year

  print n

  x=today()

  s='x'

  print s

  При наследовании классов в Python для производного (дочернего) класса вызывается метод __init__(). При этом все методы, ранее определённые в базовом (родительском) классе, становятся доступными для нового производного класса точно так же, как если бы мы определили их в этом классе. С другой стороны, переменные-члены класса не будут появляться в производном классе до тех пор, пока мы явно не создадим их или пока не вызовем метод __init__() для базового класса. В листинге 10.6 первый метод нового класса явно создаёт переменную (см. строки 6,7). Данный код можно немного упростить, если вместо создания нового метода установить вызов метода __init__() базового класса в методе __init__() дочернего класса:

def __init__(self,y=1970):

  now.__init__(self)

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

  Прежде чем завершить эту главу, давайте добавим в класс today ещё один метод — update(), с помощью которого мы сможем устанавливать для объекта класса today значение времени в промежутке от 1970 до 2038 гг. Добавление метода update() показано в листинге 10.7.

Листинг 10.7. Добавление метода update () в класс today

#!C:\PYTHON\PYTHON.EXE

import time

import now

class today(now.now):

  def __init__(self,y=1970):

    now.__init__(self)

 

  def update(self,tt):

    if len(tt) < 9:

      raise TypeError

    if tt[0] < 1970 or tt[0] > 2038:

      raise OverflowError

    self.t = time.mktime(tt)

    self(self.t)

 

if __name__ == "__main__" :

  n = today ()

  print "The year is", n.year

  print n

  x = today()

  s = 'x'

  print s

  tt = (1999,7,16,12,59,59,0,0,-1)

  at.update (tt)

  print x

  Новый метод update принимает набор значений даты и времени, выполняет простенькую проверку соответствия введённых данных допустимым параметрам и пытается с помощью функции time.mktime() извлечь из полученного набора значение времени. Время в Python отображается в виде числа с плавающей запятой. Время, заданное в нашем примере, выражается числом 932151599,0. Результат запуска программы today.ру показан на рис. 10.4.

Рис. 10.4. Выполнение программы today.ру

  Следующую главу мы начнём с добавления новых методов в класс today для повышения его функциональности. Так, нам нужно будет иметь возможность складывать и отнимать значения времени.

Резюме

  В этой главе Вы узнали, как создавать классы, объекты и методы классов, а также как наследовать классы.

Практикум

Вопросы и ответы

Почему 1970-й и 2038-й годы являются граничными в UNIX?

  Дата 1 января 1970 года 00:00:00 выбрана как базовая в UNIX. Текущее время всегда сохраняется как целое число, имеющее размер 4 байта, или 32 бита. Один бит отводится на символ знака, а время представлено числом секунд от исходной даты. Таким образом, на запись числа секунд отводится 31 бит. В результате в большинстве систем UNIX дата 19 января 2038 года является предельной, так как исчерпывается запас битов и значение обнуляется. В некоторых версиях UNIX для представления даты используется беззнаковое целое число, что добавляет ещё один разряд. В результате предельная дата отодвигается на 68 лет. В других версиях для представления времени отводится не 4, а 8 байт, что в принципе делает предельную границу недостижимой в реальном календаре.

Где можно получить подробное описание модуля time?

  Всю документацию можно загрузить из Internet по адресу http://www.python.org/doc/current/download.html. Выберите предпочитаемый формат документа. Если определённых предпочтений нет, начните с документации в формате HTML, которую можно прочесть с помощью Вашего браузера. Установите документацию в соответствии с прилагаемой инструкцией и выберите в указателе ссылку Global module index (Указатель общих модулей). В открывшемся окне выберите ссылку time. Если Вы предпочитаете просматривать документацию в интерактивном режиме, то ссылку time можно выбрать непосредственно на странице http://www.python.org/doc/current/modindex.html.

Контрольные вопросы

  1. В чем состоит отличие между инструкциями t = time.time() и self.t = time.time()?

    а) В первом случае создаётся переменная, видимая только в содержащем её модуле, а вторая переменная видна только в классе в котором она опредёлена.

    б) В первом случае создаётся временная переменная, а во втором случае — постоянная.

    в) Нет никакой разницы.

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

  2. Какая разница между методами __str__() и __rерr__()?

    а) Никакой.

    б) Метод __str__() может возвращать только ранее сохранённые строки, тогда как метод __rерr__() может возвращать строки и преобразовывать их в исходные объекты.

    в) Метод __str__() всегда возвращает строки, тогда как метод __rерr__() может возвращать данные в двоичном формате.

    m) Метод __str__() используется для записи и печати, а __rерr__() — для чтения.

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

    а) Методов родительского класса.

    б) Переменных-членов родительского класса.

    в) Методов и переменных родительского класса.

    т) Базового класса, от которого был произведён родительский класс.

Ответы

  1. г. Инструкция t=time.time() используется внутри функции для создания локальной переменной. Внутри модуля эта инструкция создаст переменную, локальную для модуля. Если же в определении класса использовать инструкцию self.t=time.time(), то получится переменная-член класса, видимая всюду, где виден данный класс.

  2. 6. С помощью метода __str__() можно возвращать любые требуемые Вам строки, но метод __rерr__() позволяет также преобразовать полученную строку в исходный объект. В остальном же между этими методами нет никаких различий.

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

Примеры и задания

  Перепишите класс today, используя альтернативное выполнение, показанное в листинге 10.3. Какой стиль Вам нравится больше и почему?

  Интересная информация о Королевской обсерватории в Гринвиче (кстати, эта обсерватория уже не действует) представлена на Web-странице по адресу http://www.rog.nram.ac.uk/index.html. О мировом времени UTC Вы можете прочесть информацию на домашней странице Космической академии NASA (NASA's Space Academy) пo адресу http://liftoff.msfс.nasa.gov/academy/rocket_sci/clocks/time-gmt.html.