С. В. Вабищевич инженерпрограммист компании ооо ск хайникс мемори солюшнс Восточная Европа
Скачать 1.28 Mb.
|
Пример менеджера контекста >>> class ContextManager(object): >>> def __init__(self): >>> print(‘__init__()’) >>> >>> def __enter__(self): >>> print(‘__enter__()’) >>> return ‘some data’ >>> >>> def __exit__(self, exc_type, exc_val, exc_tb): >>> print(‘__exit__({}, {})’.format( >>> exc_type.__name__, exc_val)) >>> >>> with ContextManager() as c: >>> print(‘inside context “%s”’% c) Менеджер контекста работает следующим образом: – создается и инициализируется (метод __init__); – организуется вход в контекст (метод __enter__) и возвращается объект контекста (в примере с файлом – объект типа file); – выполняются действия внутри контекста (внутри блока with); – организуется выход из контекста с возможной обработкой ис- ключений (метод __exit__). В примере будет выведено следующее: __init__() __enter__() inside context “some data” __exit__(None, None) Замечания. Если исключения не произошло, то параметры, передаваемые в функцию __exit__ – тип, значение исключения и traceback – имеют значения None. Менеджер контекста, реализуемый функцией open, по выходе из контекста просто вызывает метод close (см. декоратор contextlib. closing). 90 Менеджеры контекста используются: – для корректной, более простой и переносимой обработки ис- ключений в некотором блоке кода; – управления ресурсами. Декоратор contextlib.contextmanager позволяет создать менеджер контекста из функции- генератора, что значительно упрощает син- таксис. Менеджер контекста из генератора >>> @contextlib.contextmanager >>> def get_context(): >>> print(‘__enter__()’) >>> try: >>> yield ‘some data’ >>> finally: >>> print(‘__exit__()’) >>> >>> with get_context() as c: >>> print(‘inside context “%s”’% c) __enter__() inside context “some data” __exit__() 3.10. РАБОТА С АТРИБУТАМИ ДИНАМИЧЕСКИЕ АТРИБУТЫ Python позволяет создавать, изменять и удалять атрибу- ты run- time. За это отвечают следующие магические методы и гло- бальные функции: – получение атрибута по имени: __ getattr__, __getattribute__ или функция getattr; – присваивание атрибута по имени: __setattr__ или функция setattr; – удаление атрибута по имени: __delattr__ или функция delattr; – проверка наличия атрибута: функция hasattr (функция- обертка над getattr). 91 Напоминание. Некоторые объекты являются readonly, например добавлять или удалять атрибуты object нельзя. Замечание. Изменять атрибуты можно (не рекомендуется), мо- дифицируя __dict__. ПРИМЕР РАБОТЫ С АТРИБУТАМИ >>> class A(object): >>> def f(self): >>> pass >>> >>>a = A() >>> >>> assert hasattr(a, ‘f’) and hasattr(A, ‘f’) # ‘f’ is a class attribute >>> assert getattr(a, ‘g’, None) is None # no such attribute -> default >>> >>>setattr(a, ‘x’, 2) # a.x = 2 >>> assert getattr(a, ‘x’) == 2 # assert a.x == 2 >>> assert not hasattr(A, ‘x’) # attribute has been set for instance only >>> >>>delattr(a, ‘x’) # del a.x >>> assert not (hasattr(a, ‘x’) or hasattr(A, ‘x’)) МАГИЧЕСКИЕ МЕТОДЫ РАБОТЫ С АТРИБУТАМИ Пример реализации методов работы с атрибутами >>> class Proxy(object): >>> def __init__(self, inner_object): >>> self._inner_object = inner_object >>> >>> def __setattr__(self, name, value): >>> if name != ‘_inner_object’: >>> setattr(self._inner_object, name, value) >>> else: >>> super(Proxy, self).__setattr__(name, value) >>> 92 >>> def __getattribute__(self, name): >>> if name == ‘_inner_object’: >>> return object.__getattribute__(self, name) >>> return getattr(self._inner_object, name) Пример реализации методов работы с атрибутами >>>p = Proxy([1]) # p._inner_object = [1] >>>p.append(2) # p._inner_object = [1, 2] Обратите внимание на вызовы методов базовых классов: – super(Proxy, self).__setattr__(name, value) – object.__getattribute__(self, name) Такие вызовы (в случае одинаковых методов, разумеется) экви- валенты как через super(…), так и через ЗАМЕЧАНИЯ ПО РАБОТЕ С АТРИБУТАМИ В примере Proxy можно было ограничиться переопределе- нием только метода __getattr__ (и реализацией конструктора): >>> class Proxy(object): >>> … >>> def __getattr__(self, name): >>> return getattr(self._inner_object, name) Важно! Интерпретатор оптимизирует вызов всех магических ме- тодов: явный вызов x.__ len__ обратится к __getattribute__, неявный len(x) – нет. Замечание. Создавать новые методы, присваивая функции объ- екту или классу, некорректно. Присваивать нужно объекты типа types. MethodType. 3.11. МЕХАНИЗМ СОЗДАНИЯ КЛАССОВ ПОСЛЕДОВАТЕЛЬНОСТЬ СОЗДАНИЯ КЛАССА Определение класса приводит к следующим действиям: – определяется подходящий метакласс (класс, который созда- ет другие классы); 93 – подготавливается namespace класса; – выполняется тело класса; – создается объект класса и присваивается переменной. >>> class X(object): >>> a = 0 >>> >>># equivalent: type(name, bases, namespace) >>>X = type(‘X’, (object,), {‘a’: 0}) Метаклассом по умолчанию является type. Выполнение тела класса приводит к созданию словаря всех его атрибутов, который передается в type. Далее этот словарь доступен через __ dict__ или с помощью built- in функции vars. Замечание. Изменять, добавлять и удалять атрибуты можно, мо- дифицируя __ dict__. Данный способ менее явный, нежели исполь- зование __ getattr__ и пр. Важно! Атрибуты классов при наследовании не перезаписыва- ются, а поиск их происходит последовательно в словарях базовых классов. Вопрос: есть ли разница между реализацией синонима (alias) для функции (функции g и h в примере)? >>> class X(object): >>> def f(self): >>> return 0 >>> >>> def g(self): >>> return self.f() >>> >>> h = f Обычно реализация синонимов необходима при реализации операторов. Код в модуле выполняется подобно коду тела класса. Неудиви- тельно, ведь модуль – тоже класс. Значит, в теле класса можно писать любые синтаксически корректные конструкции. >>> class C(object): >>> if sys.version_info.major == 3: >>> def f(self): >>> return 1 94 >>> else: >>> def g(self): >>> return 2 Замечание. В Python 3 порядок объявления атрибутов сохраня- ется (PEP-520) 1 СОЗДАНИЕ ЭКЗЕМПЛЯРА КЛАССА Создание экземпляра класса заключается в вызове мето- да __ new __ для получения объекта класса и метода __init__ для его инициализации. >>> class C(object): >>> def __new__(cls, name): >>> return super().__new__(cls) # make a new class >>> >>> def __init__(self, name): >>> self.name = name >>> >>>c = C(‘class’) Сигнатура метода __ new__ совпадает с сигнатурой __init__. В методе __ new__ можно возвращать объект другого класса, мо- дифицировать и присваивать атрибуты! Метод __ init__ не вызывается автоматически, если __new__ воз- вращает объект другого класса. МЕТАКЛАССЫ >>> class Meta(type): >>> def __new__(mcs, name, bases, attrs, **kwargs): >>> # invoked to create class C itself >>> return super().__new__(mcs, name, bases, attrs) >>> >>> def __init__(cls, name, bases, attrs, **kwargs): 1 https://www.python.org/dev/peps/pep-0520 95 >>> # invoked to init class C itself >>> return super().__init__(name, bases, attrs) >>> >>> def __call__(cls): >>> # invoked to create an instance of C >>> # -> call __new__ and __init__ inside >>> # Note: __call__ must share the signature >>> # with class’ __new__ and __init__ method signatures >>> return super().__call__() 1 >>> class C(metaclass=Meta): 2 >>> def __new__(cls): 3 >>> return super().__new__(cls) 4 >>> 5 >>> def __init__(self): 6 >>> pass 7 >>> 8 >>>c = C() Строка 1: вызываются методы __ new__ и __init__ метакласса Meta (создается объект – класс). Строка 8: вызывается метод __ call__ метакласса Meta, который вызывает методы __ new__ и __init__ класса C. Методы __ new__ и __init__ метакласса принимают **kwargs – клю- чевые аргументы. Они используются для настройки класса – вызо- ва метода __ prepare__, который возвращает mapping для сохранения атрибутов класса (см. PEP-3115) 1 Примером метакласса в стандартной библиотеке является Enum 2 (с Python 3.4). Замечание. В Python 3.6 появился метод __init_subclass__ (PEP-487) 3 , позволяющий изменить создание классов наследников (например, добавить атрибуты). В классе присутствуют специальный атрибут __ bases__ (кортеж базовых классов) и функция __ subclasses__, возвращающая список подклассов. 1 https ://www.python.org/dev/peps/pep‑3115/ 2 https ://docs.python.org/3/library/enum.html 3 https ://www.python.org/dev/peps/pep‑0487 96 АБСТРАКТНЫЕ БАЗОВЫЕ КЛАССЫ В Python есть возможность создавать условные интерфей- сы и абстрактные классы. Для этого используется метакласс ABCMeta (в Python 3.4 – базовый класс ABC) из модуля abc (PEP-3119) 1 Для объявления абстрактного метода используется декоратор abstractmethod, абстрактного свой ства – abstractproperty. В Python иерархия типов введена для чисел – модуль numbers PEP-3141 2 , а также коллекций и функционалов – модуль collections.abc. ЗАМЕЧАНИЯ О КЛАССАХ В Python 3.6. метод __ set_name__(self, owner, name) можно переопределить у дескрипторов для получения имени name, под ко- торым дескриптор сохраняется в классе owner. Замечание. При реализации __getattribute__ в некоторых случа- ях требуется принимать во внимание дескрипторы. В классах допустимы некоторые атрибуты, характеризующие класс: – __ slots__ (используется вместо __dict__); – __ annotations__ (аннотации типов: PEP-318 3 , PEP-481 4 , PEP-526 5 , PEP-3107 6 ); – __ weakref__ («слабые ссылки»: документация 7 , PEP-205) 8 Замечание. PyCharm IDE имеет возможность подсказывать тип переменных, основываясь на docstrings и коде (https://www.jetbrains. com/help/pycharm/type- hinting- in- pycharm.html). 3.12. ДЕСКРИПТОРЫ Свой ства ( property) и декораторы staticmethod и classme- thod являются дескрипторами – специальными объектами, реализо- 1 https://www.python.org/dev/peps/pep‑3119 2 https://www.python.org/dev/peps/pep‑3141 3 https ://www.python.org/dev/peps/pep‑0318 4 https ://www.python.org/dev/peps/pep‑0481 5 https ://www.python.org/dev/peps/pep‑0526 6 https ://www.python.org/dev/peps/pep‑3107/ 7 https ://docs.python.org/3.7/library/weakref.html 8 https ://www.python.org/dev/peps/pep‑0205 97 ванными как атрибуты класса (непосредственного или одного из ро- дителей). В классах в зависимости от типа дескриптора реализуются ме- тоды: – __ get__(self, instance, owner) # owner – instance class / type; – __ set__ (self, instance, value) # self – объект дескриптора; – __ delete__(self, instance) # instance – объект, в котором вызыва- ется дескриптор. Стандартное поведение дескрипторов заключается в работе со словарями объекта, класса или базовых классов. Пример. Вызов a.x приводит к вызову a.__dict__[‘x’], потом type(a).__dict__[‘x’] и далее по цепочке наследования. Пример дескриптора: >>> class Descriptor(object): >>> def __init__(self, label): >>> self.label = label >>> >>> def __get__(self, instance, owner): >>> return instance.__dict__.get(self.label) >>> >>> def __set__(self, instance, value): >>> instance.__dict__[self.label] = value >>> >>> class C(object): >>> x = Descriptor(‘x’) >>> >>>c = C() >>>c.x = 5 >>> print(c.x) Методы и свой ства в классе являются дескрипторами. По сути, в каждой функции (неявно) есть метод __ get__: >>> class Function(object): >>> def __get__(self, obj, objtype=None): >>> “Simulate func_descr_get() in Objects/ funcobject.c” >>> return types.MethodType(self, obj, objtype) Декораторы classmethod и staticmethod модифицируют аргу- менты вызова. Метод – объект- функция, который хранится в словаре атрибутов класса. Доступ же обеспечивается с помощью механизма дескрипто- ров (см. примеры в документации) 1, 2 >>> class D(object): … def f(self, x): … return x … >>> d = D() >>> D.__dict__[‘f’] # Stored internally as a function >>> D.f # Get from a class becomes an unbound method >>> d.f # Get from an instance becomes a bound method ОТВЕТЫ НА ВОПРОСЫ При создании декораторов- классов для применения functools.wraps обычно переопределяют метод __new__. Приме- нить напрямую к классу его не удастся. Второй вариант – приме- нить functools.update_wrapper в методе __init__ ко вновь создан- ному объекту класса. Что касается синонима и прямого вызова одного метода из друго- го, при наследовании синоним будет хранить ссылку на метод базово- го класса, а прямой вызов будет учитывать наследование. 1 https ://docs.python.org/2/howto/descriptor.html 2 https ://docs.python.org/3.7/howto/descriptor.html 99 Глава 4 ДОПОЛНИТЕЛЬНЫЕ ВОЗМОЖНОСТИ ЯЗЫКА PYTHON 4.1. МАТЕМАТИЧЕСКАЯ БИБЛИОТЕКА NUMPY NDARRAY В библиотеке Numpy реализован класс ndarray – представ- ление многомерного массива. Он характеризуется данными (data) и информацией о данных: – тип данных и его размер; – смещение данных в буфере; – размерности (shape) и размер в байтах; – количество элементов для перехода к следующему элементу в измерении (по оси – stride); – порядок байтов в массиве; – флаги буфера данных; – ориентация данных (С-order или Fortran- order). NDArray соответствует буферу – С-массиву, выровненному по размеру элемента ( itemsize). К отдельным элементам массива можно обращаться с помощью методов item/itemset – это быстрее, чем по индексу через __getitem__. Важно! Все операции с массивом могут быть как с копировани- ем данных, так и без. Некоторые операции возвращают views – новые массивы, которые указывают на те же данные, но содержат о них иную информацию. При изменении данных в view данные в ориги- нальном массиве меняются. РАЗМЕРНОСТЬ МАССИВА Форма массива задается атрибутом shape – кортеж раз- мерностей. Изменить размер можно: – ndarray.reshape() – view, но shape обязан быть compatible с текущим; – ndarray.resize() – inplace, может понадобиться копирование; – ndarray.shape – inplace, исключение, если не compatible. 100 Разворачивание массива в 1-D: – ndarray.ravel – view; – ndarray.flatten – copy; – ndarray.flat – итератор по flattened массиву. Замечание. Допустим 0-D-массив. ОСИ Оси – составляющие общей размерности массива. Важно! Нумерация осей (axes) ведется с нуля и соответствует декартовым координатам. Значение None в функциях соответству- ет развернутому массиву. Изменение осей не приводит к копированию данных. Методы transpose и swapaxis позволяют изменить порядок сле- дования осей. Важно! Эти методы могут сделать отображение данных не не- прерывным. ИНДЕКСАЦИЯ Numpy поддерживает два вида индексации: простую и «продвинутую». Простая индексация – один элемент или слайс: >>> x = numpy.arange(10) >>> x[1], x[–2], x[3: 7] В многомерном массиве элементы задаются через запятую: >>> x = numpy.arange(100).reshape(5, 5, 4) >>> x[1, 2, 3], x[1, 1:, –1], x[…, 2] == x[:,:, 2] Замечания. Многоточие «…» – Ellipsis позволяет пропустить не- которые измерения, предполагая, что их нужно взять целиком. В numpy применяется index broadcasting: если массив по одной из осей имеет длину один, он расширяется до необходимой длины (например, можно сложить с числом). ПРОДВИНУТАЯ ИНДЕКСАЦИЯ Запись x[ind_1, …, ind_n] эквивалентна x[(ind_1, …, ind_n)], но кардинально отличается от x[[ind_1, …, ind_n]]. |