Руководство по магическим методам Python — Weelky UK от PyCoder

задняя часть Python API модульный тест

Руководство по магическим методам Python

  • начиная

  • Построение и инициализация

  • Создать пользовательский класс
    • магический метод сравнения
    • Волшебные методы для численной обработки
  • выразить свой класс

  • Управление доступом к собственности

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

  • отражение

  • вызываемый объект

  • менеджер сеансов

  • Создайте объект дескриптора

  • постоянный объект

  • Суммировать

  • приложение

представлять

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

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

Надеюсь, вам понравится эта статья. Вы можете использовать его как учебник, учебный материал или справочник. Цель этой статьи — просто предоставить удобный учебник по магическим методам в Python.

Построение и инициализация

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

__new__(cls, [...) __new__это первый метод, вызываемый при создании экземпляра объекта. Его первым параметром является этот класс, остальные параметры используются для прямого перехода к__init__метод.__new__Метод довольно необычный, но у него есть свои свойства, особенно при наследовании от неизменяемого типа, такого как кортеж или строка. я не хочу быть__new__Об этом слишком много подробностей, потому что это не очень полезно, но вДокументация по Pythonподробно описано.

__init__(self, […)Этот метод является методом инициализации класса. Любые аргументы конструктора будут переданы ему при его вызове. (например, если мы позвонимx = SomeClass(10, 'foo')),Так__init__получит два параметра 10 и foo.__init__Он широко используется в определениях классов в Python.

__del__(self)если__new__а также__init__является конструктором объекта, тогда__del__является деструктором. он не реализует утверждениеdel x(Приведенный выше код не будет преобразован вx.__del__()). Он определяет поведение объекта при сборке мусора. Этот метод полезен, когда объект нуждается в дополнительной очистке при удалении, например, объект сокета или объект файла. Обратите внимание, что если объект все еще существует, когда интерпретатор завершает работу, нет никаких гарантий.__del__может быть выполнено, поэтому__del__не может служить заменой хорошей практики кодирования ()~~~~~~~

Соедините, вот__init__а также__del__Примеры реального использования.

from os.path import join

class FileObject:
    '''给文件对象进行包装从而确认在删除时文件流关闭'''

    def __init__(self, filepath='~', filename='sample.txt'):
        #读写模式打开一个文件
        self.file = open(join(filepath, filename), 'r+')

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

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

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

if instance.equals(other_instance):
    # do something

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

if instance == other_instance:
        #do something

Это лишь малая часть того, что делает магический метод. Он позволяет вам определять значение символов, чтобы мы могли использовать их в наших классах. Так же, как встроенные типы.

магический метод сравнения

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

__cmp__(self, other) __cmp__является самым основным магическим методом для сравнения. На самом деле он реализует все символы сравнения (self < otherесли__cmp__должен возвращать отрицательное число, когдаself == otherвозвращает 0, когдаself > otherвернет положительное число. Обычно лучше определять каждый символ сравнения отдельно, а не определять их все сразу. но__cmp__Метод — это хороший способ прояснить ситуацию, если вы хотите реализовать все символы сравнения.

__eq__(self, other)определяет поведение знака равенства,==.

__ne__(self, other)определяет поведение знака неравенства,!=.

__lt__(self, other)определяет поведение знака меньше,<.

__gt__(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(' ')] #单词是第一个空格之前的所有字符
        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)

Теперь мы создаем дваWordsобъект (используяWord('foo')а такжеWord('bar')Затем сравните их по длине. Обратите внимание, что мы не определяли__eq__а также__ne__метод. Это потому, что будут некоторые странные результаты (например,Word('foo') == Word('bar')вернет истину). Это не очень важно для тестирования сравнений на основе длины. Итак, мы возвращаемся и используемstrВстроено для сравнения.

Теперь вы знаете, что вам не нужно определять каждый магический метод сравнения, чтобы делать расширенные сравнения. Стандартная библиотека очень удобна вfunctiontolsДекоратор, предоставленный нам в классе, определяет все богатые функции сравнения. если вы просто определите__eq__и другое (напр.__gt__, __lt__и т. д.) Эта функция существует только в Python 2.7, но если у вас есть такая возможность, она сэкономит много времени и усилий. Вы можете сделать это, поместив перед классом, который вы определяете@total_orderingиспользовать.

Волшебные методы для численной обработки

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

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

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

__pos__(self)Реализуйте функции положительного знака (такие как+some_object)

__neg__(self)Функции, реализующие знак минус (например,-some_object)

__abs__(self)Реализовать встроенныйabs()Особенности функции.

__invert__(self)выполнить~характеристики символа. чтобы проиллюстрировать эту особенность. вы можете просмотретьЭта статья в Википедии

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

Мы рассмотрели только обычные бинарные операторы: +, -, * и подобные символы. Большинство этих символов легко понять.

__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)реализовать с помощью^побитовое исключающее ИЛИ

Обратная операция

Ниже я объясню некоторые знания об обратных операциях. Некоторые концепции могут показаться вам пугающими или незнакомыми. Но на самом деле это довольно просто. Вот пример:

some_object + other

Это обычная операция сложения, обратная операция такая же, только с перестановкой операндов:

other + some_object

Итак, все эти магические методы аналогичны обычным операциям, за исключением того, что они сами становятся вторым операндом при работе с другими объектами. В большинстве случаев результат обратной операции такой же, как и при обычной операции. так что ты можешь ты можешь__radd__а также__add__эквивалентность.

__radd__(self, other)Реализовать обратное сложение__rsub__(self, other)Реализовать встречное вычитание__rmul__(self, other)реализовать обратное умножение__rfloordiv__(self, other)выполнить//Отрицательное деление знака__rdiv__(self, other)выполнить/Отрицательное деление знака__rtruediv__(self, other)Реализовать обратное истинное деление только тогда, когдаfrom __future__ import divisionбудет работать, когда__rmod__(self, other)выполнить%Обратная операция по модулю символов__rdivmod__(self, other)когдаdivmod(other, self)При вызове встроенная реализацияdivmod()обратная операция__rpow__выполнить**Обратная операция символов__rlshift__(self, other)выполнить<<обратный сдвиг знака влево__rrshift__(self, other)выполнить>>обратный сдвиг знака вправо__rand__(self, other)выполнить&Обратная операция И над символами__ror__(self, other)выполнить|Обратное ИЛИ символов__xor__(self, other)выполнить^Обратное XOR символов

Инкрементное назначение

Python также имеет массу волшебных методов для настройки операторов приращения. Возможно, вы уже знакомы с добавочным присваиванием, в котором операторы сочетаются с присваиванием. Если вы все еще не понимаете, о чем я говорю, вот пример:

x = 5
x += 1 # in other words x = x + 1

__iadd__(self, other)Реализовать добавление задания__isub__(self, other)Реализовать вычитание присваивания__imul__(self, other)Реализовать умножение присваивания__ifloordiv__(self, other)выполнить//=Этаж назначения в дополнение к__idiv__(self, other)символ реализации/=назначение кроме__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 битов присваивания

Магический метод преобразования типов

Python также имеет множество волшебных методов для достижения чего-то вродеfloat()встроенная функция преобразования типов.__int__(self)Реализовать приведение к целому числу__long__(self)Реализовать приведение длинных целых чисел__float__(self)Реализация приведения к типу с плавающей запятой__complex__(self)Реализовать сложные приведения__oct__(self)Реализовать восьмеричное принуждение__hex__(self)Реализовать бинарное принуждение__index__(self)Реализуйте целочисленные приведения, когда объекты используются в выражениях среза. Если вы определяете пользовательский числовой тип, который может использоваться в срезах, вы должны определить__index__(Подробности см. в PEP357)__trunc__(self)когда используешьmath.trunc(self)когда звонили.__trunc__Должно возвращать значение, усеченное до целого числа (обычно длинное целое число)__coerce__(self, other)Реализуйте арифметику смешанного режима. Если преобразование типов невозможно, то__coerce__вернусьNone, иначе он будетselfа такжеotherВозвращает кортеж длины 2 одного типа.

выразить свой класс

Было бы очень полезно иметь строку для представления класса. В Python существует множество способов реализовать возвращаемое значение некоторых функций, встроенных в определение класса.__str__(self)определить, когдаstr()возвращаемое значение при вызове__repr__(self)определениеrepr()Возвращаемое значение при вызове.str()а такжеrepr()Основное отличие в том, чтоrepr()возвращает машиночитаемый вывод, аstr()То, что возвращается, удобочитаемо для человека.__unicode__(self)определить, когдаunicode()Возвращаемое значение при вызове.unicode()а такжеstr()Очень похоже, но возвращает строку юникода. Обратите внимание, что если a вызывается в вашем классеstr()Однако вы определяете только__unicode__(), то не получится. вы должны определить__str__()чтобы убедиться, что при вызове возвращается правильное значение.

__hash__(self)определить, когдаhash()Возвращаемое значение при вызове возвращает целое число, которое используется для быстрого сравнения в словаре__nonzero__(self)определить, когдаbool()Возвращаемое значение при вызове. Этот метод должен возвращать True или False, в зависимости от того, что вы хотите, чтобы он возвращал.

Управление доступом к собственности

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

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

Вы можете легко вызвать ошибку при определении элементов управления доступом к атрибутам. Рассмотрим следующий пример.

def __setattr__(self, name, value):
    self.name = value
    #每当属性被赋值的时候, ``__setattr__()`` 会被调用,这样就造成了递归调用。
    #这意味这会调用 ``self.__setattr__('name', value)`` ,每次方法会调用自己。这样会造成程序崩溃。

def __setattr__(self, name, value):
    self.__dict__[name] = value  #给类中的属性名分配值
    #定制特有属性

Волшебные методы Python сильны, но вместе с этим приходит и ответственность. Очень важно знать, как правильно его использовать.

Итак, что мы знаем о доступе к настраиваемым атрибутам. Его не следует использовать легкомысленно. На самом деле, это очень мощно. Но он существует по причине: Python не пытается сделать плохие вещи невозможными, он делает их сложными. Свобода превыше всего, поэтому вы можете делать все, что хотите. Ниже приведен пример управления конкретным свойством (мы используемsuperпотому что не во всех классах есть__dict__Атрибуты):

class AccessCounter:
    '''一个包含计数器的控制权限的类每当值被改变时计数器会加一'''

    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)]

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

Есть много способов заставить ваши классы Python вести себя как встроенные последовательности (dict, tuple, list, string и т. д.). Это, безусловно, мой любимый волшебный метод, потому что он дает вам большой контроль и заставляет многие функции хорошо работать с экземплярами вашего класса. Но прежде чем мы начнем, нам нужно поговорить о некоторых предпосылках.

Требование

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

Почему мы сейчас обсуждаем протоколы? Потому что, если вы хотите настроить тип контейнера, вам нужно использовать эти протоколы. Во-первых, существует протокол реализации неизменяемых контейнеров: для реализации неизменяемых контейнеров можно только определить__len__а также__getitem__(об этом позже). Протокол изменяемого контейнера требует всех свойств неизменяемых контейнеров в дополнение к__setitem__а также__delitem__. В конечном счете, если вы хотите, чтобы ваш объект был повторяемым, вам нужно определить__iter__вернет итератор. Итераторы должны соответствовать протоколу итераторов и должны иметь__iter__(возвращает себя) иnext.

магия за контейнером

Это волшебные методы, используемые контейнером.__len__(self)конечно длина контейнера. Часть протокола, необходимая как для изменяемых, так и для неизменяемых контейнеров.__getitem__(self, key)Определите, когда осуществляется доступ к записи, используйте символself[key]. Это также часть протокола, который есть как у неизменяемых, так и у изменяемых контейнеров. Если ключ не того типа иKeyErrorИли нет подходящего значения. то он должен бросить соответствующийTypeErrorаномальный.__setitem__(self, key, value)Чтобы определить поведение, когда элементу присваивается значение, используйтеself[key] = value. Это часть как изменяемых, так и неизменяемых протоколов контейнеров.__delitem__(self, key)Определяет поведение при удалении записи (например,del self[key]). Это всего лишь часть протокола изменяемого контейнера. При использовании недопустимого ключа должны создаваться соответствующие исключения.__iter__(self)Возвращает итератор по контейнеру. Итераторы возвращаются во многих случаях, особенно когда встроенныйiter()при вызове метода или при использованииfor x in containerпуть время цикла. Итераторы являются собственными объектами, они должны быть определены для возвратаselfиз__iter__метод.__reversed__(self)понять, когдаreversed()Поведение при вызове. Должен вернуть обратную версию списка.__contains__(self, item)при звонкеinа такжеnot inчтобы проверить, существует ли член__contains__определено. Вы спросите, почему это не является частью протокола последовательности? Это потому, что когда__contains__Если он не определен, Python перебирает последовательность и возвращается, когда находит нужное значение.True.__concat__(self, other)Наконец-то можно пройти__concat__Чтобы определить поведение при подключении двух последовательностей с другими. когда+Когда оператор вызывается, он возвращаетselfа такжеother.__concat__Новая последовательность, полученная в результате вызова.

один пример

В нашем случае давайте взглянем на реализацию оператора построения функции, который вы могли бы использовать в других языках (например, в Haskell).

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):
    #如果键的类型或者值无效,列表值将会抛出错误
    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 reversed(self.values)

def append(self, value):
    self.values.append(value)
def head(self):
    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]

отражение

Вы можете управлять управлением с помощью магического методаisinstance()а такжеissubclass()Отражающее поведение для встроенных методов. Вот эти магические методы:

__instancecheck__(self, instance)

Проверьте, является ли экземпляр экземпляром класса, который вы определили

__subclasscheck__(self, subclass)

Проверьте, является ли класс подклассом определенного вами класса

Кажется, что у этих методов мало вариантов использования, что может быть правдой. Я не буду тратить больше времени на эти волшебные методы, потому что они не очень важны, но они отражают некоторые основные свойства объектно-ориентированного программирования в Python: очень легко что-то сделать, даже если это не очень нужно. Эти волшебные методы не выглядят очень полезными, но вы будете счастливы иметь эту функцию, когда она вам понадобится.

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

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

управление сессиями

В Python 2.5 было определено новое ключевое слово для эксплуатации кода.withутверждение. Управление сеансом не является чем-то необычным в Python (ранее реализованным как часть библиотеки), покаPEP343после добавления. Это называется структурой языка первого уровня. Возможно, вы уже видели подобное заявление:

with open('foo.txt') as bar:
# perform some action with bar

Контроллер обратного вызова путем упаковкиwithоператоры для установки и очистки поведения. Поведение контроллера обратного вызова определяется двумя магическими методами:__enter__(self)определить при использованииwithоператор, когда диспетчер сеансов должен вести себя при создании начального блока. Уведомление__enter__Возвращаемое значениеwithцель заявления илиasПосле того, как имя связано.__exit__(self, exception_type, exception_value, traceback)Определяет, что должен делать диспетчер сеансов, когда блок кода выполняется или завершается. Его можно использовать для обработки исключений, очистки работы или выполнения какой-либо рутинной работы после выполнения блока кода. Если блок кода выполняется успешно,exception_type , exception_value, а такжеtracebackбудетNone. В противном случае вы можете обработать исключение или передать его пользователю напрямую. Если вы хотите обработать это исключение, подтвердите__exit__вернется после того, как все закончитсяTrue. Если вы хотите, чтобы исключения обрабатывались диспетчером сеансов, сделайте это.

__enterа также__exit__Полезно для классов, которые имеют четко определенное и рутинное поведение для настройки и очистки. Вы также можете использовать эти методы для создания универсальных менеджеров сеансов, которые могут обертывать другие объекты. Ниже приведен пример.

class Closer:
'''通过with语句和一个close方法来关闭一个对象的会话管理器'''

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

def __enter__(self):
    return self.obj # bound to target

def __exit__(self, exception_type, exception_val, trace):
    try:
        self.obj.close()
    except AttributeError: # obj isn't closable
        print 'Not closable.'
        return True # exception handled successfully

Ниже приводится использованиеCloserПример использования FTP-ссылки для демонстрации (закрываемый сокет):

>>> from magicmethods import Closer
>>> from ftplib import FTP
>>> with Closer(FTP('ftp.somesite.com')) as conn:
...     conn.dir()
...
>>> conn.dir()
>>> with Closer(int(5)) as i:
...     i += 1
...
Not closable.
>>> i
6

Вы видели, как наша оболочка молча обрабатывает правильное и недопустимое поведение при использовании. В этом сила менеджеров сессий и магических методов.

Создайте дескриптор для объекта

Дескрипторы — это классы, доступ к которым осуществляется с помощью функций get, set и delete. Конечно, другие объекты также могут быть изменены. Дескрипторы не поощряются, им суждено принадлежать классу-владельцу. Дескрипторы полезны при создании объектно-ориентированных баз данных или классов с взаимозависимыми свойствами. Типичное использование — использование разных единиц измерения для представления одного и того же значения или для представления дополнительных свойств некоторых данных (например, точки в системе координат, которая содержит информацию о расстоянии от этой точки до дальней точки).

Чтобы построить дескриптор, класс должен иметь как минимум__get__или__set__один из них и__delete__реализуется. Давайте посмотрим на эти волшебные методы.__get__(self, instance, owner)определяет поведение при извлечении значения дескриптора,instanceявляется экземпляром объекта-владельца.ownerявляется самим классом-владельцем.__set__(self, instance, value)Определяет поведение при изменении значения дескриптора.instanceявляется экземпляром класса владельцаvalueзначение, которое необходимо установить.__delete__(self, instance)Определяет поведение при удалении значения дескриптора.instanceявляется экземпляром объекта-владельца. Ниже приведен пример дескриптора: преобразование единиц измерения.

class Meter(object):
'''Descriptor for a meter.'''

    def __init__(self, value=0.0):
    self.value = float(value)
    def __get__(self, instance, owner):
    return self.value
    def __set__(self, instance, value):
    self.value = float(value)

class Foot(object):
    '''Descriptor for a foot.'''

    def __get__(self, instance, owner):
    return instance.meter * 3.2808
    def __set__(self, instance, value):
    instance.meter = float(value) / 3.2808

class Distance(object):
    '''Class to represent distance holding two descriptors for feet and
    meters.'''
    meter = Meter()
    foot = Foot()

сохранить свой объект

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

Сериализация данных — очень важная функция, поэтому у нее есть не только связанные модули (Pickle , cPickle), а также собственные протоколы и магические методы, но сначала поговорим о способах сериализации встроенных структур данных.

Травление: простой пример

Давайте углубимся в Pickle, скажем, вам нужно временно сохранить словарь сейчас, вы можете записать его в файл и быть осторожным, чтобы убедиться, что формат правильный, затем используйте exec() или обработайте ввод файла для восстановления данных, на самом деле Это очень небезопасно. Если вы используете текст для хранения каких-то важных данных, любые изменения каким-либо образом могут повлиять на вашу программу, начиная от сбоев программы и заканчивая вредоносными программами. Итак, давайте использовать Pickle вместо этого:

import pickle

data = {'foo': [1, 2, 3],
        'bar': ('Hello', 'world!'),
        'baz': True}
jar = open('data.pkl', 'wb')
pickle.dump(data, jar) # write the pickled data to the file jar
jar.close()

Что ж, через несколько часов нам нужно его использовать, просто распакуйте его:

import pickle

pkl_file = open('data.pkl', 'rb') # connect to the pickled data
data = pickle.load(pkl_file) # load it into a variable
print data
pkl_file.close()

Как и следовало ожидать, данные вернулись в целости и сохранности!

Также дам вам совет: pickle не идеален, файлы Pickle легко случайно или намеренно повредить, файлы Pickle немного более безопасны, чем обычные текстовые файлы, но все же могут использоваться для запуска вредоносных программ. Pickle не совместим с разными версиями, поэтому старайтесь не распространять текст Pickle, потому что другие могут не открыться. Тем не менее, Pickle по-прежнему очень полезен при кэшировании или других данных, которые необходимо сериализовать.

Сериализация собственных объектов

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

__getinitargs__(self)

Если вы хотите позвонить во время десериализации__init__, вы можете определить__getinitargs__метод, этот метод должен возвращать массив__init__В параметрах вызова обратите внимание, что этот метод работает только с классами старого стиля.

__getnewargs__(self)

Для классов нового стиля вы можете определить любой__new__параметры в методе. Этот метод также должен возвращать массив__new__параметры вызова.

__getstate__(self)

Вы можете настроить состояние, возвращаемое при сериализации объекта, вместо использования__dictметод, при десериализации объекта возвращаемое состояние будет__setstate__вызов метода.

__setstate__(self, state)

Когда объект десериализуется, если__setstate__Если определено, состояние объекта будет передано ему вместо__dict__. Этот метод и__getstate__В паре, когда определены оба метода, вы имеете полный контроль над всем процессом сериализации и десериализации.

пример

Возьмем, к примеру, Slate. Это программа, которая записывает значение и время его записи. Однако этот Slate немного особенный. Текущее значение не сохраняется.

import time

class Slate:
    '''Class to store a string and a changelog, and forget its value when
    pickled.'''

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

    def change(self, new_value):
        # Change the value. Commit last value to history
        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 '%s\t %s' % (k, v)

    def __getstate__(self):
        # Deliberately do not return self.value or self.last_change.
        # We want to have a "blank slate" when we unpickle.
        return self.history

    def __setstate__(self, state):
        # Make self.history = state and last_change and value undefined
        self.history = state
        self.value, self.last_change = None, None

В заключение

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

Приложение: Как вызывать магические методы

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

магический метод метод вызова объяснять
__new__(cls [,...]) instance = MyClass(arg1, arg2) __new__ вызывается при создании экземпляра
__init__(self [,...]) instance = MyClass(arg1, arg2) __init__ вызывается при создании экземпляра
__cmp__(self, other) я == другой, я > другой и т. д. вызывается при сравнении
__pos__(self) +self унарный плюс оператор
__neg__(self) -self унарный минус оператор
__invert__(self) ~self оператор отрицания
__index__(self) x[self] Когда объект используется в качестве индекса
__nonzero__(self) bool(self) логическое значение объекта
__getattr__(self, name) Self.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 self, value not in self При проверке отношений с помощью оператора in
__concat__(self, value) self + other При соединении двух объектов
__call__(self [,...]) self(args) При «вызове» объекта
__enter__(self) with self as x: с управлением средой заявлений
__exit__(self, exc, val, trace) with self as x: с управлением средой заявлений
__getstate__(self) pickle.dump(pkl_file, self) Сериализация
__setstate__(self) data = pickle.load(pkl_file) Сериализация

Надеюсь, эта таблица поможет вам ответить на вопрос, когда какой метод использовать.