Помимо типов (классов) данных программы язык Python имеет и так называемые абстрактные базовые
классы (англ. abstract base classes) – ABC-классы, каждый из которых объединяет данные
нескольких различных типов, имеющих общие свойства. Например, к ABC-классу контейнер
(англ. container) относятся все типы данных, которые имеют в своем составе более одного объекта
(см. подраздел 1.1).
В табл. 1 приведены названия ABC-классов, классы, их порождающие, и
атрибуты.
ABC-класс | Базовые ABC-классы | Атрибуты, методы |
---|---|---|
Container (см. подраздел 1.1) | __contains__ | |
Hashable (см. подраздел 1.3) | __hash__ | |
Iteruble (см. подраздел 1.2) | __iter__ | |
Iterator (см. подраздел 1.2) | Iteruble | __next__ |
Sized | __len__ | |
Callable | __call__ | |
Sequence (см. подраздел 1.4) | Container, Iteruble Sized | __getitem__, __len__ |
MutableSequence (см. подраздел 1.4) | Sequence | __getitem__, __len__, __setitem__, __delitem__ |
Set(см. раздел 1 лаб. раб. №5) | __contains__, __iter__, __len__ | |
MutableSet (см. раздел 1 лаб. раб. №5) | Set | __contains__, __iter__, __len__, __setitem__, __delitem__ |
Mapping (см. раздел 2 лаб. раб. №5) | Sized, Iterable, Container | __getitem__, __iter__, __len__ |
MutableMapping (см. раздел 2 лаб. раб. №5) | Mapping | __getitem__, __setitem__, __delitem__, __iter__, __len__ |
В отличие от встроенных типов, которые хранятся в модуле builtins
(см. подраздел 1.2 лаб. раб. №2) и автоматически загружаются в
память при вызове интерпретатора, ABC-классы описаны в модуле
collections. Поэтому для их использования необходимо подключить к
программе этот модуль. Начиная с версии Python 3.3 ABC-классы помещены в новый модуль
collections.abc. При этом с целью обеспечения обратной совместимости
сохранена возможность доступа к ABC-классам по-прежнему через модуль
collections. Использование модуля collections
и ABC-классов см. в подразделах 1.1, 1.2,
1.3, 1.4.1, 2.1 данной лабораторной
работы, в разделе 2 и в подразделах
1.1, 3.3
лаб. раб. №4, в разделе 1 и в подразделах
2.1, 3.1
лаб. раб. №5.
Модуль collections помимо ABC-классов также
содержит специализированные типы контейнеров, обеспечивающие альтернативы встроенным типам
list, tuple,
dict и др. Например,
collections.deque (очереди),
collections.nametuple (именнованные кортежи),
collections.OrderedDict (упорядоченные словари) и др.
Протоколы в языке Python подобны интерфейсам в других языках в том,
что определяют набор методов, который объекты должны поддерживать при использовании протокола, но
отличаются тем, что разпознаются на уровне языка и имеют синтаксическую поддержку. Использование
протоколов приведено в подразделах 1.1, 1.2 и
1.4.
Контейнеры – это объекты, которые содержат произвольное число других объектов. Они
принадлежат ABC-классу container (см. табл. 1). Контейнеры делятся на
последовательности (см. подраздел 1.4), множества
(см. раздел 1 лаб. раб. №5) и словари
(см. раздел 2 лаб. раб. №5).
Все объекты-контейнеры, поддерживая протокол контейнера, имеют
специальный метод __contains__(other), который проверяет, содержится ли в
данном объекте объект other, и возвращает значение True, если содержится,
и значение False, если нет.
Возьмем два объекта: строку "It's a string" и целое число 32,
заданные литералами, и попытаемся вызвать для них метод
__contains__():
>>>
"It's a string"
.__contains__(
"string"
)
True.
>>>
"It's a string"
.__contains__(
"cat"
)
False.
Т.е. метод __contains__() определил, что
подстрока "string" является частью строки "It's a string", а подстрока "cat" – нет.
Следовательно, строки являются контейнерами.
>>>
(32).__contains__(3)
AttributeError: 'int' object has no attribute '__contains__' .
Для числа использование метода
__contains__() привело к возникновению исключения AttributeError (ошибка
атрибута), поскольку целочисленный объект не имеет этого метода. Поэтому целые числа не являются
контейнерами.
Отметим, что число 32 взято в скобки для устранения лексической
неоднозначности – последовательность символов "32." интерпретатор воспринимает не как целое
число, за которым следует оператор точка, а как число с плаващей точкой. Поэтому в этом случае (без
использования скобок) будет зафиксирована синтаксическая.ошибка.
Метод __contains__() вызывается при
выполнении операторов in и not in для
определения вхождения или невхождения одного объекта в состав другого объекта (см. подразделы
1.4.2 и 2.2).
При создании пользователем контейнера необходимо в объявлении класса
описать метод __contains__().
Для определения, является ли некий объект контейнером или нет, можно
также воспользоваться модулем collections (см. раздел 1)
для получения ABC-класса Container и встроенной функцией
isinstance(object, classinfo)
(см. подраздел 8.1 лаб. раб. №1), которая возвращает True,
если объект, указанный аргументом object является экземпляром класса,
заданного аргументом classinfo. Если object не
является объектом указанного типа, то функция возвращает значение False.
Для проверки возьмем те же два объекта: строку и число и, загрузив
описание ABC-класса Container из модуля
collections:
>>>
from
collections
import
Container,
определим с помощью функции isinstance(), принадлежат ли эти объекты
классу container:
>>>
isinstance
(
"It's a string"
, Container)
True,
>>>
isinstance
(32, Container)
False.
Новая проверка подтвердила, что строки являются контейнерами, а целые
числа (см. подроздел 4.1.1 лаб. раб. №1) – нет.
Также не являются контейнерами числа с пдавающей точкой, комплексные числа и значения логического
типа (см. подрозделы
4.1.2, 4.1.3 и
4.2 лаб. раб. №1).
Python поддерживает концепцию итерации контейнеров. Ее реализация осуществляется с помощью двух
методов, которые позволяют классам, определенным пользователем, поддерживать итерации.
Первый из этих методов – __iter__(),
примененный к объекту-контейнеру: container.__iter__(), обеспечивает
этому объекту поддержку итераций. Метод возвращает объект-итератор (англ. iterator) для
объекта-контейнера, что делает его итерабельным (или перечисляемым) объектом
(англ. iterable object). Если контейнер поддерживает несколько типов итерации, то для каждого вида
итерации должен быть определен свой итератор, например, для объекта структуры дерева, должны быть
определены два интератора – один для обхода дерева "сначала вниз", другой –
"сначала вправо".
Итераторы-объекты поддерживают следующие два метода, которые вместе
составляют протокол итератора:
Проверим, является ли строка "cat" итерабельным объектом:
>>>
from
collections
import
Iterable, Iterator
>>>
isinstance
(
"cat"
, Iterable)
True.
>>>
isinstance
(
"cat"
, Iterator)
False.
Поскольку строки являются итерабельными объектами, но не итераторами,
создадим для строки "cat" объект-итератор i:
>>>
i=
'cat'
.__iter__()
и, используя метод __iter__(), получим все символы этой строки:
>>>
i.__next__()
'c'
>>>
i.__next__()
'a'
>>>
i.__next__()
't'
>>>
i.__next__()
StopIteration.
Когда символы строки закончились, вырабатывается исключение
StopIteration.
Отметим, что при вызове для объекта-итератора метода
__iter__() новый объект-итератор не создается, а возвращается ссылка на
исходный объект:
>>>
id
(i)
33426320
>>>
id
(i.__iter__())
33426320
Вместо метода __next__() можно использовать
встроенную функцию next()
(см. подраздел 8.1 лаб. раб. №1).
Укажем, что все встроенные типы последовательностей
(см. подраздел 1.4) являются итерабельными типами.
Вместо метода __iter__() можно использовать
встроенную функцию iter(object[, sentinel])
(см. подраздел 8.1 лаб. раб. №1), которая возвращает
объект-итератор.
Встроенная функция hash(object)
(см. подраздел 8.1 лаб. раб. №1) возвращает
хэш-значение (англ. hash value) объекта (если он его имеет), представленное числом. Хэш-значения
используются для ускорения сравнения ключей словарей при их просмотре
(см. раздел 2 лаб. раб. №5). Если значения объектов
численно равны, то хэш-функция возвращает одинаковые значения (даже если объекты принадлежат
разным типам). Наример:
>>>
hash
(1)
1
>>>
hash
(1.0)
1
Объект является хэшируемым (англ. hashable), если он имеет
хэш-значение, которое не меняется в течение срока его существования. Для этого требуется, чтобы для
объектп был определен специальный метод __hash__(), и чтобы он мог быть
сравним с другими объектами, т.е. для него также должен быть определен специальный метод
__eq__() (см. операции сравнения в
подразделе 6.2 лаб. раб. №1).
Хэшируемые объекты принадлежат ABC-классу Hashable (см. табл. 1).
Хэшируемость объекта дает возможность ему быть использованым в
качестве ключа словаря (см. подраздел 2.1 лаб. раб. №5),
а также в качестве элементов множеств, поскольку их структуры данных внутренне используют
хэш-значения (см. раздел 1 лаб. раб. №5).
Все хэшируемые объекты являются неизменяемыми объектами языка Python.
В то время как ни один изменяемый контейнер не является хэшированным объектом.
Объекты, которые являются экземплярами пользовательских классов,
хэшируемы по умолчанию, они при сравнении дают неравентсво и их хэш-значениями являются значения
их 4id().
Специальный метод __hash__() вызывается при
выполнении встроенной функции hash() и при выполнении операций над
элементами хэшируемых контейнеров. Метод __hash__() должен возвращать
число. Единственным условием равенства объектов, является равенство их хэш-значений. Выше
установлено, что объекты 1 и 1.0 имеют одинаковые хэш-значения. Поэтому эти объекты равны:
>>>
1==1.0
True
Отметим, что функция hash() обрезает
возращаемое значение для методов __hash__() пользовательских классов до
значения 2**31 (4 байта) на 32-битных и 2**63 (8 байтов) на 64-битных платформах.
Если класс не определил метод __eq__(), он
не должен определять и метод __hash__(). Если в классе был определен
__eq__(), но не определен __hash__(), его
экземпляры не могут использоваться как элементы в хэшируемых коллекциях. Если класс определяет
изменяемые объекты и объявляяет метод __eq__(), он не должен объявлять
метод __hash__(), поскольку применение хэшируемых коллекций
предусматривает, чтобы их ключи были неизменяемыми объектами.
Определенные пользователями классы имеют методы
__eq__() и __hash__() по умолчанию. Сравнение
со всеми объектами дает неравнество (кроме сравнения объекта с самим собой) и
x.__hash__() возвращает соответствующее значение, такое что из
x == y следует, что x есть y
и hash(x) == hash(y).
Класс, который переопределяет __eq__() и не
определяет __hash__() будет иметь неявно установленное значение
__hash__(), равное None. Когда метод __hash__()
класса равен None, экземпляры этого класса вызовут ошибку TypeError при попытке программы получить
их хэш-значение, и будут правильно идентифицированы как нехэшируемые объекты при проверке
с помощью вызова функции isinstance(obj, collections.Hashable).
Класс, который переопределяет __eq__() и
нуждается в применении метода __hash__() родительского класса, должен в
явном виде указать это интерпретатору, устанавливая
__hash__ = <ParentClass>.__hash__.
Класс, который переопределяет __eq__() и
желает подавить поддержку хэширования, должен включить __hash__ = None в
описание класса.
Класс, который так определяет свой собсвтенный метод
__hash__(), что это вызывет ошибку TypeError, не будет правильно
идентифицирован вызовом функции isinstance(obj, collections.Hashable).
Рассмотрим теперь взаимосвязь между понятиями кэшируемости объектов и
их изменяемости. В языке Python все объекты, которые имеют хэш, являются неизменяемыми и ни один
изменяемый объект не имеет хэш. Это означает, что из хэшируемости объекта следует его
неизменяемость, а из изменяемости следует его нехэшируемость. Но эти понятия не равнозначны,
поскольку из неизменяемости объъекта не следует его хэшируемость.
Для примера определим, изменяемыми или неизменяемыми являются такие
объекты как строки и числа с плавающей точкой. Для этого воспользуемся
ABC-классом (см. раздел 1) и встроенной функцией
isinstance(object, classinfo)
(см. подраздел 8.1 лаб. раб. №1):
>>>
from
collections
import
Hashable
>>>
isinstance
(
'cat'
, Hashable)
True.
>>>
isinstance
(0.25, Hashable)
True.
Т.е. строки и числа сплавающей точкой являются хэшируемыми, а,
следовательно, неизменяемыми объектами (так же, как целые числа, комплекстные числа и данные
логического типа).
Последовательностями, т.е. объектами, относящимися к ABC-классу Sequence, в языке Python являются контейнеры (см. подраздел 1.1), которые поддерживают протокол последовательности (см. подраздел 1.4.1).
Чтобы объекты могли поддерживать протокол последовательности, требуется чтобы для них были определены следующие два специальных метода:
Проверим, определены ли эти методы для строк и целых чисел: Проверим, являются ли строки изменяемой последовательностью. Попробуем изменить второй символ
строки: Осуществить проверку, является ли некоторый объект неизменяемой последовательностью, можно и
по-другому, не привлекая протокол последовательности, а используя встроенную функцию
isinstance() (см. подраздел 8.1
лаб. раб. №1) и ABC-классы:
Sequence и MutableSequence
(см. раздел 1): К дополнительным последовательностям относятся следующие типы данных:
Все указанные выше типы последовательностей относятся к итерабельным типам данных
(см. подраздел 1.2). Ниже приводится список операций, которые являются общими
для этих последовательностей. Операции для последовательностей приводятся в порядке возрастания
приоритета (в скобках указаны соответствующие им специальные методы). S
и T обозначают последовательности одного типа,
x – элемент последовательности, i,
j, k и n –
целые числа. Отметим, что операции in и not in имеют тот
же приоритет, что и операции сравнения. Операции "+" и "*" имеют такой же приоритет, как и
соответствующие операции над числами. Оператор цикла for in представляет собой цикл обхода заданной
последовательности элементов, которая должна быть итерабельным объектом, и выполнения в своем теле
различных операций над ними. Имеет следующую форму: Списки в языке Python имеют тип (класс) list и представляют собой
последовательности элементов, значениями которых, в отличие от строк, могут быть данные разных
типов, в том числе и списки. Другим отличием списков от строк является то, что они являются
изменяемыми объектами (см. раздел 2 лаб. раб. №2),
т.е. значения элементов списка можно изменять без создания нового списка. Списки в языке Python
похожи на индексные массивы языка Jaxascript.
Существует два подхода для создания списка:
В логическом контексте пустой список ([]) соответствует False, любой
другой – True. Теперь проверим, являются ли списки изменяемыми последовательностями
(см. также проверку для строк в подразделе 1.4.1): A CLASS="out" HREF="#beg" style="margin-left:80%">Оглавление
Будучи последовательностями, списки поддерживают все операции, которые являются общими для всех
типов последовательностей (см. подраздел 1.4.2):
Поскольку списки являются изменяемыми последовательностями
(см. подраздел 2.1), они поддерживают все операции, которые являются общими для
любой изменяемой последовательности s
>>>
'Cat'
.__len__()
3
>>>
(125).__len__()
AttributeError: 'int' object has no attribute '__len__'
>>>
'Cat'
.__getitem__(1)
'a'
Результаты показывают, что строки поддерживают оба метода протокола и
поэтому являются последовательностями. А целые числа не поддерживают уже первый метод (второй
– тоже) и, следовательно, не принадлежат классу Sequence. Отметим,
что доступ к значениям элементов последовательности по их номерам означает упорядоченность значений
последовательностей.
Чтобы объекты относилисть к ABC-классу
Mutable Sequence – изменяемой последовательности, необходимо,
чтобы для них были определены дополнительно еще два специальных метода:
>>>
'Cat'
.__setitem__(1,
'u'
)
AttributeError: 'str' object has no attribute '__setitem__'
Т.е., строки не имеют атрибута __setitem__
и, следовательнло, не принадлежат к классу изменяемых последовательностей. Это неизменяемые
объекты.
К четырем основным правилам предъявляются такде такие требования:
>>>
from
collections
import
Sequence, MutableSequence
>>>
isinstance
(
'cat'
, Sequence)
True.
>>>
isinstance
(
'cat'
, MutableSequence)
False.
Т.е. получаем тот же результат – строки действительно являются
неизменяемыми последовательностями.
К базовым последовательностям относяться следующие типы данных:
1.4.2 Операции, общие для последовательностей
Для каждой операции указана ссылка на соответствующую ей операцию над
строками, которые были рассмотрены в разделе 2 лаб. раб №2:
>>>
'1020304050'
.index(
'0'
,2)
3;
>>>
'1020304050'
.count(
'0'
)
5.
Последовательности одинаковых типов также поддерживают операции
сравнения. Две последовательности будут равны, если имеют одинаковый тип, равное число элементов и
все элементы одной последовательности равны соответствующим элементам другой последовательности.
Единственной операцией, которая поддерживается неизменяемыми
последовательностями и не поддерживается изменяемыми последовательностями, является вызов
встроенной функции hash() (см. подраздел 1.3). Эта
поддержка позволяет использовать неизменяемые последовательности, такие как кортежи
(см. разделе 1 лаб. раб. №4), в качестве ключей словарей
(см. подраздел 2.1 лаб. раб. №5) и сохранять их
в экземплярах объектов set и frozenset
(см. раздел 1 лаб. раб. №5).
Помимо применения последовательностей встроенных типов пользователь
может, используя выше приведенные методы правил, создавать собственные типы данных, являющиеся
последовательностями.
1.5 Оператор цикла for in
for <элемент> in
<последовательность элементов>:
<блок>
.
При выполнении оператора for in
интерпертатор осуществляет следующие действия. Сначала для итерабельного объекта вызывает метод
__iter__, чтобы получить его итератор. Затем последовательно для
итератора вызывается метод __next__, который возвращает очередной
элемент последовательности, до тех пор, пока не возникнет исключение
StopIteration. Эти действия соответствуют протоколу итераций (см.
подраздел 1.2).
Поскольку строка представляет собой последовательность и,
следовательно, является итерабельным типом (см. подраздел 1.2), то можно
использовать оператор for in для перебора всех символов строки и вывода
их с дополнительным пробелом:
>>>
s=
'Микропроцессор'
>>>
for
l
in
s:
print
(l,end=
' '
)
М и к р о п р о ц е с с о р.
По умолчанию функция print() после вывода
сообщения на экран осуществляет перевод строки. Однако с помощью параметра
end=<символ>можно указать символ, которым будет заканчиваться вывод.
В данном случае это пробел.
2 Списки
2.1 Создание списков
>>>
a_list=[0.01,
'Web'
, [1,2,3]];
>>>
a_list
[0.01, 'Web', [1, 2, 3]];
>>>
a_list=[]
>>>
a_list
[];
>>>
b_list=
list
(
'Python'
)
>>>
b_list
['P', 'y', 't','h','o','n'].
>>>
type
(b_list)
<'class 'list'>.
Если аргументом конструктора является объект типа list, то создается
копия списка, если аргумент не указан – создается пустой список.
Проверим, являются ли списки итерабельными объектами
(см. подраздел 1.2):
>>>
from
collections
import
Iterable
>>>
isinstance
([1,2,3], Iterable)
True.
Все итерабельные объекты поддерживают следующие встроенные функции:
>>>
all
([1,2,3])
True,
>>>
all
([])
True,
>>>
all
([1,2,0])
False;
>>>
any
([1,2,0])
True,
>>>
any
([])
False,
>>>
any
([0,0,0])
False;
>>>
from
collections
import
Sequence, MutableSequence
>>>
isinstance
(a_list, Sequence)
True,
>>>
isinstance
(a_list, MutableSequence)
True.
Действительно, списки являются изменяемыми последовательностями.
2.2 Поддержка списками операций, общих для последовательностей
>>>
'Web'
in
a_list
True
>>>
1
in
a_list
False;
>>>
id
(a_list)
36877496
>>>
a_list+=[0x25]
>>>
a_list
[0.01, 'Web', [1, 2, 3], 37]
>>>
id
(a_list)
36877496
Видно, что для изменяемых типов операция конкатенации не создает
нового объекта, поскольку значение функции id(), т.е. адрес объекта, остается неизменным. Меняется
только его значение. В отличие от неизменяемого типа, для которого операция конкатенации создает
новый объект с другим адресом:
>>>
s=
'cat'
>>>
id
(s)
35173984
>>>
s+=
's'
>>>
s
'cats'
>>>
id
(s)
37084672;
>>>
[1,2,3]*2
[1, 2, 3, 1, 2, 3];
>>>
a_list[1]
'Web'
>>>
a_list[-2]
[1, 2, 3].
Для доступа к элементам списка, которые сами являются списками,
необходимо указывать в квадратных скобках два индекса – первый для элементов основного списка,
второй – для элементов внутреннего списка. Степень вложенности списков не ограничена:
>>>
a_list[2][0]
1;
>>>
a_list[:2]
[0.01, 'Web']
>>>
a_list[::-1]
[37,[1, 2, 3], 'Web', 0.01];
>>>
a_list[1:4:2]
['Web', 37];
>>>
len
(a_list)
4;
>>>
min
([4,2,7])
2
>>>
min
([
'cat'
,
'www'
])
'cat'
>>>
min
([[11,2],[10,7],[26,0]])
[10, 7]
>>>
min
([
'cat'
,77])
TypeError: unorderable types: int() >
str();
>>>
max
([1,9],[3,1])
[3, 1];
>>>
[0,1,1,0,2,2,0,3,3,0,4,4].index(0,4)
6.
Если искомого элемента нет в списке:
[0,1,1,0,2,2,0,3,3,0,4,4].index(5),
выводится сообщение об ошибке:
ValueError: 5 is not in list,
т.е. элемент 5 не найден в списке;
>>>
[0,1,1,0,2,2,0,3,3,0,4,4].count(0)
4
2.3 Поддержка списками операций, общих для изменяемых последовательностей
Объекты класса list, кроме описанных выше, имеют также метод list.sort(), который выполняет сортировку элементов списка (будет рассмотрен в подразделе 1.4 лаб. раб. №6, поскольку в общем случае при его использовании требуется знать, как создаются пользовательские функции).
С работой со списками связаны и два метода класса str:
Разработать программу на языке Python, которая выполняет следующее:
Номер п/п | Операции | Строка | Фукции | |
---|---|---|---|---|
Содержимое | Разделитель | |||
1 | 1,3,6 | слова | 1 | 1 |
2 | 2,4,5 | числа | 2 | 2 |
3 | 3,7,8 | слова | 3 | 3 |
4 | 4,6,1 | числа | 4 | 1 |
5 | 5,2,8 | слова | 5 | 2 |
6 | 7,2,1 | числа | 1 | 3 |
7 | 3,8,4 | слова | 2 | 1 |
8 | 1,3,6 | числа | 3 | 2 |
9 | 2,4,5 | слова | 4 | 3 |
10 | 3,7,8 | числа | 5 | 1 |
11 | 4,6,1 | слова | 1 | 2 |
12 | 5,2,8 | числа | 2 | 3 |
13 | 7,2,1 | слова | 3 | 1 |
14 | 3,8,4 | числа | 4 | 2 |
15 | 1,3,6 | слова | 5 | 3 |
16 | 2,4,5 | числа | 1 | 1 |
17 | 3,7,8 | слова | 2 | 2 |
18 | 4,6,1 | числа | 3 | 3 |
19 | 5,2,8 | слова | 4 | 1 |
20 | 7,2,1 | числа | 5 | 2 |