Главная страница

С. В. Вабищевич инженерпрограммист компании ооо ск хайникс мемори солюшнс Восточная Европа


Скачать 1.28 Mb.
НазваниеС. В. Вабищевич инженерпрограммист компании ооо ск хайникс мемори солюшнс Восточная Европа
Дата22.11.2021
Размер1.28 Mb.
Формат файлаpdf
Имя файлаKositsin.pdf
ТипУчебно-методическое пособие
#278468
страница5 из 9
1   2   3   4   5   6   7   8   9
True.
Сравнение и хеширование
Сравнение и хеширование реализовано методами __
eq__,
__
ne__, __le__, __lt__, __ge__, __gt__ и __hash__.
1
https://docs.python.org/2.7/reference/datamodel.html
2
https://docs.python.org/3.7/reference/datamodel.html

65
Замечание. Явно реализовывать все операторы не нужно. Доста- точно метода __
eq__ и одного из методов сравнения, а также декора- тора
functools.total_ordering.
Если класс переопределяет метод __
eq__, то для метода __hash__ должно выполняться одно из следующих утверждений:

__hash__ явно реализован;
– явно присвоено __
hash__ = None (для изменяемых объектов- коллекций);
– явно присвоено __
hash__ =
.__hash__ (если не из- менен).
Если метод __
eq__ для двух объектов возвращает True, то __hash__ объектов должен также совпадать.
Также метод __
eq__ должен либо бросать TypeError, либо воз- вращать
NotImplemented, если передан объект некорректного для сравнения типа.
Проверка на тип в методе __
eq__ обязательна, поскольку требуе- мых атрибутов для сравнения у другого класса может не быть.
Реализация оператора «==» не означает, что «!=» будет работать корректно. Для этого следует явно перегрузить метод __
ne__.
Операции с числами
Можно переопределить любые математические операции:
+ (__
add__), – (__sub__), * (__mul__), @ (__matmul__), / (__truediv__), //
(__
floordiv__) и пр.
Помимо таких операций есть еще методы- компаньоны. На- пример для сложения они называются __
radd__ и __iadd__. Метод
__
radd__ вызывается, когда у левого операнда метод __add__ не реа- лизован, а __
iadd__ – для операции сложения inplace («+=»).
На примере сложения (как и других арифметических операций) рассмотрим разницу между методами __
add__ («+») и __iadd__ («+=»).
Так, первый должен создавать копию объекта, а второй – модифи- цировать исходный объект и возвращать его.
Также есть возможность переопределить операции приведения к типам
complex, float и int, округления round и взятия модуля abs.
Прочие методы
Методы __
getitem__, __setitem__, __delitem__ – работа с ин- дексами (оператор []).
Методы __
iter__, __reversed__, __contains__ отвечают за итериро- вание и проверку вхождения.

66
Методы __
instancecheck__ и __subclasscheck__ отвечают за про- верку типа.
Метод __
missing__ вызывается словарем, если запрошенный ключ отсутствует (переопределен в defaultdict).
3.3. НАСЛЕДОВАНИЕ
Рассмотрим небольшой пример наследования:
>>> class Animal(object):
>>> pass
>>>
>>> class Cat(Animal):
>>> pass
>>>
>>> class Dog(Animal):
>>> pass
>>>
>>>bob = Cat()
ПРОВЕРКА ТИПА
Проверка типа с учетом наследования производится с по- мощью функции
isinstance:
>>> isinstance(bob, Cat) # True
>>> isinstance(bob, Animal) # True
>>> isinstance(bob, Dog) # False
>>> type(bob) is Animal # False; type is Cat
Все объекты наследуются от
object:
>>> isinstance(bob, object) # True
Замечания. Метод isinstance вторым аргументом принимает также котртеж (tuple) допустимых типов.
Для корректной проверки, является ли объект
x целым числом, в Python 2.x следует использовать
isinstance(x, (int, long)).
ИЕРАРХИЯ НАСЛЕДОВАНИЯ
Посмотреть иерархию наследования можно с помощью метода
mro() или атрибута __mro__:

67
>>> Cat.mro()[,
, ]
Для проверки того, что некоторый класс является подклассом другого класса, используется функция
issubclass:
>>> issubclass(Cat, Animal) # True
НАСЛЕДОВАНИЕ МЕТОДОВ И АТРИБУТОВ
Наследование классов позволяет не переписывать неко- торые общие для подклассов методы, оставив их в базовом классе.
>>> class A(object):
>>>
X = 1
>>>
def f(self):
>>>
print(“Called A.f()”, end= )
>>>
>>> class B(A):
>>>
pass
>>>
>>> b = B()
>>> b.f()
>>> print(b.X) # Called A.f() 1
ПЕРЕОПРЕДЕЛЕНИЕ АТРИБУТОВ
Поведение в дочернем классе, разумеется, можно перео- пределить.
>>> class A(object):
>>>
def f(self):
>>>
print(“Called A.f()”)
>>>
>>> class B(A):
>>>
def f(self):
>>>
print(“Called B.f()”)
>>>
>>> a, b = A(), B()
>>> a.f()
>>> b.f()
Called A.f()
Called B.f()

68
ЧАСТИЧНОЕ ПЕРЕОПРЕДЕЛЕНИЕ АТРИБУТОВ
>>> class A(object):
>>>
NAME = “A”
>>>
>>>
def f(self):
>>>
print(self.NAME)
>>>
>>> class B(A):
>>>
NAME = “B”
>>>
>>> a, b = A(), B()
>>> a.f()
>>> b.f()
A
B
ПЕРЕОПРЕДЕЛЕНИЕ КОНСТРУКТОРА
>>> class A(object):
>>>
def __init__(self):
>>> self.x = 1
>>>
>>> class B(A):
>>>
def __init__(self):
>>> self.y = 2
>>>
>>> b = B()
>>> print(b.x) # AttributeError
>>> print(b.y) # 2
ВЫЗОВ МЕТОДОВ БАЗОВОГО КЛАССА
Конструктор базового класса также должен быть вызван.
К методам базовых классов можно обращаться, как

super(, self).method(…)
.method(self, …)
Второй вариант нежелателен, однако порой необходим при
мно-
жественном наследовании.

69
Замечания. В Python 3 метод super внутри класса можно вызы- вать без параметров – будут использованы значения по умолчанию.
Метод
super возвращает специальный proxy- объект, а потому следует обратиться к некоторым «магическим» методам. Но обра- титься по индексу к нему невозможно.
ВЫЗОВ КОНСТРУКТОРА БАЗОВОГО КЛАССА
>>> class A(object):
>>>
def __init__(self):
>>> self.x = 1
>>>
>>> class B(A):
>>>
def __init__(self):
>>> super(B, self).__init__()
>>>
# A.__init__(self) – второй нежелательный
вариант:
>>> self.y = 2
>>>
>>> b = B()
>>> print(b.x, b.y)
1 2
МНОЖЕСТВЕННОЕ НАСЛЕДОВАНИЕ
В Python допустимо множественное наследование. Не все схемы наследования допустимы.
Наиболее распространенные виды:
– ромбовидное наследование;
– добавление Mixin- классов (реализация некоторого функцио- нала, выраженная через другие методы).
Пример ромбовидного наследования (рис. 5):
>>> class A(object):
>>>
pass
>>>
>>> class B(object):
>>>
pass
>>>
>>> class C(A, B):
>>>
pass

70
object
C
А
B
Рис. 5. Пример ромбовидного наследования
ПОРЯДОК РАЗРЕШЕНИЯ ИМЕН
Если атрибут отсутствует в классе, предпринимается по- пытка найти его в базовых классах согласно MRO (Method resolution order). Алгоритм поиска – C 3-линеаризация
1
. Примеры последова- тельности разрешения имен указаны на рис. 6 и 7.
object (3)
C (0)
А (1)
B (2)
Рис. 6. Пример последовательности разрешения имен при C 3-линеаризации
O (6)
B (1)
C (0)
F (5)
D (3)
C (2)
C (0)
A (0)
E (4)
Рис. 7. Пример последовательности разрешения имен при C 3-линеаризации
1
https://en.wikipedia.org/wiki/C3_linearization

71
ПРИМЕР НЕДОПУСТИМОЙ ИЕРАРХИИ
В случае некорректной иерархии произойдет
TypeError:
«Cannot create a consistent method resolution order (MRO)». Такая ие- рархия не линеаризуема (рис. 8).
O
A
C
E
B
D
Рис. 8. Пример недопустимой иерархии
ПРОЦЕДУРА ПОИСКА АТРИБУТОВ
Рассмотрим пример класса:
>>> class C(object):
>>>
pass
>>>
>>> c = C()
>>> c.attribute
Поиск атрибута «attribute» происходит следующим образом:
– поискать атрибуты через механизм дескрипторов;
– поискать атрибут в c.__
dict__;
– поискать атрибут в C.__
dict__;
– поискать атрибут в родительских классах согласно MRO;
– raise
AttributeError.
Замечание. __dict__ – словарь всех атрибутов объекта.
СЛУЧАЙ МНОЖЕСТВЕННОГО НАСЛЕДОВАНИЯ
Proxy- объект
super вернет только один основной базовый класс. Вызвать конструктор всех базовых классов нужно явно.

72
>>> class A(object):
>>>
pass
>>>
>>> class B(object):
>>>
pass
>>>
>>> class C(A, B):
>>>
def __init__(self):
>>>
A.__init__(self)
>>>
B.__init__(self)
ВСТРОЕННЫЕ БАЗОВЫЕ КЛАССЫ
В модуле
collections (collections.abc с Python 3.3) нахо- дятся некоторые базовые классы. Их используют:
– для проверки типов (Callable, Iterable, Mapping);
– создания собственных типов (коллекций).
В данных классах содержатся абстрактные (требующие реали- зации) методы, а также mixin- методы, выраженные через другие.
Пример. Класс
Sequence требует наличия реализации методов
__
getitem__ и __len__, а методы __contains__, __iter__, __reversed__,
index и count реализует, обращаясь к __getitem__ и __len__.
Вывод: для проверки возможности вызвать объект или итери-
роваться по нему следует проверить, что он наследуется от данных базовых классов.
3.4. ОБРАБОТКА ОШИБОК
ТИПЫ ОШИБОК
Ошибки, вообще говоря, бывают:
синтаксические (
SyntaxError): переменная названа «for», не- корректный отступ;
– исключения:
некорректный индекс (
IndexError);
деление на 0 (ZeroDivisionError);
другие.
Базовый класс для почти всех исключений –
Exception. Од- нако есть так называемые control flow-исключения:
SystemExit,

73
KeybordInterrupt, GeneratorExit – у них базовый класс Base-
Exception.
Такое разделение нужно для того, чтобы была возможность не перехватывать ислючения, влияющие на поток управления, по- скольку это может привести к некорректному ее поведению.
Замечание. КлассException, в свою очередь, унаследован от Ba-
se Exception (Python 2.x)
1
, (Python 3.x)
2
ПРИМЕР РАБОТЫ С ИСКЛЮЧЕНИЯМИ
>>> class MyValueError(ValueError):
>>>
pass
>>>
>>> def crazy_exception_processing():
>>>
try:
>>>
raise MyValueError(‘incorrect value’)
>>>
except (TypeError, ValueError) as e:
>>>
print(e)
>>>
raise
>>>
except Exception:
>>>
raise Exception()
>>>
except:
>>>
pass
>>>
else:
>>>
print(‘no exception raised’)
>>>
finally:
>>>
return –1
Можно создавать собственные исключения – их следует наследо- вать от
Exception либо его потомком (например, ValueError).
Исключение бросается с помощью выражения
raise <исключение>.
Основной блок обработки исключения начинается
try и закан- чивается любым из выражений –
except, else или finally.
В блоке
except обрабатывается исключение определенного типа и при необходимости бросается либо то же, либо иное исключение.
Блок
except можно специфицировать одним или несколькими ис- ключениями (в скобках через запятую), а присвоить локальной пе- ременной объект исключения можно выражением
as.
1
https
://docs.python.org/2.7/library/exceptions.html
2
https
://docs.python.org/3.7/library/exceptions.html

74
Для обработки всех исключений стоит указывать тип
Excep -
tion.
Замечание. Блок except без указания типа использовать нуж- но
крайне редко, иначе поток управления может быть некорректно изменен.
В случае исключения в блоке
try интерпретатор будет последова- тельно подбирать подходящий блок
except. Если ни один не подой- дет или ни одного блока нет, исключение будет проброшено на уро- вень выше (по стеку вызовов).
Блок
else выполнится, если в блоке try исключений не было.
Если исключения не было, по окончании блока
try,
– выполняется блок
else;
– выполняется блок
finally.
Если исключение в
try было,
– выполняется подходящий блок
except, если есть,
– исключение сохраняется;
– выполняется блок
finally;
– сохраненное исключение бросается выше по стеку вызовов.
Очень тонкий момент: если в блоке finally есть return, break или
continue, сохраненное исключение сбрасывается. В блоке finally оно недоступно.
Замечание. Исключение, присвоенное с помощью инструкции as в одном из блоков
except, доступно только в нем. Переменная с та- ким именем за пределами блока
except доступна не будет.
ОБРАБОТКА ИСКЛЮЧЕНИЙ
В случае возникновения исключения в блоке
except, else или
finally бросается новое исключение, а старое либо присоединя- ется (Python 3.x), либо сбрасывается (Python 2.x).
Сохраненное исключение можно получить, вызвав
sys.exc_
info() (кроме блока finally). Функция вернет тройку: тип исклю- чения, объект исключения и
traceback – объект, хранящий инфор- мацию о стеке вызовов (обработать его можно с помощью модуля
traceback).
У исключений есть атрибуты типа message, однако набор атри- бутов различен для разных типов. Преобразование к строке не га-
рантирует получения полной информации о типе ошибки и сооб- щении.

75
Важно! Старайтесь сделать блок try- except как можно меньше и локализовать там только одну возможную ошибку. Это упростит понимание, где и как именно произошла ошибка, а также не позво- лит пропустить ошибку, которая была неожиданной.
ОСОБЕННОСТИ РАБОТЫ С PYTHON 2
Если во время обработки исключения его нужно передать выше по стеку вызовов или бросить новое исключение, сохранив ин- формацию о старом, можно использовать специальный синтаксис
raise.
>>> def process_exception(exc_type):
>>>
try:
>>>
raise exc_type()
>>>
except ValueError:
>>>
# some actions here
>>>
exc_type, exc_instance, exc_traceback = sys.exc_info()
>>>
# raise other exception with original
traceback
>>>
raise Exception, Exception(), exc_
traceback
>>>
except Exception:
>>>
# some actions here
>>>
raise # re- raise the same exception
ОСОБЕННОСТИ РАБОТЫ С PYTHON 3
В Python 3 исключение доступно также через вызов
sys.
exc_info().
Если во время обработки будет брошено новое исключение, ори- гинальное исключение будет присоединено к новому и сохранено в атрибутах
__cause__ (явно) и __context__ (неявно), а оригинальный
traceback – в атрибуте __traceback__.
Бросить новое исключение, явно сообщив информацию о старом или явно указав исходный traceback, можно так:
>>> raise Exception() from original_exc
>>> raise Exception().with_traceback(original_tb)
Замечание. Значение original_exc может быть None – в таком слу- чае контекст явно присоединен не будет.

76
ПОДХОДЫ К ОБРАБОТКЕ ОШИБОК
Look Before You Leap (LBYL) – более общий и читаемый:
>>> def get_second_LBYL(sequence):
>>> if len(sequence) > 2:
>>> return
sequence[1]
>>> else:
>>> return
None
Easier to Ask for Forgiveness than Permission (EAFP) – не тратит время на проверку:
>>> def get_second_EAFP(sequence):
>>> try:
>>> return
sequence[1]
>>> except
IndexError:
>>> return
None
ПРЕДУПРЕЖДЕНИЯ
Помимо исключений в Python есть и предупреждения (мо- дуль
warnings). Они не прерывают поток выполнения программы, а лишь явно указывают на нежелательное действие.
Примеры предупреждений:

DeprecationWarning – сообщение об устаревшем функционале;

RuntimeWarning – некритичное сообщение о некорректном значении.
>>> def deprecation(message):
>>> warnings.warn(message, DeprecationWarning,
… stacklevel=2)
3.5. ПРОФИЛИРОВАНИЕ
Для замера времени исполнения используйте модуль
timeit.
>>> import timeit
>>> timeit.timeit(‘”-”.join(str(n) for n in
range(10000))’, number=1000)
2.0277862698763673

77
>>> timeit.timeit(‘”-”.join(str(n) for n in
list(range(10000)))’, number=1000)
2.269286573095144
Замечание. Также доступна функция repeat (повторять экспери- мент несколько раз).
Для профилирования есть модули
cProfile и Profile.
>>> import cProfile # or Profile – pure Python
implementation
>>> profiler = cProfile.Profile()
>>> profiler.run_call(calculate_binomial_mean, 10, 0.5)
# another way: run(‘calculate_binomial_mean(10, 0.5)’)
>>> profiler.print_stats()
Вызов выведет статистику по времени выполнения функции, в том числе по всем вложенным (если есть).
Библиотеки для профилирования:
– просмотр времени выполнения каждой строки: line_profiler
1
;
– использование памяти: memory_profiler
2
и др.;
– визуализация профилирования: SnakeViz
3
;
– прочие инструменты: ссылка
4 3.6. ТЕСТИРОВАНИЕ
ПРОВЕРКА АРГУМЕНТОВ
1   2   3   4   5   6   7   8   9


написать администратору сайта