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

Тема: ABC-классы. Списки

Цель: Рассмотрение особенностей использования ABC-классов и списков

ОГЛАВЛЕНИЕ

1 ABC-классы
1.1 Контейнеры
1.2 Итераторы
1.3 Хэширование объектов
1.4 Последовательности
1.4.1 Протокол последовательности
1.4.2 Операции, общие для последовательностей
1.5 Оператор цикла for in
2 Списки
2.1 Создание списков
2.2 Поддержка списками общих свойств последовательностей
2.3 Поддержка списками операций, общих для изменяемых последовательностей
2.4 Использование других методов при работе со списками
Индивидуальные задания

Оглавление

1 ABC-классы и протоколы

Помимо типов (классов) данных программы язык Python имеет и так называемые абстрактные базовые классы (англ. abstract base classes) – ABC-классы, каждый из которых объединяет данные нескольких различных типов, имеющих общие свойства. Например, к ABC-классу контейнер (англ. container) относятся все типы данных, которые имеют в своем составе более одного объекта (см. подраздел 1.1).
В табл. 1 приведены названия ABC-классов, классы, их порождающие, и атрибуты.

Таблица 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.

 Оглавление

1.1 Контейнеры

Контейнеры – это объекты, которые содержат произвольное число других объектов. Они принадлежат 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).

Оглавление

1.2 Итераторы

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), которая возвращает объект-итератор.

Оглавление

1.3 Хэширование объектов

Встроенная функция 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.

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

Оглавление

1.4 Последовательности

Последовательностями, т.е. объектами, относящимися к ABC-классу Sequence, в языке Python являются контейнеры (см. подраздел 1.1), которые поддерживают протокол последовательности (см. подраздел 1.4.1).

Оглавление

1.4.1 Протокол последовательности

Чтобы объекты могли поддерживать протокол последовательности, требуется чтобы для них были определены следующие два специальных метода:

Проверим, определены ли эти методы для строк и целых чисел:

>>> '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__ и, следовательнло, не принадлежат к классу изменяемых последовательностей. Это неизменяемые объекты.
К четырем основным правилам предъявляются такде такие требования:

Осуществить проверку, является ли некоторый объект неизменяемой последовательностью, можно и по-другому, не привлекая протокол последовательности, а используя встроенную функцию isinstance() (см. подраздел 8.1 лаб. раб. №1) и ABC-классы: Sequence и MutableSequence (см. раздел 1):

>>> from collections import Sequence, MutableSequence
>>> isinstance ( 'cat' , Sequence)
True.
>>> isinstance ( 'cat' , MutableSequence)
False.

Т.е. получаем тот же результат – строки действительно являются неизменяемыми последовательностями.
К базовым последовательностям относяться следующие типы данных:

К дополнительным последовательностям относятся следующие типы данных:

Оглавление

1.4.2 Операции, общие для последовательностей

Все указанные выше типы последовательностей относятся к итерабельным типам данных (см. подраздел 1.2). Ниже приводится список операций, которые являются общими для этих последовательностей. Операции для последовательностей приводятся в порядке возрастания приоритета (в скобках указаны соответствующие им специальные методы). S и T обозначают последовательности одного типа, x – элемент последовательности, i, j, k и n – целые числа.
Для каждой операции указана ссылка на соответствующую ей операцию над строками, которые были рассмотрены в разделе 2 лаб. раб №2:

Отметим, что операции in и not in имеют тот же приоритет, что и операции сравнения. Операции "+" и "*" имеют такой же приоритет, как и соответствующие операции над числами.
Последовательности одинаковых типов также поддерживают операции сравнения. Две последовательности будут равны, если имеют одинаковый тип, равное число элементов и все элементы одной последовательности равны соответствующим элементам другой последовательности.
Единственной операцией, которая поддерживается неизменяемыми последовательностями и не поддерживается изменяемыми последовательностями, является вызов встроенной функции hash() (см. подраздел 1.3). Эта поддержка позволяет использовать неизменяемые последовательности, такие как кортежи (см. разделе 1 лаб. раб. №4), в качестве ключей словарей (см. подраздел 2.1 лаб. раб. №5) и сохранять их в экземплярах объектов set и frozenset (см. раздел 1 лаб. раб. №5).
Помимо применения последовательностей встроенных типов пользователь может, используя выше приведенные методы правил, создавать собственные типы данных, являющиеся последовательностями.

 Оглавление

1.5 Оператор цикла for in

Оператор цикла 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 Списки

Списки в языке Python имеют тип (класс) list и представляют собой последовательности элементов, значениями которых, в отличие от строк, могут быть данные разных типов, в том числе и списки. Другим отличием списков от строк является то, что они являются изменяемыми объектами (см. раздел 2 лаб. раб. №2), т.е. значения элементов списка можно изменять без создания нового списка. Списки в языке Python похожи на индексные массивы языка Jaxascript.

Оглавление

2.1 Создание списков

Существует два подхода для создания списка:

В логическом контексте пустой список ([]) соответствует False, любой другой – True.
Проверим, являются ли списки итерабельными объектами (см. подраздел 1.2):

>>> from collections import Iterable
>>> isinstance ([1,2,3], Iterable)
True.
Все итерабельные объекты поддерживают следующие встроенные функции:

Теперь проверим, являются ли списки изменяемыми последовательностями (см. также проверку для строк в подразделе 1.4.1):

>>> from collections import Sequence, MutableSequence
>>> isinstance (a_list, Sequence)
True,
>>> isinstance (a_list, MutableSequence)
True.

Действительно, списки являются изменяемыми последовательностями.

A CLASS="out" HREF="#beg" style="margin-left:80%">Оглавление

2.2 Поддержка списками операций, общих для последовательностей

Будучи последовательностями, списки поддерживают все операции, которые являются общими для всех типов последовательностей (см. подраздел 1.4.2):

Оглавление

2.3 Поддержка списками операций, общих для изменяемых последовательностей

Поскольку списки являются изменяемыми последовательностями (см. подраздел 2.1), они поддерживают все операции, которые являются общими для любой изменяемой последовательности s:

Оглавление

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

Объекты класса list, кроме описанных выше, имеют также метод list.sort(), который выполняет сортировку элементов списка (будет рассмотрен в подразделе 1.4 лаб. раб. №6, поскольку в общем случае при его использовании требуется знать, как создаются пользовательские функции).

С работой со списками связаны и два метода класса str:

Оглавление

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

Разработать программу на языке Python, которая выполняет следующее:

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

Номер
п/п
ОперацииСтрока Фукции
Содержимое Разделитель
11,3,6слова11
22,4,5числа22
33,7,8слова33
44,6,1числа41
55,2,8слова52
67,2,1числа13
73,8,4слова21
81,3,6числа32
92,4,5слова43
103,7,8числа51
114,6,1слова12
125,2,8числа23
137,2,1слова31
143,8,4числа42
151,3,6слова53
162,4,5числа11
173,7,8слова22
184,6,1числа33
195,2,8слова41
207,2,1числа52

Оглавление `