С. В. Вабищевич инженерпрограммист компании ооо ск хайникс мемори солюшнс Восточная Европа
Скачать 1.28 Mb.
|
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( – Второй вариант нежелателен, однако порой необходим при мно- жественном наследовании. 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. ТЕСТИРОВАНИЕ ПРОВЕРКА АРГУМЕНТОВ |