ЛАБОРАТОРНАЯ РАБОТА №11

Тема: Создание пользовательских классов

Цель: Рассмотрение способов создания классов, объектов и их использования

ОГЛАВЛЕНИЕ

1 Создание пользовательского класса
1.1 Объявление класса
1.2 Создание объектов – экземпляров класса
2 Использование атрибутов и методов при объявлении класса
2.1 Использование специальных методов при объявлении класса
2.2 Применение атрибутов класса и статических методов
2.3 Использование закрытых атрибутов и методов
2.4 Управление атрибутами
Индивидуальные задания

 Оглавление

1 Создание пользовательского класса

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

 Оглавление

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

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

>>> class My_class ( object ):

или

>>> class My_class :

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

>>> class Min_class:
pass

или

>>> class My_class : pass

При описании класса второй строкой может быть строка, взятая в тройные кавычки, которая документирует класс (см. использование строки документирования при объявлении функции в подраздел 1.1 лаб. раб. №6). Обычно в этой строке указывается назначение класса, например:

'''Пример простого класса'''

Классы могут иметь атрибуты, которые делятся на атрибуты-переменные и атрибуты-методы. Атрибуты-переменные указываются с помощью оператора присваивания и являются ссылками на объекты, значеения которых им было присвоено.
Форма описания атрибутов-методов класса совпадает с описанием функций (см. подраздел 1.1 лаб. раб. №6), но первым аргументом каждого метода (за исключением статичестких методов (см. подраздел 2.2) должно быть слово self, которое возвращает ссылку на объект, вызывающий данный метод. Можно отметить, что self в языке Python не является ключевым словом и используется по соглашению.
Добавив в класс My_class описания атрибута-переменной a и атрибута-метода f(), получим следующее объявление класса:

>>> class My_class :
'''Пример простого класса'''
a=25
def f (self):
print ( "Привет всем!" )

При создании класса формируется его пространство имен, которое состоит из имен его атрибутов.
В языке Python класс является объектом класса type:

>>> type (My_class)
<class 'type'>,

который поддерживает два вида операций:

Для доступа к атрибутам класса используется обычный для объекта синтаксис с использованием оператора точки – Obj.name, где: Obj – имя объекта, name – имя атрибута объекта. Например, получить значение атрибута-переменной a класса My_class можно следующим образом:

>>> My_class.a
25

Если попытаться вызвать атрибут-метод f() класса My_class, то это вызовет ошибку:

>>> My_class.f()
TypeError: f() missing 1 required positional argument: 'self',

потому, что так вызывать можно только статические методы классов (см. подраздел 2.2), а методы, в которых указан параметр self, могут вызывать только объекты – экземпляры класса.
Каждый класс имеет преопределенные атрибуты (см. табл. 1).

Таблица 1 – Преопределенные атрибуты класса

АтрибутТипДоступ(1)Описание
__dict__словарьR/Wпространство имен класса
__name__строкаRимя класса
__qualname__строкаRполное имя класса или функции
__bases__кортеж классовRклассы, от которых наследуется данный класс
__doc__строка или NoneR/Wстрока документации класса
__module__строкаR/Wимя модуля, в котором данный класс был определен

Примечание (1): R/W – разрешены запись и чтение, R – разрешено только чтение.

Приведем значения некоторых преопределенных атрибутов класса My_class:

>>> My_class.__name__
'My_class'
>>> My_class.__bases__
(<class 'object'>,),

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

>>> My_class.__doc__
'Пример простого класса',
>>> My_class.__module__
'__main__'.

Python позволяет создавать классы динамически. Для этого используется встроенная функция type(), которая, если задан один аргумент – type(object), возвращает тип (класс) объекта object (см. подраздел 1.1 ). Если при вызове функции указано три аргумента – type(name, bases, dict), то она возвращает класс с именем name, который наследует классы, заданные кортежем bases, и имеет атрибуты, заданные словарем dict:

>>> A= type ( "A" , ( object ,), dict (a=25)).

Этот класс эквивалентен следующему классу, созданному статически:

>>> class A:
a=25.

 Оглавление

1.2 Создание объектов – экземпляров класса

После объявления класса можно выполнить операцию инстанцирования класса. Создадим один экземпляр этого класса – объект m1:

>>> m1=My_class()

Экземпляры классов имеют следующие преопределенные атрибуты (см. табл. 2).

Таблица 2 – Преопределенные атрибуты экземпляра класса

АтрибутТипДоступОписание
__dict__словарьR/Wпространство имен экземпляра класса
__class__классR/Wкласс этого экземпляра

Определим класс объекта m1:

>>> m1.__class___
<class '__main__.My_class'> .

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

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

Пространство имен экземпляра класса m1 пусто. Тем не менее при обращении к атрибуту a этого объекта

>>> m1.a
25

получаем значение 25, т.е. значение атрибута класса. Это происходит потому, что интерпретатор сначала ищет искомое имя в адресном простанстве экземпляра класса. Если его там нет, то – в адресном пространстве самого класса. После присвоения объекту нового значения:

>>> m1.a=100

его адресное пространство изменилось:

>>> m1.__dict__
{'a': 100}

и значения атрибута a класса и экземпляра класса стали отличаться:

>>> m1.a
100

>>> My_class.a
25

Вызов метода f() как метода экземпляра класса не вызывает ошибки:

>>> m1.f()
Привет всем!

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

>>> My_class.new_a= 'Новый'

или удалить старые (f):

>>> del My_class.f

После создания объекта m2, экземпляра измененного класса My_class:

>>> m2=My_class()

проверим его атрибуты:

>>> m2.new_a
'Новый'
>>> m2.f()
AttributeError: 'My_class' object has no attribute 'f'

Т.е, атрибут new_a в класс добавлен, а атрибут f из класса удален.
Отметим, что преопределенный атрибут __qualname__ был введен в язык Python, начиная с версии 3.3. Он указывает полное имя (англ. qualified name) класса или функции. Для классов и функций верхнего уровня атрибут __qualname__ совпадает с __name__. Для проверки создадим класс A, содержащий класс B:

>>> class A :
class B : pass

и определим для классов A и B значения атрибутов __name__ и __qualname__:

>>> A.__name__
'A'
>>> A.__qualname__
'A'
>>> A.B.__name__
'B'
>>> A.B.__qualname__
'A.B'

т.е. для класса верхнего уровня A значения атрибутов __name__ и __qualname__ совпадают, а для встроенного класса B – нет.

 Оглавление

2 Использование атрибутов и методов при объявлении класса

 Оглавление

2.1 Использование специальных методов при объявлении класса

В языке Python имеются специальные методы (см. раздел 2 лаб. раб. №1), предназначенные для использования во время объявления класса:

Метод __new__() при создании пользовательских классов используется нечасто. Можно привести две области его применения. Во-первых, когда нужно выполнить какие-то действия до выполнения метода __init__(). Это бывает необходимо, например, при наследовании классов неизменяемых типов (см. подраздел 2.3 лаб. раб. №12).
Во-вторых, когда необходимо контролировать процесс создания экземпляров класса. Примером может служить класс, который инстанцирует только один объект:

>>> class One :
obj= None
def __new__ (cls,*p,**kw):
if cls.obj is None :
cls.obj= object .__new__(cls,*p,**kw)
return cls.obj

При создании каждого экземпляра класса One первым вызывается метод __new__ этого класса, который проверяет значение атрибута obj класса One. Если оно равно None, т.е. создается первый экземпляр класса, вызывается метод __new__ базового класса object. Он создает объект – экземпляр класса, ссылка на который присваиваеься атрибуту obj класса One. Это же значение возвращается методом __new__ класса One.
При создании последующих экземпляров класса, поскольку значение атрибута obj уже не равно None, метод __new__ базового класса не вызывается и вновь созданный объект получает ссылку с тем же значением, которое хранится в атрибуте obj. Таким образом все последующие экземпляры класса One являются одним и тем же объектом. Это можно проверить. Создадим два экземпляра класса One:

>>> one1=One()
>>> one2=One()

и проверим их идентичность:

>>> id (one1)
36932784
>>> id (one2)
36932784

Можно показать, что ссылки one1 и one2 – это ссылки на один и тот же объект и по-другому:

>>> one1 is one2
True

Использование специальных методов __init__(), __str__() и __del__() показано в объявлении класса Virt_zoo, который описывает поведение виртуальных зверюшек:

>>> class Virt_zoo ( object ):
''' Виртуальные зверюшки '''
def __init__ (self,name):
print ( 'Появилась новая зверюшка.' )
self.name=name
def __str__ (self):
return 'Класс – Virt_zoo, имя зверюшки – ' +self.name
def __del__ (self):
print ( 'Зверюшка исчезла.' )

С помощью метода __init__(), который вызывается при каждом создании экземпляра класса (для Virt_zoo это будет виртуальная зверюшка), на экран выводится сообщение об этом и выполняется инициализация атрибута name, указывающего имя этой зверюшки. Отметим, что первым параметром каждого метода в объявлении класса, за исключением статических методов (см. подраздел 2.2), является слово self, которое при вызовах этих методов не указывается.
Метод __str__вызывается при использовании функции print(obj) и возвращает данные об объекте obj – в данном случае это имя класса и имя созданной зверюшки.
С помощью десктруктора класса (метода __del__) осуществляется вывод на экран сообщения "Зверюшка исчезла" при удалении объекта класса Virt_zoo.
После объявления класса можно выполнять операции его инстанцирования, то есть создания объектов класса. Создаются два экземпляра класса Virt_zoo – объекты v1 и v2:

>>> v1=Virt_zoo( 'Колобок' )
Появилась новая зверюшка.
>>> v2=Virt_zoo( 'Серый волк' )
Появилась новая зверюшка.

При создании каждого объекта на экран выводится, предусмотренное конструктором, сообщение "Появилась новая зверюшка.".
По умолчанию все методы и атрибуты класса являются открытыми (public) и клиентский код может обращаться к этим методам и получать значения атрибутов. Поэтому получим для каждого созданного объекта значение его атрибута name:

>>> v1.name
'Колобок'
>>> v2.name
'Серый волк'

Указав имя класса в функции print(), получим данные, определяемые методом __str__(), заданные по умолчанию для класса Virt_zoo:

>>> print (Virt_zoo)
<class '__main__.Virt_zoo'>,

т.е. класс Virt_zoo объявлен в модуле __main__ .
Указав имя объекта в функции print(), получим данные об объекте, определяемые пользовательским специальным методом __str__():

>>> print (v1)
Класс – Virt_zoo, имя зверюшки – Колобок

После удаления созданного объекта v1

>>> del v1

в соответствии с работой пользовательского специального метода __del__() появляется сообщение

Зверюшка исчезла.


И последующее обращение к атрибуту объекта вызовет исключение

>>> v1.name
NameError: name 'v1' is not defined

Как уже отмечалось, после объявления класса в него можно добавлять новые атрибуты и методы. Поэтому создадим метод talk():

>>> def talk (self):
print ( 'Привет! Меня зовут ' +self.name+ '.' ),

который при вызове выводит на экран сообщение, сделанное зверюшкой, и добавим этот метод в класс Virt_zoo:

>>> Virt_zoo.talk=talk

После этого вызовем для каждого созданного объекта класса Virt_zoo метод talk():

>>> v1.talk()
Привет! Меня зовут Колобок.
>>> v2.talk()
Привет! Меня зовут Серый волк.

 Оглавление

2.2 Применение атрибутов класса и статических методов

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

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

>>> class Virt_zoo :
''' Виртуальные зверюшки '''
total=0 # Число созданных зверюшек
def __init__ (self,name):
print ( 'Появилась новая зверюшка.' )
self.name=name
Virt_zoo.total+=1
@ staticmethod
def status ():
print ( 'Всего зверюшек – ' , Virt_zoo.total)
def talk (self):
print ( 'Привет! Меня зовут ' +self.name, '.' )

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

>>> v1=Virt_zoo( 'Колобок' )
Появилась новая зверюшка.
>>> v1.total
1
>>> v2=Virt_zoo( 'Змей Горыныч' )
Появилась новая зверюшка.
>>> Virt_zoo.status()
Всего зверюшек – 2

 Оглавление

2.3 Использование закрытых атрибутов и методов

В языке Python используется три вида атрибутов:

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

А также удалим из него:

поскольку они были рассмотрены в подразделе 2.2. После внесенных изменений описание класса выглядит следующим образом:

>>> class Virt_zoo:
'''Виртуальные зверюшки'''
def __init__ (self,name,mood):
print ( 'Появилась новая зверюшка.' )
self.name=name
self.__mood=mood
def talk (self):
print ( 'Привет! Меня зовут ' +self.name+ '. Чуствую себя ' +self.__mood)

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

>>> v1=Virt_zoo( 'Колобок' , 'отлично.' )
Появилась новая зверюшка.
>>> v1.talk()
Привет! Меня зовут Колобок. Чуствую себя отлично.
>>> v2=Virt_zoo( 'Серый волк' , 'голодным.' )
Появилась новая зверюшка.
>>> v2.talk()
Привет! Меня зовут Серый волк. Чуствую себя голодным.

Попытка непосредственного обращения к атрибуту с именем mood или __mood ведет к ошибке:

>>> v1.mood
AttributeError: 'Virt_zoo' object has no attribute 'mood'
>>> v1.__mood
AttributeError: 'Virt_zoo' object has no attribute '__mood'

Т.е. объект класса Virt_zoo не имеет ни атрибута mood, ни атрибута __mood.
Тем не менее в языке Python имеется возможность обратиться к закрытому аргументу из клиентского кода и получить его значение. Для этого необходимо использовать следующую форму: объект._ИмяКласса__ИмяАргумента:

>>> print (v1._Virt_zoo__mood)
отлично.

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

Рассмотрим небольшой пример создания и использования закрытого метода:

>>> class С:
def __private_method (self):
print ( 'Это закрытый метод' )
def public_method (self):
print ( 'Это открытый метод' )
self.__private_method()

>>> c1=C()
>>> c1.public_method()
Это открытый метод
Это закрытый метод

Непосредственное обращение к закрытому методу приводит к ошибке:

>>> c1.__private_method()
AttributeError: 'C' object has no attribute 'private_method'

Так же как и для атрибутов в языке Python имеется возможность обратиться к закрытому методу из клиентского кода. Для этого необходимо использовать следующую форму: объект._ИмяКласса__ИмяМетода:

>>> c1._C__private_method()
Это закрытый метод

Но применять ее (также как и форму доступа к закрытому атрибуту) не следует.

 Оглавление

2.4 Управление атрибутами

Python поддерживает возможность управления доступом к атрибутам класса. Создадим класс D с одним закрытым атрибутом __x:

>>> class D :
def __init__ (self):
self.__x= None

и создадим объект d1 :

>>> d1=D()

При попытке обратиться к свойству x :

>>> d1.x

вырабатывается исключение:

AttributeError: 'D' object has no attribute 'x'

Аналогичное сообщение получим при попытке обратиться к атрибуту __x.
Для предоставления возможности получить доступ к атрибуту x из пользовательского кода, предствим этот атрибут в виде свойства класса с разрешением на чтение. Для этого создается метод x() с декоратором @property:

>>> class D :
def __init__ (self):
self.__x= None
@ property
def x (self):
return self.__x

Снова попытаемся обратиться к свойству x :

>>> d1=D()
>>> d1.x

На этот раз ошибки не возникло. Но на экран ничего не выведено, так как атрибуту x изначально было присвоено значение None. Попробуем задать этому свойству некоторое другое значение:

>>> d1=D()
>>> d1.x=7
AttributeError: can't set attribute

Ошибка о невозможности установки атрибута произошла потому, что пользователю не было разрешено изменять значения свойства. Чтобы дать ему такую возможность добавим в класс D декоратор @x.setter с методом x():

>>> class D :
def __init__ (self):
self.__x= None
@ property
def x (self):
return self.__x
@ x.setter
def x (self,value):
self.__x=value

Теперь попытаемся изменить свойство x и определить его новое значение:

>>> d1=D()
>>> d1.x=77
>>> d1.x
77
Задание нового значения свойству x произошло успешно, а попытка удалить это свойство привела к ошибке:

>>> del d1.x
AttributeError: can't delete attribute

Чтобы дать возможность пользователю удалить свойство (если это необходимо), добавим в описание класса еще один декоратор – @x.deleter и еще один метод x():

>>> class D :
def __init__ (self):
self.__x= None
@ property
def x (self):
return self.__x
@ x.setter
def x (self,value):
self.__x=value
@ x.deleter
def x (self):
del self.__x

Зададим новое значение свойству x и попытаемся удалить это свойство:

>>> d1=D()
>>> d1.x=125
>>> d1.x
125
>>> del d1.x
>>> d1.x
AttributeError: 'D' object has no attribute '_D__x'

Т.е. свойство x удалено.

 Оглавление

Индивидуальные задания

Разработать программу на языке Python, в которой:

Таблица 3 – Перечень индивидуальных заданий

Номер
п/п
ПоведениеАтрибуты/
методы
Управление
111,3,41
222,3,52
331,3,53
442,3,41
551,3,42
662,3,53
711,3,51
822,3,42
931,3,43
1042,3,51
1151,3,52
1262,3,43
1311,3,41
1422,3,52
1531,3,53
1642,3,41
1751,3,42
1862,3,53
1911,3,51
2022,3,42

 Оглавление