Классы в языке Python – это объекты первого рода, т.е. их можно создавать в процессе выполнения программы, сохранять в переменной, передавать в качестве аргументов функций, возвращать как результат из функций и др.
Объявление класса осуществляется с помощью оператора 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) | Описание |
---|---|---|---|
__dict__ | словарь | R/W | пространство имен класса |
__name__ | строка | R | имя класса |
__qualname__ | строка | R | полное имя класса или функции |
__bases__ | кортеж классов | R | классы, от которых наследуется данный класс |
__doc__ | строка или None | R/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.
После объявления класса можно выполнить операцию инстанцирования класса. Создадим один экземпляр
этого класса – объект m1:
>>>
m1=My_class()
Экземпляры классов имеют следующие преопределенные атрибуты
(см. табл. 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 – нет.
В языке 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()
Привет! Меня зовут Серый волк.
Атрибуты позволяют присваивать разные значения разным объектам одного и того же класса. Но
бывает и такая информация, которая относиться не к индивидуальным объектам, а ко всему классу.
Нпример, необходимо следить за количеством созданных зверюшек. Можно было для этого присвоить
каждому объекту атрибут 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
В языке 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()
Это закрытый метод
Но применять ее (также как и форму доступа к закрытому атрибуту) не
следует.
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, в которой:
Номер п/п | Поведение | Атрибуты/ методы | Управление |
---|---|---|---|
1 | 1 | 1,3,4 | 1 |
2 | 2 | 2,3,5 | 2 |
3 | 3 | 1,3,5 | 3 |
4 | 4 | 2,3,4 | 1 |
5 | 5 | 1,3,4 | 2 |
6 | 6 | 2,3,5 | 3 |
7 | 1 | 1,3,5 | 1 |
8 | 2 | 2,3,4 | 2 |
9 | 3 | 1,3,4 | 3 |
10 | 4 | 2,3,5 | 1 |
11 | 5 | 1,3,5 | 2 |
12 | 6 | 2,3,4 | 3 |
13 | 1 | 1,3,4 | 1 |
14 | 2 | 2,3,5 | 2 |
15 | 3 | 1,3,5 | 3 |
16 | 4 | 2,3,4 | 1 |
17 | 5 | 1,3,4 | 2 |
18 | 6 | 2,3,5 | 3 |
19 | 1 | 1,3,5 | 1 |
20 | 2 | 2,3,4 | 2 |