Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Начальство по магическим способам в Питоне

Anna | 16.06.2014 | нет комментариев
Это перевод 1.17 версии начальства от Rafe Kettler.

Оглавление

  1. Введение
  2. Проектирование и инициализация
  3. Переопределение операторов на произвольных классах
  4. Представление своих классов
  5. Контроль доступа к признакам
  6. Создание произвольных последовательностей
  7. Отражение
  8. Вызываемые объекты
  9. Администраторы контекста
  10. Абстрактные базовые классы
  11. Построение дескрипторов
  12. Копирование
  13. Применение модуля pickle на своих объектах
  14. Завершение
  15. Приложение 1: Как вызывать волшебные способы
  16. Приложение 2: Метаморфозы в Питоне 3

Введение


Что такое волшебные способы? Они всё в объектно-ориентированном Питоне. Это особые способы, с поддержкой которых вы можете добавить в ваши классы «магию». Они неизменно обрамлены двумя нижними подчеркиваниями (скажем, __init__ либо __lt__). Ещё, они не так отлично документированны, как хотелось бы. Все волшебные способы описаны в документации, но крайне беспорядочно и примерно безо каждой организации. Следственно, Дабы поправить то, что я воспринимаю как недочет документации Питона, я собираюсь предоставить огромнее информации о магических способах, написанной на внятном языке и богато снабжённой примерами. Верю, это начальство вам понравится. Используйте его как обучающий материал, памятку либо полное изложение. Я легко постарался как дозволено внятнее описать волшебные способы.

Проектирование и инициализация.


Каждому знаменит самый базовый волшебный способ, __init__. С его поддержкой мы можем инициализировать объект. Впрочем, когда я пишу x = SomeClass()__init__ не самое первое, что вызывается. На самом деле, экземпляр объекта создаёт способ __new__ и после этого передаёт доводы в инициализатор. На ином конце жизненного цикла объекта находится способ __del__. Давайте подробнее разглядим эти три магических способа:

  • __new__(cls, [...)
    Это 1-й способ, тот, что будет вызван при инициализации объекта. Он принимает в качестве параметров класс и потом всякие другие доводы, которые он должен передать в __init____new__ применяется крайне редко, но изредка бывает пригоден, в частности, когда класс наследуется от неизменяемого (immutable) типа, такого как кортеж (tuple) либо строка. Я не намерен дюже подробно останавливаться на __new__, так как он не то Дабы дюже Зачастую необходим, но данный способ дюже отлично и подробно описан в документации.
  • __init__(self, [...)
    Инициализатор класса. Ему передаётся всё, с чем был вызван изначальный конструктор (так, скажем, если мы вызываем x = SomeClass(10, 'foo')__init__ получит 10 и 'foo' в качестве доводов.__init__ примерно повсюду применяется при определении классов.
  • __del__(self)
    Если __new__ и __init__ образуют конструктор объекта, __del__ это его деструктор. Он не определяет поведение для выражения del x (следственно данный код не равнозначен x.__del__()). Скорее, он определяет поведение объекта в то время, когда объект попадает в сборщик мусора. Это может быть достаточно комфортно для объектов, которые могут требовать дополнительных чисток во время удаления, таких как сокеты либо файловыве объекты. Впрочем, необходимо быть осмотрительным, так как нет ручательства, что __del__ будет вызван, если объект продолжает жить, когда интерпретатор завершает работу. Следственно __del__ не может служить заменой для отличных программистских практик (неизменно завершать соединение, если завершил с ним трудиться и тому сходственное). Реально, из-за отсутствия ручательства вызова, __del__ не должен применяться примерно никогда; используйте его с осторожностью!

Объединим всё совместно, вот пример __init__ и __del__ в действии:

from os.path import join

class FileObject:
    '''Обёртка для файлового объекта, Дабы быть уверенным в том, что файл будет закрыт при удалении.'''

    def __init__(self, filepath='~', filename='sample.txt'):
        # открыть файл filename в filepath в режиме чтения и записи
        self.file = open(join(filepath, filename), 'r ')

    def __del__(self):
        self.file.close()
        del self.file

Переопределение операторов на произвольных классах


Одно из крупных превосходств применения магических способов в Питоне то, что они предоставляют примитивный метод принудить объекты вести себя по подобию встроенных типов. Это обозначает, что вы можете избежать печального, нелогичного и нестандартного поведения базовых операторов. В некоторых языках обыкновенное явление писать как-нибудь так:

if instance.equals(other_instance):
    # do something


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

if instance == other_instance:
    #do something


Это одна из мощных сторон магических способов. Подавляющее множество из них разрешают определить, что будут делать типовые операторы, так что мы можем применять операторы на своих классах так, как словно они встроенные типы.

Волшебные способы сопоставления


В Питоне множество магических способов, сделанных для определения интуитивного сопоставления между объектами применяя операторы, а не неуклюжие способы. Помимо того, они предоставляют метод переопределить поведение Питона по-умолчанию для сопоставления объектов (по ссылке). Вот список этих способов и что они делают:

  • __cmp__(self, other)
    Самый базовый из способов сопоставления. Он, в реальности, определяет поведение для всех операторов сопоставления (>, ==, !=, итд.), но не неизменно так, как вам это необходимо (скажем, если эквивалентность 2-х экземпляров определяется по одному критерию, а то что один огромнее иного по какому-нибудь иному). __cmp__ должен воротить негативное число, если self < other, нуль, если self == other, и позитивное число в случае self > other. Но, обыкновенно, отменнее определить всякое сопоставление, которое вам необходимо, чем определять их всех в __cmp__. Но __cmp__ может быть отличным методом избежать повторений и увеличить ясность, когда все нужные сопоставления оперерируют одним критерием.
  • __eq__(self, other)
    Определяет поведение оператора равенства, ==.
  • __ne__(self, other)
    Определяет поведение оператора неравенства, !=.
  • __lt__(self, other)
    Определяет поведение оператора поменьше, <.
  • __gt__(self, other)
    Определяет поведение оператора огромнее, >.
  • __le__(self, other)
    Определяет поведение оператора поменьше либо равно, <=.
  • __ge__(self, other)
    Определяет поведение оператора огромнее либо равно, >=.


Для примера расммотрим класс, описывающий слово. Мы можем сопоставлять слова лексиграфически (по алфавиту), что является дефолтным поведением при сопоставлении строк, но можем захотеть применять при сопоставлении какой-нибудь иной критерий, такой, как длина либо число слогов. В этом примере мы будем сопоставлять по длине. Вот реализация:

class Word(str):
    '''Класс для слов, определяющий сопоставление по длине слов.'''

    def __new__(cls, word):
        # Мы обязаны применять __new__, так как тип str неизменяемый
        # и мы обязаны инициализировать его прежде (при создании)
        if ' ' in word:
            print "Value contains spaces. Truncating to first space."
            word = word[:word.index(' ')] # Сейчас Word это все символы до первого пробела
        return str.__new__(cls, word)

    def __gt__(self, other):
        return len(self) > len(other)
    def __lt__(self, other):
        return len(self) < len(other)
    def __ge__(self, other):
        return len(self) >= len(other)
    def __le__(self, other):
        return len(self) <= len(other)


Сейчас мы можем сделать два Word (при помощи Word('foo') и Word('bar')) и сравнить их по длине. Подметьте, что мы не определяли __eq__ и __ne__, так как это приведёт к необычному поведению (скажем,Word('foo') == Word('bar') будет расцениваться как правда). В этом нет смысла при тестировании на эквивалентность, основанную на длине, следственно мы оставляем стандартную проверку на эквивалентность от str.
Теперь, кажется, успешное время упомянуть, что вы не обязаны определять всякий из магических способов сопоставления, Дабы всецело охватить все сопоставления. Стандартная библиотека вежливо предоставляет нам класс-декторатор в модуле functools, тот, что и определит все сопоставляющие способы, от вас довольно определить только __eq__ и ещё один (__gt____lt__ и т.п.) Эта вероятность доступна начиная с 2.7 версии Питона, но если это вас устраивает, вы сэкономите кучу времени и усилий. Для того, Дабы задействовать её, разместите @total_ordering над вашим определением класса.

Числовые волшебные способы


Верно так же, как вы можете определить, каким образом ваши объекты будут сравниваться операторами сопоставления, вы можете определить их поведение для числовых операторов. Приготовтесь, друзья, их много. Для лучшей организации, я разбил числовые волшебные способы на 5 категорий: унарные операторы, обыкновенные арифметические операторы, отражённые арифметические операторы (подробности позднее), комбинированные присваивания и реформирования типов.

Унарные операторы и функции


Унарные операторы и функции имеют только один операнд — отрицание, безусловное значение, и так дальше.

  • __pos__(self)
    Определяет поведение для унарного плюса ( some_object)
  • __neg__(self)
    Определяет поведение для отрицания(-some_object)
  • __abs__(self)
    Определяет поведение для встроенной функции abs().
  • __invert__(self)
    Определяет поведение для инвертирования оператором ~. Для объяснения что он делает смотри статью в Википедии о бинарных операторах.
  • __round__(self, n)
    Определяет поведение для встроенной функции round()n это число знаков позже запятой, до которого округлить.
  • __floor__(self)
    Определяет поведение для math.floor(), то есть, округления до ближайшего меньшего целого.
  • __ceil__(self)
    Определяет поведение для math.ceil(), то есть, округления до ближайшего большего целого.
  • __trunc__(self)
    Определяет поведение для math.trunc(), то есть, обрезания до целого.

Обыкновенные арифметические операторы

Сейчас разглядим обыкновенные бинарные операторы (и ещё пару функций): , -, * и схожие. Они, по большей части, отменно сами себя описывают.

  • __add__(self, other)
    Сложение.
  • __sub__(self, other)
    Вычитание.
  • __mul__(self, other)
    Умножение.
  • __floordiv__(self, other)
    Целочисленное деление, оператор //.
  • __div__(self, other)
    Деление, оператор /.
  • __truediv__(self, other)
    Положительное деление. Подметьте, что это работает только когда применяется from __future__ import division.
  • __mod__(self, other)
    Остаток от деления, оператор %.
  • __divmod__(self, other)
    Определяет поведение для встроенной функции divmod().
  • __pow__
    Возведение в степень, оператор **.
  • __lshift__(self, other)
    Двоичный сдвиг налево, оператор <<.
  • __rshift__(self, other)
    Двоичный сдвиг вправо, оператор >>.
  • __and__(self, other)
    Двоичное И, оператор &.
  • __or__(self, other)
    Двоичное ЛИБО, оператор |.
  • __xor__(self, other)
    Двоичный xor, оператор ^.

Отражённые арифметические операторы

Помните как я сказал, что собираюсь остановиться на отражённой арифметике подробнее? Вы могли подумать, что это какая-то огромная, ужасная и непонятная доктрина. На самом деле всё дюже легко. Вот пример:

some_object   other

Это «обыкновенное» сложение. Исключительное, чем отличается равнозначное отражённое выражение, это порядок слагаемых:

other   some_object

Таким образом, все эти м__itruediv__(self, other)
Положительное деление с присваиванием. Подметьте, что работает только если применяется from __future__ import division.

  • __imod_(self, other)
    Остаток от деления с присваиванием, оператор %=.
  • __ipow__
    Возведение в степерь с присваиванием, оператор **=.
  • __ilshift__(self, other)
    Двоичный сдвиг налево с присваиванием, оператор <<=.
  • __irshift__(self, other)
    Двоичный сдвиг вправо с присваиванием, оператор >>=.
  • __iand__(self, other)
    Двоичное И с присваиванием, оператор &=.
  • __ior__(self, other)
    Двоичное ЛИБО с присваиванием, оператор |=.
  • __ixor__(self, other)
    Двоичный xor с присваиванием, оператор ^=.

Волшебные способы реформирования типов

Помимо того, в Питоне уйма магических способов, предуготовленных для определния поведения для встроенных функций реформирования типов, таких как float(). Вот они все:

  • __int__(self)
    Реформирование типа в int.
  • __long__(self)
    Реформирование типа в long.
  • __float__(self)
    Реформирование типа в float.
  • __complex__(self)
    Реформирование типа в комплексное число.
  • __oct__(self)
    Реформирование типа в восьмеричное число.
  • __hex__(self)
    Реформирование типа в шестнадцатиричное число.
  • __index__(self)
    Реформирование типа к int, когда объект применяется в срезах (выражения вида [start:stop:step]). Если вы определяете свой числовый тип, тот, что может применяться как индекс списка, вы обязаны определить __index__.
  • __trunc__(self)
    Вызывается при math.trunc(self). Должен воротить своё значение, обрезанное до целочисленного типа (обыкновенно long).
  • __coerce__(self, other)
    Способ для реализации арифметики с операндами различных типов. __coerce__ должен воротить Noneесли реформирование типов немыслимо. Если реформирование допустимо, он должен воротить пару (кортеж из 2-х элементов) из self и other, преобразованные к одному типу.

Представление своих классов


Зачастую бывает пригодно представление класса в виде строки. В Питоне существует несколько способов, которые вы можете определить для настройки поведения встроенных функций при представлении вашего класса.

  • __str__(self)
    Определяет поведение функции str(), вызванной для экземпляра вашего класса.
  • __repr__(self)
    Определяет поведение функции repr(), вызыванной для экземпляра вашего класса. Основное различие от str() в целевой аудитории. repr() огромнее предуготовлен для машинно-ориентированного итога (больше того, это Зачастую должен быть валидный код на Питоне), а str() предуготовлен для чтения людьми.
  • __unicode__(self)
    Определяет поведение функции unicode(), вызыванной для экземпляра вашего класса. unicode() схож на str(), но возвращает строку в юникоде. Будте осмотрительны: если заказчик вызывает str() на экземпляре вашего класса, а вы определили только __unicode__(), то это не будет трудиться. Постарайтесь неизменно определять __str__() для случая, когда кто-то не имеет такой роскоши как юникод.
  • __format__(self, formatstr)
    Определяет поведение, когда экземпляр вашего класса применяется в форматировании строк нового жанра. Скажем, "Hello, {0:abc}!".format(a) приведёт к вызову a.__format__("abc"). Это может быть пригодно для определения ваших собственных числовых либо строковых типов, которым вы можете захотеть предоставить какие-нибудь особые опции форматирования.
  • __hash__(self)
    Определяет поведение функции hash(), вызыванной для экземпляра вашего класса. Способ должен возвращать целочисленное значение, которое будет применяться для стремительного сопоставления ключей в словарях. Подметьте, что в таком случае обыкновенно необходимо определять и __eq__ тоже. Руководствуйтесь дальнейшим правилом: a == b подразумевает hash(a) == hash(b).
  • __nonzero__(self)
    Определяет поведение функции bool(), вызванной для экземпляра вашего класса. Должна воротить True либо False, в зависимости от того, когда вы считаете экземпляр соответствующим True либо False.
  • __dir__(self)
    Определяет поведение функции dir(), вызванной на экземпляре вашего класса. Данный способ должен возвращать пользователю список признаков. Обыкновенно, определение __dir__ не требуется, но может быть животрепещуще значимо для интерактивного применения вашего класса, если вы переопределили __getattr__ либо __getattribute__ (с которыми вы встретитесь в дальнейшей части), либо каким-либо иным образом динамически создаёте признаки.
  • __sizeof__(self)
    Определяет поведение функции sys.getsizeof(), вызыванной на экземпляре вашего класса. Способ должен воротить размер вашего объекта в байтах. Он основным образом пригоден для классов, определённых в растяжениях на C, но всё-равно благотворно о нём знать.


Мы примерно завершили со тоскливой (и лишённой примеров) частью начальства по магическим способам. Сейчас, когда мы разглядели самые базовые волшебные способы, пришло время перейти к больше продвинутому материалу.

Контроль доступа к признакам


Многие люди, пришедшие в Питон из других языков, жалятся на неимение настоящей инкапсуляции для классов (скажем, нет метода определить приватные признаки с публичными способами доступа). Это не вовсе правда: легко многие вещи, связанные с инкапсуляцией, Питон реализует через «магию», а не очевидными модификаторами для способов и полей. Глядите:

  • __getattr__(self, name)
    Вы можете определить поведение для случая, когда пользователь пытается обратиться к признаку, тот, что не существует (вовсе либо пока ещё). Это может быть пригодным для перехвата и перенаправления частых опечаток, предупреждения об применении устаревших признаков (вы можете всё-равно вычислить и воротить данный признак, если хотите), либо хитроумно возвращать AttributeError, когда это вам необходимо. Правда, данный способ вызывается только когда пытаются получить доступ к несуществующему признаку, следственно это не дюже отличное решение для инкапсуляции.
  • __setattr__(self, name, value)
    В различии от __getattr____setattr__ решение для инкапсуляции. Данный способ разрешает вам определить поведение для присвоения значения признаку, самостоятельно от того существует признак либо нет. То есть, вы можете определить всякие правила для всяких изменений значения признаков. Однако, вы обязаны быть осмотрительны с тем, как применять __setattr__, смотрите пример нехорошего случая в конце этого списка.
  • __delattr__
    Это то же, что и __setattr__, но для удаления признаков, взамен установки значений. Тут требуются те же меры предосторожности, что и в __setattr__ Дабы избежать безмерной рекурсии (вызов del self.name в определении __delattr__ вызовет безграничную рекурсию).
  • __getattribute__(self, name)
    __getattribute__ выглядит к месту среди своих коллег __setattr__ и __delattr__, но я бы не рекомендовал вам его применять. __getattribute__ может применяться только с классами нового типа (в новых версиях Питона все классы нового типа, а в ветхих версиях вы можете получить такой класс унаследовавшись от object). Данный способ разрешает вам определить поведение для всякого случая доступа к признакам (а не только к несуществующим, как __getattr__(self, name)). Он страдает от таких же задач с безграничной рекурсией, как и его сотрудники (на данный раз вы можете вызывать__getattribute__ у базового класса, Дабы их недопустить). Он, так же, основным образом устраняет надобность в __getattr__, тот, что в случае реализации __getattribute__ может быть вызван только очевидным образом либо в случае генерации исключения AttributeError. Вы безусловно можете применять данный способ (в конце концов, это ваш выбор), но я бы не рекомендовал, потому что случаев, когда он подлинно пригоден дюже немного (гораздо реже необходимо переопределять поведение при приобретении, а не при установке значения) и реализовать его без допустимых ошибок дюже трудно.


Вы можете запросто получить задачу при определении всякого метогда, руководящего доступом к признакам. Разглядим пример:

def __setattr__(self, name, value):
    self.name = value
    # это рекурсия, так как каждый раз, когда любому признаку присваивается значение,
    # вызывается  __setattr__().
    # тоесть, на самом деле это эквивалентно self.__setattr__('name', value). 
    # Так как способ вызывает сам себя, рекурсия продолжится беспредельно, пока всё не упадёт

def __setattr__(self, name, value):
    self.__dict__[name] = value # присваивание в словарь переменных класса
    # дальше определение произвольного поведения


Ещё раз, мощь магических способов в Питоне невообразима, а с огромный силой приходит и огромная ответственность. Значимо знать, как верно применять волшебные способы, ничего не ломая.

Выходит, что мы узнали об управлении доступом к признакам? Их не необходимо применять легковерно. На самом деле, они имеют наклонность к Непомерной мощи и нелогичности. Повод, по которой они всё-таки существуют, в удволетворении определённого мечты: Питон склонен не воспрещать дрянные штуки всецело, а только усложнять их применение. Воля первостепенна, следственно вы на самом деле можете делать всё, что хотите. Вот пример применения способов контроля доступа (подметьте, что мы используем super, так как не все классы имеют признак __dict__):

class AccessCounter(object):
    '''Класс, содержащий признак value и реализующий счётчик доступа к нему.
    Счётчик возрастает всякий раз, когда меняется value.'''

    def __init__(self, val):
        super(AccessCounter, self).__setattr__('counter', 0)
        super(AccessCounter, self).__setattr__('value', val)

    def __setattr__(self, name, value):
        if name == 'value':
            super(AccessCounter, self).__setattr__('counter', self.counter   1)
        # Не будем делать тут никаких условий.
        # Если вы хотите недопустить метаморфоза других признаков,
        # выбросьте исключение AttributeError(name)
        super(AccessCounter, self).__setattr__(name, value)

    def __delattr__(self, name):
        if name == 'value':
            super(AccessCounter, self).__setattr__('counter', self.counter   1)
        super(AccessCounter, self).__delattr__(name)]

Создание произвольных последовательностей


В Питоне существует уйма методов принудить ваши классы вести себя как встроенные последовательности (словари, кортежи, списки, строки и так дальше). Это, абсолютно, мои любимые волшебные способы, из-за до вздора высокой степени контроля, которую они дают и той магии, от которой с экзеть словарь d и я пишуd["george"] когда "george" не является ключом в словаре, вызывается d.__missing__("george")).

Пример


Для примера, давайте посмотрим на список, тот, что реализует некоторые функциональные конструкции, которые вы могли встретить в других языках (Хаскеле, скажем).

class FunctionalList:
    '''Класс-обёртка над списком с добавлением некоторой функциональной магии: head,
    tail, init, last, drop, take.'''

    def __init__(self, values=None):
        if values is None:
            self.values = []
        else:
            self.values = values

    def __len__(self):
        return len(self.values)

    def __getitem__(self, key):
        # если значение либо тип ключа некорректны, list выкинет исключение
        return self.values[key]

    def __setitem__(self, key, value):
        self.values[key] = value

    def __delitem__(self, key):
        del self.values[key]

    def __iter__(self):
        return iter(self.values)

    def __reversed__(self):
        return FunctionalList(reversed(self.values))

    def append(self, value):
        self.values.append(value)
    def head(self):
        # получить 1-й элемент
        return self.values[0]
    def tail(self):
        # получить все элементы позже первого
        return self.values[1:]
    def init(self):
        # получить все элементы помимо последнего
        return self.values[:-1]
    def last(self):
        # получить конечный элемент
        return self.values[-1]
    def drop(self, n):
        # все элементы помимо первых n
        return self.values[n:]
    def take(self, n):
        # первые n элементов
        return self.values[:n]


Сейчас у вас есть пригодный (касательно) пример реализации своей собственной последовательности. Существуют, безусловно, и куда больше практичные реализации произвольных последовательностей, но огромное их число теснее реализовано в стандартной библиотеке (с батарейками в комплекте, да?), такие какCounterOrderedDictNamedTuple.

Отражение


Вы можете контролировать и отражение, использующее встроенные функции isinstance() и issubclass(), определив некоторые волшебные способы. Вот они:

  • __instancecheck__(self, instance)
    Проверяет, является ли экземлпяр членом вашего класса (isinstance(instance, class), скажем.
  • __subclasscheck__(self, subclass)
    Проверяет, является наследуется ли класс от вашего класса (issubclass(subclass, class)).


Может показаться, что вариантов пригодного применения этих магических способов немножко и, допустимо, это на самом деле так. Я не хочу тратить слишком много времени на волшебные способы отражения, не особенно они и значимые, но они отражают кое-что значимое об объектно-ориентированном программировании в Питоне и о Питоне вообще: примерно неизменно существует легкой метод что-либо сделать, даже если потребность в этом «что-либо» появляется дюже редко. Эти волшебные способы могут не выглядеть пригодными, но если они вам когда-нибудь потребуются, вы будете рады припомнить, что они есть (и для этого вы читаете реальное начальство!).

Вызываемые объекты


Как вы вероятно теснее знаете, в Питоне функции являются объектами первого класса. Это обозначает, что они могут быть переданы в функции либо способы так же, как всякие другие объекты. Это немыслимо сильная специфика.

Особый волшебный способ разрешает экземплярам вашего класса вести себя так, как словно они функции, тоесть вы сумеете «вызывать» их, передавать их в функции, которые принимают функции в качестве доводов и так дальше. Это иная комфортная специфика, которая делает программирование на Питоне таким славным.

  • __call__(self, [args...])
    Разрешает любому экземпляру вашего класса быть вызванным как-словно он функция. Основным образом это обозначает, что x() обозначает то же, что и x.__call__(). Подметьте, __call__ принимает произвольное число доводов; то есть, вы можете определить __call__ так же как всякую иную функцию, принимающую столько доводов, сколько вам необходимо.


__call__, в частности, может быть пригоден в классах, чьи экземпляры Зачастую изменяют своё состояние. «Вызвать» экземпляр может быть подсознательно внятным и изящным методом изменить состояние объекта. Примером может быть класс, представляющий расположение некоторого объекта на плоскости:

class Entity:
    '''Класс, описывающий объект на плоскости. "Вызываемый", Дабы обновить позицию объекта.'''

    def __init__(self, size, x, y):
        self.x, self.y = x, y
        self.size = size

    def __call__(self, x, y):
        '''Изменить расположение объекта.'''
        self.x, self.y = x, y

    # чик...

Администраторы контекста


В Питоне 2.5 было представлено новое ключевое слово совместно с новым методом вторично применять код, ключевое слово with. Доктрина администраторов контекста не являлась новой для Питона (она была реализована прежде как часть библиотеки), но в PEP 343 достигла ранга языковой конструкции. Вы могли теснее видеть выражения с with:

with open('foo.txt') as bar:
    # выполнение каких-нибудь действий с bar

Администраторы контекста разрешают исполнить какие-то действия для настройки либо чистки, когда создание объекта обёрнуто в оператор with. Поведение администратора контекста определяется двумя магическими способами:

  • __enter__(self)
    Определяет, что должен сделать администратор контекста в начале блока, сделанного оператором with. Подметьте, что возвращаемое __enter__ значение и есть то значение, с которым производится работа внутри with.
  • __exit__(self, exception_type, exception_value, traceback)
    Определяет действия администратора контекста позже того, как блок будет исполнен (либо прекращен во время работы). Может применяться для контроллирования исключений, чистки, всяких действий которые обязаны быть исполнены немедленно позже блока внутри with. Если блок исполнен удачно,exception_typeexception_value, и traceback будут установлены в None. В ином случае вы сами выбираете, перехватывать ли исключение либо предоставить это пользователю; если вы решили перехватить исключение, удостоверитесь, что __exit__ возвращает True позже того как всё сказано и сделано. Если вы не хотите, Дабы исключение было перехвачено администратором контекста, легко дозвольте ему случиться.

__enter__ и __exit__ могут быть пригодны для специфичных классов с отлично описанным и распространённым поведением для их настройки и чистки источников. Вы можете применять эти способы и для создания всеобщих администраторов контекста для различных объектов. Вот пример:

class Closer:
    '''Администратор контекста для механического закрытия объекта вызовом способа close 
    в with-выражении.'''

    def __init__(self, obj):
        self.obj = obj

    def __enter__(self):
        return self.obj # привязка к энергичному объекту with-блока

    def __exit__(self, exception_type, exception_val, trace):
        try:
           self.obj.close()
        except AttributeError: # у объекта нет способа close
           print 'Not closable.'
           return True # исключение перехвачено

Пример применения Closer с FTP-соединением (сокет, имеющий способ close):

>>> from magicmethods import Closer
>>> from ftplib import FTP
>>> with Closer(FTP('ftp.somesite.com')) as conn:
...     conn.dir()
...
# output omitted for brevity
>>> conn.dir()
# long AttributeError message, can't use a connection that's closed
>>> with Closer(int(5)) as i:
...     i  = 1
...
Not closable.
>>> i
6

Видите, как наша обёртка изысканно управляется и с верными и с неподходящими объектами. В этом сила администраторов контекста и магических способов. Подметьте, что стандартная библиотека Пиte__(self)
Взамен стандартного признака __dict__, где хранятся признаки класса, вы можете воротить произвольные данные для сериализации. Эти данные будут переданы в __setstate__ во время десериализации.

  • __setstate__(self, state)
    Если во время десериализации определён __setstate__, то данные объекта будут переданы сюда, взамен того Дабы легко записать всё в __dict__. Это парный способ для __getstate__: когда оба определены, вы можете представлять состояние вашего объекта так, как вы только захотите.
  • __reduce__(self)
    Если вы определили свой тип (с поддержкой Python's C API), вы обязаны осведомить Питону как его сериализовать, если вы хотите, Дабы он его сериализовал. __reduce__() вызывается когда сериализуется объект, в котором данный способ был определён. Он должен воротить либо строку, содержащую имя всеобщей переменной, содержимое которой сериализуется как обыкновенно, либо кортеж. Кортеж может содержать от 2 до 5 элементов: вызываемый объект, тот, что будет вызван, Дабы сделать десериализованный объект, кортеж доводов для этого вызываемого объекта, данные, которые будут переданы в __setstate__(опционально), итератор списка элементов для сериализации (опционально) и итератор словаря элементов для сериализации (опционально).
  • __reduce_ex__(self, protocol)
    Изредка благотворно знать версию протокола, реализуя __reduce__. И этого дозволено добиться, реализовав взамен него __reduce_ex__. Если __reduce_ex__ реализован, то предпочтение при вызове отдаётся ему (вы всё-равно обязаны реализовать __reduce__ для обратной совместимости).

Пример

Для примера опишем грифельную доску (Slate), которая запоминает что и когда было на ней записано. Однако, реально эта доска становится чистой всякий раз, когда она сериализуется: нынешнее значение не сохраняется.

import time

class Slate:
    '''Класс, хранящий строку и лог изменений. И забывающий своё значение позже 
    сериализации.'''

    def __init__(self, value):
        self.value = value
        self.last_change = time.asctime()
        self.history = {}

    def change(self, new_value):
        # Изменить значение. Зафиксировать последнее значение в истории. 
        self.history[self.last_change] = self.value
        self.value = new_value
        self.last_change = time.asctime()

    def print_changes(self):
        print 'Changelog for Slate object:'
        for k, v in self.history.items():
            print '%st %s' % (k, v)

    def __getstate__(self):
        # Специально не возвращаем self.value or self.last_change.
        # Мы хотим "чистую доску" позже десериализации.
        return self.history

    def __setstate__(self, state):
        self.history = state
        self.value, self.last_change = None, None

Завершение


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

Дополнение 1: Как вызывать волшебные способы


Некоторые из магических способов напрямую связаны со встроенными функциями; в этом случае абсолютно видимо как их вызывать. Впрочем, так бывает не неизменно. Это дополнение посвящено тому, Дабы раскрыть неочевидный синтаксис, приводящий к вызову магических способов.

__new__(cls [,...]) instance = MyClass(arg1, arg2) __new__ вызывается при создании экземпляра
__init__(self [,...]) instance = MyClass(arg1, arg2) __init__ вызывается при создании экземпляра
__cmp__(self, other) self == otherself > other, etc. Вызывается для всякого сопоставления
__pos__(self) self Унарный знак плюса
__neg__(self) -self Унарный знак минуса
__invert__(self) ~self Побитовая инверсия
__index__(self) x[self] Реформирование, когда объект применяется как индекс
__nonzero__(self) bool(self)if self: Булевое значение объекта
__getattr__(self, name) self.name # name не определено Пытаются получить несуществующий признак
__setattr__(self, name, val) self.name = val Присвоение любому признаку
__delattr__(self, name) del self.name Удаление признака
__getattribute__(self, name) self.name Получить всякий признак
__getitem__(self, key) self[key] Приобретение элемента через индекс
__setitem__(self, key, val) self[key] = val Присвоение элементу через индекс
__delitem__(self, key) del self[key] Удаление элемента через индекс
__iter__(self) for x in self Итерация
__contains__(self, value) value in selfvalue not in self Проверка принадлежности с поддержкой in
__call__(self [,...]) self(args) «Вызов» экземпляра
__enter__(self) with self as x: with оператор администраторов контекста
__exit__(self, exc, val, trace) with self as x: with оператор администраторов контекста
__getstate__(self) pickle.dump(pkl_file, self) Сериализация
__setstate__(self) data = pickle.load(pkl_file) Сериализация

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB