Специальный метод: 23. Специальные методы — Документация Python для сетевых инженеров 3.0

Содержание

Руководство по магическим методам в Питоне / Хабр

Это перевод 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, [...)
    Это первый метод, который будет вызван при инициализации объекта. Он принимает в качестве параметров класс и потом любые другие аргументы, которые будут переданы в __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__ не должен использоваться почти никогда; используйте его с осторожностью!

    Замечание от переводчика: svetlov отмечает, что здесь автор ошибается, на самом деле __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)
    Самый базовый из методов сравнения.
    Он, в действительности, определяет поведение для всех операторов сравнения (>, ==, !=, итд.), но не всегда так, как вам это нужно (например, если эквивалентность двух экземпляров определяется по одному критерию, а то что один больше другого по какому-нибудь другому). __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)
    Остаток от деления, оператор %. .

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

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

some_object + other

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

other + some_object

Таким образом, все эти магические методы делают то же самое, что и их обычные версии, за исключением выполнения операции с

other

в качестве первого операнда и

self

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

__radd__

вы можете ограничиться вызовом

__add__

да и всё. Заметьте, что объект слева от оператора (

other

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

some_object.__radd__

будет вызван только если в

other

не определён

__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(), когда вызывается divmod(other, self). .
Составное присваивание

В Питоне широко представлены и магические методы для составного присваивания. Вы скорее всего уже знакомы с составным присваиванием, это комбинация «обычного» оператора и присваивания. Если всё ещё непонятно, вот пример:

x = 5
x += 1 # другими словами x = x + 1

Каждый из этих методов должен возвращать значение, которое будет присвоено переменной слева (например, для

a += b

,

__iadd__

должен вернуть

a + b

, что будет присвоено

a

). Вот список:

  • __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 с присваиванием, оператор ^=.
Магические методы преобразования типов

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

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

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

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

Протоколы

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

протоколах

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

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

Магия контейнеров

Без дальнейшего промедления, вот магические методы, используемые контейнерами:

  • __len__(self)
    Возвращает количество элементов в контейнере. Часть протоколов для изменяемого и неизменяемого контейнеров.
  • __getitem__(self, key)
    Определяет поведение при доступе к элементу, используя синтаксис self[key]. Тоже относится и к протоколу изменяемых и к протоколу неизменяемых контейнеров. Должен выбрасывать соответствующие исключения: TypeError если неправильный тип ключа и KeyError если ключу не соответствует никакого значения.
  • __setitem__(self, key, value)
    Определяет поведение при присваивании значения элементу, используя синтаксис self[nkey] = value. Часть протокола изменяемого контейнера. Опять же, вы должны выбрасывать KeyError и TypeError в соответсвующих случаях.
  • __delitem__(self, key)
    Определяет поведение при удалении элемента (то есть del self[key]). Это часть только протокола для изменяемого контейнера. Вы должны выбрасывать соответствующее исключение, если ключ некорректен.
  • __iter__(self)
    Должен вернуть итератор для контейнера. Итераторы возвращаются в множестве ситуаций, главным образом для встроенной функции iter() и в случае перебора элементов контейнера выражением for x in container:. Итераторы сами по себе объекты и они тоже должны определять метод __iter__, который возвращает self.
  • __reversed__(self)
    Вызывается чтобы определить поведения для встроенной функции reversed(). Должен вернуть обратную версию последовательности. Реализуйте метод только если класс упорядоченный, как список или кортеж.
  • __contains__(self, item)
    __contains__ предназначен для проверки принадлежности элемента с помощью in и not in. Вы спросите, почему же это не часть протокола последовательности? Потому что когда __contains__ не определён, Питон просто перебирает всю последовательность элемент за элементом и возвращает True если находит нужный.
  • __missing__(self, key)
    __missing__ используется при наследовании от dict. Определяет поведение для для каждого случая, когда пытаются получить элемент по несуществующему ключу (так, например, если у меня есть словарь 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):
        # получить первый элемент
        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]

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

Counter

,

OrderedDict

,

NamedTuple

.

Отражение

Вы можете контролировать и отражение, использующее встроенные функции

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_type, exception_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

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

contextlib

, который включает в себя

contextlib. closing()

— менеджер контекста, который делает приблизительно то же (без какой-либо обработки случая, когда объект не имеет метода

close()

).

Абстрактные базовые классы

Смотри

http://docs.python.org/2/library/abc.html

.

Построение дескрипторов

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

Чтобы класс стал дескриптором, он должен реализовать по крайней мере один метод из __get__, __set__ или __delete__. Давайте рассмотрим эти магические методы:

  • __get__(self, instance, instance_class)
    Определяет поведение при возвращении значения из дескриптора. instance это объект, для чьего атрибута-дескриптора вызывается метод. owner это тип (класс) объекта.
  • __set__(self, instance, value)
    Определяет поведение при изменении значения из дескриптора. instance это объект, для чьего атрибута-дескриптора вызывается метод. value это значение для установки в дескриптор.
  • __delete__(self, instance)
    Определяет поведение для удаления значения из дескриптора. instance это объект, владеющий дескриптором.

Теперь пример полезного использования дескрипторов: преобразование единиц измерения.

class Meter(object):
    '''Дескриптор для метра.'''

    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):
    '''Дескриптор для фута.'''

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

class Distance(object):
    '''Класс, описывающий расстояние, содержит два дескриптора для футов и
    метров.'''
    meter = Meter()
    foot = Foot()

Копирование

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

copy

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

  • __copy__(self)
    Определяет поведение copy.copy() для экземпляра вашего класса. copy.copy() возвращает поверхностную копию вашего объекта — это означает, что хоть сам объект и создан заново, все его данные ссылаются на данные оригинального объекта. И при изменении данных нового объекта, изменения будут происходить и в оригинальном.
  • __deepcopy__(self, memodict={})
    Определяет поведение copy.deepcopy() для экземпляров вашего класса. copy.deepcopy() возвращает глубокую копию вашего объекта — копируются и объект и его данные. memodict это кэш предыдущих скопированных объектов, он предназначен для оптимизации копирования и предотвращения бесконечной рекурсии, когда копируются рекурсивные структуры данных. Когда вы хотите полностью скопировать какой-нибудь конкретный атрибут, вызовите на нём copy.deepcopy() с первым параметром memodict.

Когда использовать эти магические методы? Как обычно — в любом случае, когда вам необходимо больше, чем стандартное поведение. Например, если вы пытаетесь скопировать объект, который содержит кэш как словарь (возможно, очень большой словарь), то может быть вам и не нужно копировать весь кэш, а обойтись всего одним в общей памяти объектов.

Использование модуля pickle на своих объектах

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

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

Вкратце про сериализацию

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

exec()

, или прочитав файл. Но это в лучшем случае рискованно: если вы храните важные данные в тексте, он может быть повреждён или изменён множеством способов, с целью обрушить вашу программу или, вообще, запустить какой-нибудь опасный код на вашем компьютере. Лучше использовать pickle:

import pickle

data = {'foo': [1, 2, 3],
        'bar': ('Hello', 'world!'),
        'baz': True}
jar = open('data.pkl', 'wb')
pickle.dump(data, jar) # записать сериализованные данные в jar
jar.close()

И вот, спустя несколько часов, нам снова нужен наш словарь:

import pickle

pkl_file = open('data.pkl', 'rb') # открываем
data = pickle.load(pkl_file) # сохраняем в переменную
print data
pkl_file.close()

Что произошло? Точно то, что и ожидалось.

data

как-будто всегда тут и была.

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

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

Модуль pickle не только для встроенных типов. Он может использоваться с каждым классом, реализующим его протокол. Этот протокол содержит четыре необязательных метода, позволяющих настроить то, как pickle будет с ними обращаться (есть некоторые различия для расширений на C, но это за рамками нашего руководства):

  • __getinitargs__(self)
    Если вы хотите, чтобы после десериализации вашего класса был вызыван __init__, вы можете определить __getinitargs__, который должен вернуть кортеж аргументов, который будет отправлен в __init__. Заметьте, что этот метод работает только с классами старого стиля.
  • __getnewargs__(self)
    Для классов нового стиля вы можете определить, какие параметры будут переданы в __new__ во время десериализации. Этот метод так же должен вернуть кортеж аргументов, которые будут отправлены в __new__.
  • __getstate__(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 '%s\t %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 == other, self > 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 self, value 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) Сериализация

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

Дополнение 2: Изменения в Питоне 3

Опишем несколько главных случаев, когда Питон 3 отличается от 2.x в терминах его объектной модели:

  • Так как в Питоне 3 различий между строкой и юникодом больше нет, __unicode__ исчез, а появился __bytes__ (который ведёт себя так же как __str__ и __unicode__ в 2. 7) для новых встроенных функций построения байтовых массивов.
  • Так как деление в Питоне 3 теперь по-умолчанию «правильное деление», __div__ больше нет.
  • __coerce__ больше нет, из-за избыточности и странного поведения.
  • __cmp__ больше нет, из-за избыточности.
  • __nonzero__ было переименовано в __bool__.
  • next у итераторов был переименован в __next__.

Методы обследования в пульмонологии | клиника «Евромед»

Специальные методы обследования в пульмонологии
1. Физикальные методы исследования.

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

2. Инструментальные методы диагностики.

Спирометрия (спирография) – исследование функции внешнего дыхания. Исследование позволяет оценить жизненную емкость легких, функциональную проходимость бронхов, форсированные объемы дыхания. Спирометрия — обязательный метод обследования в пульмонологии, особенно важен для диагностики таких болезней как бронхиальная астма, ХОБЛ, бронхит и т.д. В ряде случаев спирометрия выполняется с медикаментозной нагрузкой, т.н. тест с бронхолитиком. Вначале пациент делает дыхательные маневры в аппарат в обычном состоянии, затем его просят провести ингаляцию бронхорасширяющего вещества и спирометрию повторяют. При этом проводится сравнение результатов до, и после ингаляции. Если разница будет значима (прирост более 15%) – это свидетельствует об обратимых изменениях, которые наиболее вероятны для бронхиальной астмы. В арсенале современной пульмонологии имеется такой метод как спирометрия с бронхопровакационной пробой. Например, при ингаляции метахолина здоровым человеком – никаких изменений не произойдет, та же доза для больного с «истинной» бронхиальной астмой спровоцирует приступ бронхоспазма. Для проведения подобных исследований необходимо специальное оборудование, обученные специалисты и возможность оказать экстренную помощь пациенту. 

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

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

Рентгенологические методы:

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

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

Мультиспиральная компьютерная томография — на сегодняшний день это самый лучший способ не инвазивной диагностики структурных изменений органов дыхания. Разрешающая способность компьютерной томографии в десятки раз превышает возможности обычной рентгенографии. Томограф позволяет провести детальную оценку состояния любого органа грудной клетки на структурном уровне с точностью до миллиметров. Помимо этого возможно точно измерить размеры патологических очагов, точно указать их локализацию, плотность, оценить их пропускную способность для крови (при контрастировании), создать трехмерные изображения и многое другое. В «Евромеде» мультиспиральная компьютерная томография проводится на уникальном 25-срезовом томографе SomatomDefinitionFlash — единственной установке за Уралом.

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

Специальной подготовки не требуется. Если Вы являетесь носителем кардиостимулятора, об этом необходимо предупредить персонал рентгенологического кабинета. Желательно снять с шеи цепочки и украшения, которые могут помешать чтению снимков.

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

Как правильно подготовиться к фибробронхоскопии?

Исследование проводят утром натощак. Если Вам необходимо принять утром препараты (таблетки, капли, капсулы) – Вы можете это сделать, запив их количеством воды необходимым для этого, но не более. Если Вы страдаете сахарным диабетом, обязательно имейте при себе сахар или конфеты. Желательно больным с сахарным диабетом заранее позаботится о времени записи на исследование, и записаться не самые первые часы. В день исследование не следует курить (курить, вообще никогда не следует!!!). Пользоваться ингаляторами можно. 

Эхокардиография (ЭХОКГ) — ультразвуковой метод исследования сердца, который очень важен в пульмонологии, поскольку многие хронические заболевания легких приводят к вторичным изменениям в сердце, усугубляющим тяжесть заболевания. С другой стороны, болезни сердца, могут проявляться легочными жалобами (одышка, кашель, кровохарканье). ЭХОКГ позволяет провести измерения размеров камер сердца, толщину миокарда, оценить состояние клапанного аппарата сердца. Важным методом диагностики, используемым при ЭХОКГ, является допплерометрия с измерением давления в легочной артерии.

Как правильно подготовиться к эхокардиографии?

Специальной подготовки не требуется. Желательно не пить перед исследованием кофе или крепкий чай, что бы не провоцировать учащенное сердцебиение.

3. Лабораторные методы диагностики, широко используемые в пульмонологии.

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

Специальной подготовки не требуется. Желательно не пить перед исследованием кофе или крепкий чай, что бы не провоцировать учащенное сердцебиение.

Как правильно подготовиться к сдаче мокроты?

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

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

Общие и специальные методы исследования

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

1. Методы эмпирического исследования (наблюдение, сравнение, измерение, эксперимент).

2. Методы, используемые как на эмпирическом, так и на теоретическом уровне исследования (абстрагирование, анализ и синтез, индукция и дедукция, моделирование и др.).

3. Методы теоретического исследования (восхождение от абстрактного к конкретному и др. ).

Общие и специальные методы могут быть количественными, качественными и качественно-количественными.

2.3. Наблюдение

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

Под термином «наблюдение» мы понимаем систематическое наблюдение за человеком с

целью регистрации, описания, анализа и интерпретации его поведения. Существует два основных типа наблюдений, которые существенно отличаются между собой. Метод включенного наблюдения предполагает получение качественных данных. При использовании этого метода исследователь стремится понять, какое значение люди приписывают своим действиям. Применяя метод включенного наблюдения, «исследователь стремится принимать участие в жизни участников исследований и их деятельности и таким образом стать членом их группы, организации или сообщества. Это позволяет исследователю приобрести необходимый опыт совместной жизни не путем наблюдения, а путем собственных переживаний» (Gill and Johnson, 1997). Этот метод до сих пор успешно используется для получения информации о событиях, происходящих в самых разных социальных условиях. Включенное наблюдение основывается на широком круге источников информации. Наблюдатель явно или неявно соучаствует в повседневной жизни людей в течение достаточно продолжительного времени, наблюдая за происходящим, прислушиваясь к сказанному, задавая вопросы. В сущности, он собирает любые доступные данные, которые могут пролить свет на интересующие его (или ее) проблемы.

Специальные методы испытаний — Энциклопедия по машиностроению XXL

Специальные методы испытаний материалов неорганического происхождения  [c. 360]

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

Поскольку при быстром нагружении развитие пластических деформаций затруднено, главенствующим механизмом разрушения оказывается развитие трещин, и материал обостренно воспринимает местные повышенные напряжения. Это позволяет создать специальный метод испытания материала на чувствительность к хрупкому разрушению — так называемое испытание на ударную вязкость.  [c.84]


Оценить способность материалов сопротивляться развитию трещин в условиях циклического нагружения позволяет специальный метод испытаний [234, 236]. Трещиностойкость при циклическом на-  [c.144]

Существуют специальные методы испытания для определения стойкости металла к ударной коррозии в условиях локального нагрева (коррозии в месте нагрева), однако в определении коррозионной стойкости котельной стали и материалов конденсаторных трубок температурный фактор обычно не учитывается.[c.180]

Специальные методы испытания  [c.76]

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

Анализ работы на изнашивание основных типов изделий и разработка на этой основе специальных методов испытания эластомеров и пластиков на износ с учетом работы каждого данного типа изделия.  [c.112]

Такая оценка не всегда может быть сделана по значениям механических характеристик, определяемым методами механических испытаний. Поэтому разработка и применение специальных методов испытания материалов на изнашивание для определения их износостойкости в различных условиях трения столь же необходимы, как и постановка специальных лабораторных испытаний при оценке жаропрочности, сопротивления усталости и других характеристик служебной прочности.  [c.229]

СПЕЦИАЛЬНЫЕ МЕТОДЫ ИСПЫТАНИЙ  [c.26]

При внешнем нагреве образцов неравномерность электрических свойств не активизирует окисление участков с повышенным электрическим сопротивлением. При электронагреве это имеет место, поэтому для сплавов сопротивления разработаны специальные методы испытания проволочных образцов — с нагревом их электрическим током метод стендовых испытаний нагревателей на срок службы и метод ускоренного испытания на живучесть.  [c.26]

Специальные методы испытаний Ю.П. Солнцев)  [c.76]

Специальные методы испытаний представляют значительный интерес, но для их проведения надо иметь мощные испытательные установки.[c.78]

Специальные методы испытаний 76  [c.1079]


Осевое растяжение фактически является эксцентричным растяжением (разрушение происходит от одного края образца через все сечение). Испытания дают большой разброс результатов из-за непостоянства величины эксцентриситета. Кроме того, существуют специальные методы испытания на растяжение с перекосом или с заранее заданным постоянным или изменяемым эксцентриситетом.  [c.181]

Особенности поведения металлов в области высоких температур вызывают необходимость применения специальных методов испытаний и установления особых критериев механической прочности в нагретом состоянии. В большинстве случаев жаропрочность оценивается с помощью определения следующих характеристик  [c.325]

СПЕЦИАЛЬНЫЕ МЕТОДЫ ИСПЫТАНИЯ СЛЮДЯНОЙ ИЗОЛЯЦИИ  [c.261]

Специальные методы испытания (ГОСТ 2189—52)  [c.174]

Рассмотренная выше гипотеза свидетельствует о том, что состав сплава является определяющим фактором для оценки технологической прочности сплавов. Для экспериментального определения запаса технологической прочности необходимо располагать специальными методами испытаний, позволяющими  [c.558]

СПЕЦИАЛЬНЫЕ МЕТОДЫ ИСПЫТАНИЯ ПИГМЕНТОВ  [c.103]

В настоящей главе мы остановимся на некоторых специальных методах испытания шпатлевок. При испытании шпатлевки,  [c.447]

Для определения этих важных качеств топлив разработаны специальные методы испытаний.  [c.160]

Сварные соединения и конструкции без значительных концентраторов напряжений хорошо сопротивляются удар ным нагрузкам, в том числе и при отрицательных температурах, при условии достаточно высокого качества исходного основного металла и соответствующего технологического процесса сварки. Для оценки качества сварных соединений при низких температурах и ударных нагрузках разработаны различные специальные методы испытаний. В частности, на рис. 11-6, а представлен образец Института электросварки им. Е. О. Патона. Ребро образца состоит из двух частей, приваренных угловыми швами к целой пластине. Наличие узкой щели  [c.256]

Методы испытаний неметаллических материалов, как, иаирн-мер определение плотности и массы, прочностных показателей, не отличаются от общеизвестных, широко освещенных в техническом литературе, и в специальном описании не нуждаются. Ниже мы приводим описание только некоторых специальных методов испытаний, являющихся необходимыми для оценки неметаллических коррозионностойких материалов и защитных покрытиГь  [c.360]

Для определения плеснестойкости материалов и изделий существуют специальные методы испытаний (ГОСТ 9.048—75, ГОСТ 9.049—75, ГОСТ 0.950—75 и др.). Они заключаются в том, что испытуемый материал искусственно заражается спорами плесневых грибков, а затем выдерживается в условиях, оптимальных для развития плесени, в течение определенного времени.  [c.197]

Методы испытаний тканей изложены в ГОСТе 1090—41 Ткани текстильные. Методы испытаний н в частично заменивших его ГОСТах 3810—47 Методы отбора образцов для лабораторных испытаний , 3811—47 Методы определения линейных размеров и веса , 3812—47 Методы определения плотности , 3813—47 Методы определения прочности , 3814—56 , Методы определения сминаемости, раздви-гаемости и осыпаемости , 3815—47 Методы определения качества ворса , 3816—61 Методы определения гигроскопических свойств , а также в ГОСТах 5012—66 Методы определения усадки шерстяной ткани после замочки , 4659—49 Ткани шерстяные и смешанные. Методы химических испытаний , 8710—58 Ткани текстильные. Метод определения усадки после стирки , 6303—59 Ткани и изделия льняные, полульняные и хлопчатобумажные. Методы химических испытаний , 8845—58, 8846—58, 8847—64, 8844—58 Полотна трикотажные. Отбор проб и методы физикомеханических испытаний . Некоторые специальные методы испытаний тканей (например, коэффициент неровноты стренг по удлинению, усталостная прочность и коэффициент теплостойкости кордтканей) изложены в стандартах на их изготовление.[c.343]

Специальные методы испытаний. Для диагностирования отдельных агрегатов роботов могут применяться специальные виды аппаратуры, например, для диагностирования объемных гидромашин (насосов, гидроцилиндров, гидромоторов) Р. А. Макаровыми и А. М. Шоломом предложена аппаратура, основанная на статопараметрическом и термодинамическом методах исследования [24, с. 35-42 21, с. 123-129].  [c.81]


Однако желание приблизиться к натуральным условиям привело к разработке специальных методов испытания на термиче-скую усталость. Основной метод, базирующийся на установке типа Коффина (рис. АЗ.43) и ее последующих модификациях [17, 28], узаконен ГОСТ 25.505-85. Нагрев тонкостенного труб- laxoro образца осуществляется пропусканием тока, охлажде-ние -— продувкой воздуха. Усилия, возникающие в образце, оп-  [c.119]

Кроме стандартных испытаний, разрабатывают и применяют специальные методы испытаний в соответствии с типом материала и изделия. Полимерные пленки и сварные изделия из них испытывают на одноосное статическое растяжение при помощи машины РИП-10 при температурах от —70 до -Ь300°С в воздушной и жидкой среде. Машина имеет повышенную производительность за счет того, что одновременно нагреваются или охлаждаются несколько образцов, а испытывается один из них. Она состоит (рис. 199) из механической части, измерительной системы и термостата испытуемые образцы 1 закрепляются в зажимах 2, установленных в шестипозиционной кассете 3. Верхние зажимы тягой 4 и разъемным шарниром 5 поочередно соединяются с тензометрическим динамометром 6, закрепленным на подвижном ползуне 7. Ползун помещается по вертикали  [c.244]


Специальные способы научного исследования: особенности и описание

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

Классификация

Специальные методы исследования подразделяют по научным отраслям:

  • медицинские;
  • математические;
  • социально-экономические;
  • биологические;
  • правовые.

С учетом уровня познания выделяют теоретические, эмпирические, метапредметные уровни. Специальные методы эмпирического вида – это описание, наблюдение, измерение, счет, тестирование, анкетный опрос, моделирование, эксперимент, собеседование.

Среди методов теоретического плана отмечают абстрагирование, формализацию, аксиому, синтез, аналогию, дедукцию, индукцию. Специальные методы метатеоретического уровня – это метафизика, диалектика.

Деление по степени общности

С учетом сферы использования и степени общности выделяют:

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

Важные термины

Специальные методы исследования связаны с процедурой и методикой научного познания. Техникой исследования называют сумму специальных приемов для применения определенного метода. Процедурой исследования считается последовательность действий, вариант организации непосредственного исследования. Методикой считается сумма приемов и способов познания. Любое исследование в науке проводится определенными способами и приемами, с учетом конкретных правил.

Методология

Ее составляют специальные методы, приемы. Данное понятие используют в двух значениях:

  • сумма методов, которые применяются в некой сфере деятельности: политике, науке;
  • учение о научном варианте познания.

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

Уровни методологии

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

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

Философские методы

Специальные научные методы философского плана – это метафизический и диалектический подходы. Они связаны с разными философскими системами. К примеру, Гете соединял метод с идеализмом, Маркс – с материализмом.

Диалектика при рассмотрении явлений и предметов рекомендует исходить из конкретных принципов:

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

Общенаучные методы

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

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

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

Индукция и дедукция

Среди педагогических приемов и методов, без которых сложно представить образование, выделим индукцию и дедукцию.

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

Методы специальной психологии предполагают «выведение» какой-то идеи из иных мыслей. Аналогия, которая предполагает получение информации о явлениях и предметах на базе того, что они обладают сходством с другими объектами, применяется в преподавании учебных дисциплин, а также в воспитательной деятельности.

Среди методов теоретического уровня, которые применяют педагоги в своей работе, интерес представляют гипотетический, аксиоматический виды, а также системный анализ, обобщение.

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

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

Структура гипотетико-дедуктивного метода

Поскольку он применяется в современной педагогике, остановимся на нем подробнее. В его структуру входят:

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

Какие еще специально-педагогические методы используют в настоящее время в отечественной педагогике?

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

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

Формализация – это основа программирования и алгоритмизации. С помощью данного метода осуществляется компьютеризация информации, происходит процесс исследования конкретного знания.

Особенности абстракции

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

В рамках абстрагирования отделяют второстепенные связи и свойства рассматриваемого процесса (явления) от основных характеристик. Существует несколько видов абстрагирования:

  • отождествление, предполагающее выделение общих отношений и свойств рассматриваемых предметов, объединение предметов в отдельный класс;
  • изолирование, касающееся выделения некоторых отношений и свойств, рассмотрение их в виде самостоятельных предметов исследования.

Выделяют и иные виды абстракции: актуальная бесконечность, потенциальная осуществимость.

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

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

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

Заключение

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

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

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

3.1. Специальный метод ведения войны. Формы геополитического противоборства

3.1. Специальный метод ведения войны. Формы геополитического противоборства

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

За время после Второй мировой войны мы проиграли: 45-летнюю глобальную «холодную войну» с Соединенными Штатами Америки; 9-летнюю региональную «необъявленную войну» в Афганистане; военным поражением завершилось 2-летнее «наведение конституционного порядка» в Чечне. Данное утверждение требует уточнения по двум позициям: 1) действительно ли «проиграли»; 2) какие войны, операции и сражения проиграны.

Первое: действительно ли «проиграли»? Цель войны – не разгром вооруженных сил противника или овладение его территорией, а послевоенный мир. Победа есть достижение целей войны. Поэтому отличительным признаком победы является согласие противника на мир или принуждение его к миру на определенных условиях. Исходя из этого, очевиден ответ на вопрос, кто победил в глобальной «холодной» войне, в Афганистане или в Чечне.

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

Второе. Какие войны, операции и сражения проиграны. С точки зрения существующей теории военного искусства, никакие. Ни одна советская дивизия Западной группы войск бывшим вероятным противником не разгромлена. Ни один советский батальон, не говоря о полке, не был разбит в Афганистане. В Чечне вообще якобы во всем виноваты МВД и ФСБ, а армия свою задачу выполнила. Однако десятки элитных дивизий Западной группы войск правомерно отнести к безвозвратным потерям. Мы ушли из Афганистана, так и не увидев победы. Военным поражением завершилась чеченская кампания.

«Холодную» войну мы определили через понятие физики, «необъявленную» войну в Афганистане – через понятие дипломатии, «наведение конституционного порядка в Чечне» – через понятие права. Так какие же войны, операции и сражения мы проиграли, исходя из понятий военного искусства?

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

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

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

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

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

Военные действия до сих пор определяются у нас как действия вооруженных сил по разгрому противника на суше, в воздухе и на море, а предназначение Вооруженных Сил – оборона Российской Федерации с применением средств вооруженной борьбы. Соответственно, предназначение сухопутных войск – отражение вторжения агрессора и удержание занимаемых территорий, разгром группировок войск противника и овладение его территорией. А предназначение воздушно-десантных войск – ведение тех же боевых действий, но в тылу противника. И во всех случаях уничтожение живой силы и техники противника в открытом вооруженном столкновении с противником – в бою, сражении или операции – представляется единственным средством достижения победы.

Такой подход к выбору целей, средств и методов достижения военно-политических целей в современных условиях является анахронизмом. Бой перестал быть единственным средством достижения победы. Понимание, а значит, и решение названных проблем, возможно в рамках теории, которую еще предстоит разработать, – теории специальной операции.

Цели специальных операций, их задачи, способы выполнения, виды специальных действий – должны быть известны широкой российской общественности.

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

Чтобы стоять насмерть в обороне или, преодолевая огонь противника, овладеть рубежом в наступлении, требовалось другое умение, другая логическая организация противоборства. Учитывая могущество современного вооружения, подобные методы вооруженной борьбы, а по сути – методы массового кровопролития – для развитых стран сегодня недопустимы. Требуются другие методы и способы достижения военно-политических целей.

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

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

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

В современных условиях геополитическое противоборство ведется в следующих формах:

– война с применением средств вооруженной борьбы;

– война с применением небоевых средств;

– вооруженный конфликт;

– конфликт с применением небоевых средств;

– естественное соперничество.

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

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

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

Знание (информация) заменяет силу в качестве критерия естественного отбора. Направленность знания (информации) определяется нравственностью. Уровень знания (информации) – образованием. Способность к реализации знания (информации) – психическим состоянием.

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

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

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

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

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

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

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

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

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

Мировая «холодная» война, которую проиграл Советский Союз, представляла собой, по точному определению резидента Фонда национальной и международной безопасности Л. И. Шершнева, первую мировую информационную войну.

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

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

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

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

– Переход от войны с применением небоевых средств к войне с применением средств вооруженной борьбы определяется, как правило, переходом к информационно-технической борьбе.

Данный текст является ознакомительным фрагментом.

Продолжение на ЛитРес

Объектно-ориентированное программирование. Специальные методы.

Существуют 2 особенных декоратора, которые можно повесить на функции внутри класса: — @staticmethod — @classmethod

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

class SomeClass(object):
  @staticmethod
  def hello():
    print("Hello, world")

SomeClass.hello()  # Hello, world
obj = SomeClass()
obj.hello()        # Hello, world
Hello, world
Hello, world

Декоратор @classmethod создаёт метод класса и требует обязательную ссылку на класс (cls). Поэтому объект класса явно передаётся через первый параметр как это с параметром self происходит для обычных методов. Также как и для self, переданный cls может отличаться от класса, в котором определён класс-метод (может быть потомок). Часто используется для создания альтернативных конструкторов.

class SomeClass(object):
  @classmethod
  def hello(cls):
    print('Hello, класс {}'.format(cls.__name__))

SomeClass.hello() # Hello, класс SomeClass
Hello, класс SomeClass

Давайте взглянем на пример кода, в котором одновременно показаны она декоратора, это может помочь понять основные принципы:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # classmethod чтобы создать объект по году рождения,
    # "альтернативный" конструктор
    @classmethod
    def fromBirthYear(cls, name, year):
        return cls(name, 2019 - year)

    # статический метод,чтобы проверить совершеннолетие
    @staticmethod
    def isAdult(age):
        return age > 18

person1 = Person('Петя', 21)
person2 = Person. fromBirthYear('Петя', 1996)

print(person1.age)
print(person2.age)

# print the result
print(Person.isAdult(22))
21
23
True

Важно понимать, что ни classmethod ни staticmethod НЕ являются функциями от конкретного объекта класса и соответственно не принимают self. Подчеркнем еще раз их различия: — classmethod принимает cls как первый параметр, тогда как staticmethod в специальных аргументах не нуждается — classmethod может получать доступ или менять состояние класса, в то время как staticmethod нет — staticmethod в целом вообще ничего не знают про класс. Это просто функция над аргументами, объявленная внутри класса.

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

Эти методы могут эмулировать поведение встроенных классов, но при этом они необязательно существуют у самих встроенных классов. Например, у объектов int при сложении не вызывается метод add. Таким образом, их нельзя переопределить.

Давайте для примера переопределим стандартную операцию сложения. Рассмотрим класс Vector, используемый для представления радиус-векторов на координатной плоскости, и определим в нем поля-координаты: x и y. Также очень хотелось бы определить для векторов операцию +, чтобы их можно было складывать столь же удобно, как и числа или строки.

Для этого необходимо перегрузить операцию +: определить функцию, которая будет использоваться, если операция + будет вызвана для объекта класса Vector. Для этого нужно определить метод add класса Vector, у которого два параметра: неявная ссылка self на экземпляр класса, для которого она будет вызвана (это левый операнд операции +) и явная ссылка other на правый операнд:

class Vector():
    def __init__(self, x = 0, y = 0):
        self.x = x
        self.y = y
    def __add__(self, other):
        return Vector(self. x + other.x, self.y + other.y)

A = Vector(1, 2)
B = Vector(3, 4)
C = A + B
print(C.x, C.y)
4 6

Теперь при вызове оператора A + B Питон вызовет метод A.add(B), то есть вызовет указанный метод, где self = A, other = B.

Аналогично можно определить и оставшиеся операции. Полезной для переопределения является операция <. Она должна возвращать логическое значение True, если левый операнд меньше правого или False в противном случае (также в том случае, если объекты равны). Для переопределения этого операнда нужно определить метод lt (less than):

class Vector:
    def __lt__(self, other):
        return self.x < other.x or self.x == other.x and self.y < other.y

В этом примере оператор вернет True, если у левого операнда поле x меньше, чем у правого операнда, а также если поля x у них равны, а поле y меньше у левого операнда.

После определения оператора <, появляется возможность упорядочивать объекты, используя этот оператор. Теперь можно сортировать списки объектов при помощи метода sort() или функции sorted, при этом будет использоваться именно определенный оператор сравнения <.

Список основных перегружаемых операторов

Метод Использование
Операторы сравнения  
__lt__(self, other) x < y
__le__(self, other) x <= y
__eq__(self, other) x == y
__ne__(self, other) x != y
__gt__(self, other) x > y
__ge__(self, other) x >= y
Арифметические операторы
Сложение
__add__(self, other) x + y
__radd__(self, other) y + x
__iadd__(self, other) x += y
Вычитание
__sub__(self, other) x — y
__rsub__(self, other) y — x
__isub__(self, other) x -= y
Умножение
__mul__(self, other) x * y
__rmul__(self, other) y * x
__imul__(self, other) x *= y
Математическое умножение (например векторное)
__matmul__(self, other) x @ y
__rmatmul__(self, other) y @ x
__imatmul__(self, other) x @= y
Деление
__truediv__(self, other) x / y
__rtruediv__(self, other) y / x
__itruediv__(self, other) x /= y
Целочисленное деление
__floordiv__(self, other) x // y
__rfloordiv__(self, other) y // x
__ifloordiv__(self, other) x //= y
__divmod__(self, other) divmod(x, y)
Остаток
__mod__(self, other) x % y
__rmod__(self, other) y % x
__imod__(self, other) x %= y
Возведение в степень
__pow__(self, other) x ** y
__rpow__(self, other) y ** x
__ipow__(self, other) x **= y
Отрицание, модуль
__pos__(self) +x
__neg__(self) -x
__abs__(self) abs(x)
__len__(self) len(x)
Преобразование к стандартным типам
__int__(self) int(x)
__float__(self) float(x)
__complex__(self) complex(x)
__str__(self) str(x)
__round__(self, digits = 0) round(x, digits)
Блок with
__enter__(self)  
__exit__(self)  

Расширение классов Python с помощью методов Dunder (магических, специальных) — dbader.

org

Что такое «магические методы» Python и как их использовать, чтобы сделать простой класс учетной записи более питоническим.

Что такое методы Дандера?

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

Поскольку быстро стало надоедать говорить «под-под-метод-под-под-под», питонисты приняли термин «дандер-методы», краткую форму «двойной под-метод».

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

Методы Dunder позволяют эмулировать поведение встроенных типов. Например, чтобы получить длину строки, вы можете вызвать len('string') . Но пустое определение класса не поддерживает это поведение из коробки:

 класс NoLenSupport:
    проходить

>>> объект = NoLenSupport()
>>> лен(объект)
TypeError: "объект типа NoLenSupport не имеет len()"
 

Чтобы исправить это, вы можете добавить в свой класс метод __len__ dunder:

 класс LenSupport:
    защита __len__(я):
        вернуться 42

>>> объект = LenSupport()
>>> лен(объект)
42
 

Другой пример — нарезка.Вы можете реализовать метод __getitem__ , который позволяет вам использовать синтаксис нарезки списка Python: obj[start:stop] .

Специальные методы и модель данных Python

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

Вы можете рассматривать модель данных Python как мощный API, с которым вы можете взаимодействовать, реализуя один или несколько методов dunder. Если вы хотите писать больше Pythonic-кода, важно знать, как и когда использовать методы dunder.

Хотя для новичка это может поначалу быть немного ошеломляющим. Не беспокойтесь, в этой статье я расскажу вам об использовании методов dunder на примере простого класса Account .

Расширение класса простой учетной записи

В этой статье я дополню простой класс Python различными методами dunder, чтобы разблокировать следующие функции языка:

  • Инициализация новых объектов
  • Представление объекта
  • Включить итерацию
  • Перегрузка оператора (сравнение)
  • Перегрузка оператора (дополнение)
  • Вызов метода
  • Поддержка диспетчера контекста ( с оператором )

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

Инициализация объекта:

__init__

Сразу после начала занятий мне уже нужен специальный метод. Чтобы создать объекты учетной записи из класса Account , мне нужен конструктор, который в Python представляет собой __init__ dunder:

. Учетная запись класса
:
    """Простой класс счетов"""

    def __init__(я, владелец, количество=0):
        """
        Это конструктор, который позволяет нам создавать
        объекты из этого класса
        """
        себя.владелец = владелец
        самостоятельная сумма = сумма
        self._transactions = []
 

Конструктор заботится о настройке объекта. В этом случае он получает имя владельца, необязательную начальную сумму и определяет список внутренних транзакций для отслеживания депозитов и снятия средств.

Это позволяет нам создавать новые учетные записи следующим образом:

 >>> acc = Account('bob') # сумма по умолчанию = 0
>>> акк = Счет('боб', 10)
 

Представление объекта:

__str__ , __repr__

В Python обычной практикой является предоставление строкового представления вашего объекта для потребителя вашего класса (немного похоже на документацию API. ) Есть два способа сделать это с помощью методов dunder:

  1. __repr__ : «Официальное» строковое представление объекта. Вот как вы могли бы сделать объект класса. Цель __repr__ — быть недвусмысленным.

  2. __str__ : «Неофициальное» или красиво печатное строковое представление объекта. Это для конечного пользователя.

Давайте реализуем эти два метода в классе Account :

Учетная запись класса
:
    # ... (см. выше)

    защита __repr__(сам):
        return 'Account({!r}, {!r})'.format(self.owner, self.amount)

    защита __str__(я):
        return 'Счет {} с начальной суммой: {}'.format(
            self.owner, self.amount)
 

Если вы не хотите жестко кодировать «Учетная запись» в качестве имени класса, вы также можете использовать self.__class__.__name__ для доступа к нему программно.

Если вы хотите реализовать только один из этих методов to-string в классе Python, убедитесь, что это __repr__ .

Теперь я могу запрашивать объект различными способами и всегда получать красивое строковое представление:

 >>> стр(соотв.)
'Счет боба со стартовой суммой: 10'

>>> распечатать (соотв.)
"Счет Боба со стартовой суммой: 10"

>>> репр(акк)
"Счет('боб', 10)"
 

Итерация:

__len__ , __getitem__ , __reversed__

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

 по определению add_transaction (я, сумма):
    если не isinstance (сумма, int):
        поднять ValueError («пожалуйста, используйте int для суммы»)
    self._transactions.append(сумма)
 

Я также определил свойство для расчета баланса на счете, поэтому я могу легко получить к нему доступ с помощью account. balance . Этот метод берет начальную сумму и добавляет сумму всех транзакций:

 @property
деф баланс(я):
    вернуть себя.сумма + сумма(self._transactions)
 

Давайте сделаем несколько пополнений и выводов со счета:

 >>> acc = Account('bob', 10)

>>> acc.add_transaction(20)
>>> acc.add_transaction(-10)
>>> acc.add_transaction(50)
>>> acc.add_transaction(-20)
>>> acc.add_transaction(30)

>>> по балансу
80
 

Теперь у меня есть некоторые данные, и я хочу знать:

  1. Сколько было транзакций?

  2. Индексировать объект учетной записи, чтобы получить номер транзакции …

  3. Цикл по транзакциям

С моим определением класса в настоящее время это невозможно.Все следующие операторы вызывают исключения TypeError :

 >>> длина(соотв.)
Ошибка типа

>>> для t в соотв.:
... печать (т)
Ошибка типа

>>> согл[1]
Ошибка типа
 

Методы Дандера на помощь! Чтобы сделать класс итерируемым, требуется совсем немного кода:

Учетная запись класса
:
    # . .. (см. выше)

    защита __len__(я):
        вернуть len(self._transactions)

    def __getitem__(я, позиция):
        вернуть self._transactions[позиция]
 

Теперь работают предыдущие операторы:

 >>> длина(соотв.)
5

>>> для t в соотв.:
... напечатать(т)
20
-10
50
-20
30

>>> согл[1]
-10
 

Для перебора транзакций в обратном порядке можно реализовать специальный метод __reversed__ :

 по определению __reversed__(я):
    вернуть себя[::-1]

>>> список (обратный (акк))
[30, -20, 50, -10, 20]
 

Чтобы перевернуть список транзакций, я использовал синтаксис обратного списка Python. Мне также пришлось обернуть результат reversed(acc) в вызов list() , потому что reversed() возвращает обратный итератор, а не объект списка, который мы можем красиво напечатать в REPL.Ознакомьтесь с этим руководством по итераторам в Python, если вы хотите узнать больше о том, как работает этот подход.

В общем, этот класс учетной записи теперь начинает казаться мне довольно Pythonic.

Перегрузка оператора для сравнения счетов:

__eq__ , __lt__

Мы все ежедневно пишем десятки операторов для сравнения объектов Python:

 >>> 2 > 1
Истинный

>>> 'а' > 'б'
Ложь
 

Это кажется совершенно естественным, но на самом деле просто удивительно, что здесь происходит за кулисами.Почему > одинаково хорошо работают с целыми числами, строками и другими объектами (при условии, что они одного типа)? Такое полиморфное поведение возможно, потому что эти объекты реализуют один или несколько методов сравнения dunder.

Простой способ проверить это — использовать встроенную функцию dir() :

 >>> директор('а')
['__Добавить__',
...
'__eq__', <----------------
'__формат__',
'__ge__', <---------------
'__получить атрибут__',
'__получить__',
'__getnewargs__',
'__gt__', <---------------
...]
 

Давайте создадим второй объект учетной записи и сравним его с первым (я добавляю пару транзакций для дальнейшего использования):

 >>> acc2 = Account('tim', 100)
>>> acc2. add_transaction(20)
>>> acc2.add_transaction(40)
>>> согл2.баланс
160

>>> акк2 > акк
Ошибка типа:
"'>' не поддерживается между экземплярами "Учетная запись" и "Учетная запись""
 

Что здесь произошло? Мы получили TypeError , потому что я не реализовал никаких ошибок сравнения и не унаследовал их от родительского класса.

Давайте добавим их. Чтобы не реализовывать все методы сравнения dunder, я использую декоратор functools.total_ordering, который позволяет мне сократить путь, реализовав только __eq__ и __lt__ :

.
 из functools import total_ordering

@total_ordering
Учетная запись класса:
    # ... (см. выше)

    def __eq__(я, другой):
        вернуть self.balance == other.balance

    def __lt__(я, другой):
        вернуть self.balance < other.balance
 

И теперь я могу сравнить Аккаунт экземпляров без проблем:

 >>> акк2 > акк
Истинный

>>> акк2 < акк
Ложь

>>> акк == акк2
Ложь
 

Перегрузка оператора для объединения учетных записей:

__add__

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

 >>> 1 + 2
3

>>> 'привет' + 'мир'
'Привет мир'
 

Опять же, мы видим полиморфизм в игре: вы заметили, как + ведут себя по-разному в зависимости от типа объекта? Для целых чисел он суммирует, для строк объединяет. Снова быстрое выполнение dir() для объекта показывает соответствующий интерфейс «dunder» в модели данных:

 >>> директор(1)
[...
'__Добавить__',
...
'__радд__',
...]
 

Наш объект Account еще не поддерживает добавление, поэтому при попытке добавить два его экземпляра возникает ошибка TypeError :

 >>> акк + акк2
TypeError: «неподдерживаемые типы операндов для +:« Учетная запись »и« Учетная запись »»
 

Давайте реализуем __add__ , чтобы можно было объединить две учетные записи. Ожидаемым поведением будет объединение всех атрибутов вместе: имени владельца, а также начальных сумм и транзакций. Для этого мы можем воспользоваться поддержкой итераций, которую мы реализовали ранее:

.
 по определению __add__(я, другой):
    владелец = '{}&{}'.format(self.owner, other.owner)
    начальная_сумма = собственная.сумма + другая.сумма
    acc = Аккаунт (владелец, начальная_сумма)
    для t в списке (я) + список (другое):
        acc.add_transaction(t)
    возврат согласно
 

Да, это немного сложнее, чем другие реализации dunder. Это должно показать вам, что вы находитесь на месте водителя. Вы можете реализовать дополнение, как вам заблагорассудится.Если мы хотели игнорировать исторические транзакции — отлично, можно реализовать и так:

 по определению __add__(я, другой):
    владелец = сам.владелец + другой.владелец
    start_amount = self.balance + other.balance
    вернуть Аккаунт (владелец, start_amount)
 

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

Теперь у нас есть новый объединенный аккаунт с начальной суммой $110 (10 + 100) и балансом $240 (80 + 160):

 >>> акк3 = акк2 + акк
>>> согл3
Аккаунт('тим&боб', 110)

>>> согл3. количество
110
>>> согл3.баланс
240
>>> acc3._transactions
[20, 40, 20, -10, 50, -20, 30]
 

Обратите внимание, что это работает в обоих направлениях, потому что мы добавляем объекты одного и того же типа. В общем, если бы вы добавили свой объект во встроенную функцию ( int , str , …), метод встроенной функции __add__ ничего не знал бы о вашем объекте. В этом случае вам также необходимо реализовать метод обратного добавления ( __radd__ ). Вы можете увидеть пример для этого здесь.

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

__call__

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

. Учетная запись класса
:
    # ... (см. выше)

    защита __call__(сам):
        print('Начальная сумма: {}'.format(self.amount))
        print('Транзакции:')
        для транзакции в себе:
            печать (транзакция)
        print('\nБаланс: {}'. формат (самобаланс))
 

Теперь, когда я вызываю объект с синтаксисом acc() в двойных скобках, я получаю красивую выписку по счету с обзором всех транзакций и текущим балансом:

 >>> acc = Account('bob', 10)
>>> acc.add_transaction(20)
>>> acc.add_transaction(-10)
>>> acc.add_transaction(50)
>>> acc.add_transaction(-20)
>>> acc.add_transaction(30)

>>> акк()
Начальная сумма: 10
Транзакции:
20
-10
50
-20
30
Баланс: 80
 

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

Поэтому в большинстве случаев лучше добавить в класс явный метод. В этом случае, вероятно, было бы более прозрачно иметь отдельный метод Account.print_statement().

Поддержка Context Manager

и

с оператором : __enter__ , __exit__

Мой последний пример в этом руководстве касается несколько более продвинутой концепции Python: менеджеры контекста и добавление поддержки оператора with .

Итак, что такое «менеджер контекста» в Python? Вот краткий обзор:

Менеджер контекста — это простой «протокол» (или интерфейс), которому должен следовать ваш объект, чтобы его можно было использовать с оператором with . В основном все, что вам нужно сделать, это добавить методы __enter__ и __exit__ к объекту, если вы хотите, чтобы он функционировал как менеджер контекста.

Давайте воспользуемся поддержкой менеджера контекста, чтобы добавить механизм отката в наш класс Account .Если при добавлении очередной транзакции баланс становится отрицательным, мы откатываемся к предыдущему состоянию.

Мы можем использовать оператор Pythonic with , добавив еще два метода dunder. Я также добавляю несколько вызовов печати, чтобы сделать пример более понятным, когда мы его демонстрируем:

Учетная запись класса
:
    # ... (см. выше)

    защита __enter__(сам):
        print('ENTER WITH: Создание резервной копии транзакций для отката')
        self. _copy_transactions = список(self._transactions)
        вернуть себя

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('ВЫХОД С:', end=' ')
        если exc_type:
            себя._transactions = self._copy_transactions
            print('Откат к предыдущим транзакциям')
            print('Транзакция привела к {} ({})'.format(
                exc_type.__name__, exc_val))
        еще:
            print('Транзакция прошла успешно')
 

Поскольку для запуска отката необходимо вызвать исключение, я определяю быстрый вспомогательный метод для проверки транзакций в учетной записи:

 по определению validate_transaction(acc, amount_to_add):
    с акк как:
        print('Добавление {} в аккаунт'.формат (сумма_к_добавке))
        a.add_transaction(сумма_к_добавлению)
        print('Новый баланс будет следующим: {}'.format(a.balance))
        если а.баланс < 0:
            поднять ValueError('извините, не могу остаться в долгу!')
 

Теперь я могу использовать объект Account с оператором with . Когда я делаю транзакцию, чтобы добавить положительную сумму, все хорошо:

 acc4 = Счет('иск', 10)

print('\nНачало баланса: {}'.format(acc4.balance))
validate_transaction (acc4, 20)

print('\nКонец баланса: {}'.формат(acc4.баланс))
 

Выполнение приведенного выше фрагмента Python приводит к следующему выводу:

 Начало баланса: 10
ENTER WITH: Создание резервной копии транзакций для отката
Добавление 20 к счету
Новый баланс будет: 30
ВЫХОД С: Транзакция ОК
Конец баланса: 30
 

Однако, когда я пытаюсь снять слишком много денег, срабатывает код __exit__ и откатывает транзакцию:

 acc4 = Счет('иск', 10)

print('\nНачало баланса: {}'.format(acc4.balance))
пытаться:
    validate_transaction (acc4, -50)
кроме ValueError как exc:
    печать (отл.)

print('\nКонец баланса: {}'.формат(acc4.баланс))
 

В этом случае мы получаем другой результат:

 Начало баланса: 10
ENTER WITH: Создание резервной копии транзакций для отката
Добавление -50 к аккаунту
Новый баланс будет: -40
ВЫХОД С: Откат к предыдущим транзакциям
ValueError: извините, не могу остаться в долгу!
Конец баланса: 10
 

Заключение

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

Как и с любой другой функцией, пожалуйста, не злоупотребляйте ею. Например, перегрузка операторов может быть довольно неясной. Добавление «кармы» к объекту человека с помощью +bob или tim << 3 определенно возможно с помощью dunders, но может быть не самым очевидным или подходящим способом использования этих специальных методов. Однако для обычных операций, таких как сравнение и сложение, они могут быть элегантным подходом.

Демонстрация всех без исключения методов Дандера заняла бы очень много времени.Если вы хотите узнать больше о методах dunder и модели данных Python, я рекомендую вам ознакомиться со справочной документацией Python.

Кроме того, обязательно примите участие в нашем соревновании по кодированию метода Дандера, где вы сможете поэкспериментировать и применить свои новообретенные «навыки Дандера» на практике.

Python — имена специальных методов

Существует несколько специальных методов, необходимых для реализация класса. У каждого из них есть имя, которое начинается и заканчивается с двойным подчеркиванием.Эти имена методов используются неявно Питон. Раздел 3.3 языка Python Ссылка содержит полный список этих специальных методов. имена.

Мы подробно рассмотрим имена специальных методов в главе 24, Создание или расширение типов данных . А пока мы рассмотрим несколько специальных имена методов, которые используются интенсивно.

__в этом__

Метод __init__ класса является вызывается Python для инициализации вновь созданного объекта

__str__

Метод __str__ класса является вызывается всякий раз, когда Python печатает объект.Это метод, используемый встроенной функцией str .

__repr__

Метод __repr__ класса используется, когда мы хотим увидеть детали значений объекта. Этот метод используется функцией repr .

__cmp__

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

Инициализация объекта с помощью __инициализация__ . Когда вы создаете объект, Python создает объект а также вызвать метод объекта __init__ . Этот функция может создавать переменные экземпляра объекта и выполнять любые другая одноразовая инициализация. Обычно выделяют два вида переменные экземпляра, созданные методом __init__: переменные на основе параметров и переменных, которые не зависят ни от параметры.

Вот пример описания компании, которое может подойти для оценки эффективности акций.В этом примере все экземпляры переменные ( self.name , self.symbol , собственная цена ) основаны на параметрах __init__ метод.

Компания класса
 (объект):
    def __init__( self, name, symbol, stockPrice ):
        self. name= имя
        self.symbol= символ
        self.price= stockPrice
    def valueOf (я, акции):
        возврат акций * self.price 

Когда мы создаем экземпляр Company, мы используем такой код это.

 c1= Компания ("Дженерал Электрик", "Дженерал Электрик", 30.125) 

Это предоставит три значения параметрам __в этом__.

Строковое значение объекта с __str__ . Вызывается функция метода __str__ всякий раз, когда экземпляр класса необходимо преобразовать в строку. Как правило, это occus, когда мы используем стр функцию на объекте. Неявно, когда мы ссылаемся на объект в печать заявление, стр оценивается функция.Другой пример: wРассмотрите это определение класса Карточка .

Карта класса
 (объект):
    def __init__(я, ранг, костюм):
        self.rank = ранг
        self.suit = костюм
        self.points = ранг
    деф трудно(я):
        возврат селф. баллов
    деф мягкий(я):
        возврат селф.баллов
 

Когда мы пытаемся напечатать экземпляр класса, мы получаем что-то как следующее.

  >>>  
  c= Карта(3, "D") 
 
  >>>  
  печать c 
 
 
<__основной__.Объект карты по адресу 0x2e5f6c>
 
 

Это поведение по умолчанию для __str__ метод. Однако мы можем переопределить это с помощью функции, которая производит более полезный результат.

 по определению __str__(я):
        вернуть "%2d%s" % (self.rank, self.suit)
 

Добавление этой функции метода преобразует текущее значение кубика в строку и возвращает это. Теперь мы получаем нечто большее полезный.

  >>>  
  d= Карта(4, "D") 
 
  >>>  
  печать д 
 
 
 4D
 
 

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

  >>>  
  печать репр(с) 
 
 
<__main__.Объект карты по адресу 0x2f639c>
 
 

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

 определение __repr__(сам):
        return "Карта(%d,%r)" % (self.rank,self.suit)
 

Мы используем __repr__ для получения четкого определения как воссоздать данный объект.

  >>>  
  f= Карта(5,"D") 
 
  >>>  
  печать репр(ф) 
 
 
Карточка(5,'D')
 
 

Сортировка и сравнение с __cmp__ . Еще один полезный специальный метод: __cmp__ . Мы используем __cmp__ для предоставления результаты, используемые для сортировки и сравнения объектов. встроенный Функция cmp , как правило, использует этот метод. если ты не делайте других договоренностей, то этот метод также используется для < , <= , > , >= , == и != .

 по определению __cmp__(сам, другой):
        вернуть cmp(self.rank, other.rank) или cmp(self.suit, self.suit)
 

После добавления метода __cmp__ мы можно сравнить карты, чтобы проверить их относительный ранг.

  >>>  
  см (в, г) 
 
  -1 
  >>>  
  с < д 
 
  Правда 
  >>>  
  в >= д 
 
  Ложь 
 

Имена специальных атрибутов. В дополнение к именам специальных методов каждый объект имеет количество специальных атрибутов.Они описаны в разделе 2.3.10. справочника по библиотеке Python . Есть __dict__ , __class__ и __базы__ .

Переменные атрибутов экземпляра класса хранятся в специальном объект словаря с именем __dict__ . Как следствие, когда вы говорите self.attribute= value , это почти значение идентично self.__dict__['attribute']= значение .

В сочетании с операцией форматирования строки % эта функция удобно писать __str__ и __repr__ функций.

 по определению __str__(я):
        return "%(rank)2s%(suit)s" % self.__dict__
    защита __repr__(сам):
        return "Карта(%(ранг)r,%(масть)r)" % self.__dict__
 

Объектно-ориентированное программирование: Специальные методы в Python | by Jayashree domala

Руководство по специальным методам, используемым в ООП.

Фото Владимира Кудинова на Unsplash

Специальные методы позволяют использовать некоторые встроенные в Python операции с собственными созданными объектами.

Предположим, у нас есть список и мы хотим проверить длину списка, а также распечатать список, мы сделаем следующее:

 >>> list1  =  [11,22,33,44]>>> len(list1) 
4>>> print(list1)
[11, 22, 33, 44]

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

  >>> class  Test(): 
pass >>> my_test = Test()>>> len(my_test)
--------------- -------------------------------------------------- ----------
TypeError Traceback (последний последний вызов)
in
----> 1 len ( my_test ) TypeError : объект типа «Тест» не имеет len()

Мы получаем «TypeError», говорящий, что такой объект не имеет len().

 >>> print(my_test) 
<__main__.Test object at 0x00000296237884C8>

Обратите внимание, что когда мы печатаем список, он возвращает список. Но когда мы печатаем экземпляр класса «my_test», мы получаем, где находится объект в памяти.

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

Их также называют магическими методами или методами dunder, потому что они используют двойное подчеркивание.

«init» — это первый специальный метод, который вызывается автоматически.

Далее следует метод «str», который возвращает строковое представление и помогает при печати. Это так, потому что когда мы вызываем функцию print(), она запрашивает строковое представление объекта, который мы пытаемся напечатать. Итак, нам нужен метод для получения строкового представления.

Далее идет метод «len» для получения длины объекта.

  >>> class  Novel(): 
def __init__(self, title, author, pages):
self. title = title
self.автор = автор
self.pages = страниц def __str__(self):
return ("Роман {} написан {}".format(self.title, self.author)) def __len__(self):
return (self.pages)>>> my_novel = Роман("Привычки", "Чандра", 200)>>> print(my_novel)
Роман "Привычки" написан Чандрой >>> len(my_novel)
200

Теперь, если вы хотите удалить созданный вами объект.

  >>> del  my_novel>>> my_novel 
-------------------------------------------------- ------------------------------------------------------
NameError Traceback (последний вызов last)
in
----> 1 my_novel NameError : имя 'my_novel' не определено

Ошибка в том, что объект «my_novel» был удален.

Иногда вы хотите, чтобы при удалении объекта происходили определенные действия. Так что у нас есть метод «del» и для этого.

  >>> class  Novel(): 
def __init__(self, title, author, pages):
self.title = title
self.author = author =
self.pages

pages pages def __str__(self):
return ("Роман {} написан {}".format(self.title, self.author)) def __len__(self):
return ( себя.страницы) def __del__(self):
print("Новый объект был удален")>>> my_novel = Novel("Привычки", "Чандра", 200) >>> del my_novel
A новый объект был удален

Обратитесь к записной книжке здесь.

Книги начального уровня для изучения Python:

Книги продвинутого уровня для изучения Python:

Свяжитесь со мной: LinkedIn

Ознакомьтесь с моей другой работой: 8 GitH 8

Специальные методы типов расширений — Cython 3.

0.0a10 документация

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

Примечание

Все сказанное на этой странице относится только к типам расширений, определенным с оператором класса cdef . Это не относится к классам, определенным с помощью Оператор Python class , где применяются обычные правила Python.

Декларация

Специальные методы типов расширения должны быть объявлены с def , а не cdef . Это не влияет на их производительность — Python использует разные соглашения о вызовах для вызова этих специальных методов.

Строки документации

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

Методы инициализации:

__cinit__() и __init__()

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

Основное различие между ними заключается в том, когда они вызываются.Метод __cinit__() гарантированно будет вызываться как часть объекта выделение, но до того, как объект будет полностью инициализирован. В частности, методы и атрибуты объекта, которые принадлежат подклассам или которые были переопределены подклассы могут быть еще не инициализированы и не должны использоваться __cinit__() в базовом классе. Обратите внимание, что размещение объектов в Python очищает все поля и устанавливает их в ноль (или NULL ). Цитон дополнительно берет на себя ответственность установить для всех атрибутов объекта значение None , но опять же, это может быть еще не сделано для атрибутов, определенных или переопределенных подклассы.Если вашему объекту нужно что-то большее, чем этот базовый атрибут очистка для перехода в правильное и безопасное состояние, __cinit__() может быть хорошим местом, чтобы сделать это.

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

Метод __cinit__() - это то, где вы должны выполнить базовую безопасность C-уровня инициализация объекта, возможно, включая размещение любых данных C структуры, которыми будет владеть ваш объект. В отличие от __init__() , ваш метод __cinit__() гарантированно будет вызываться ровно один раз.

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

Любые аргументы, переданные конструктору, будут переданы как __cinit__() и __init__() .Если вы ожидаете подкласса вашего типа расширения, вы можете счесть полезным дать __cinit__() метод * и ** аргументы, чтобы он мог принимать и игнорировать произвольные дополнительные аргументы, так как аргументы, которые передаются через иерархия при распределении не может быть изменена подклассами. В качестве альтернативы, для удобства, если вы объявите свой метод __cinit__() не принимать никаких аргументов (кроме себя), он просто игнорирует любые дополнительные аргументы передается конструктору, не жалуясь на несоответствие подписи.

Примечание

Все аргументы конструктора будут переданы как объекты Python. Это означает, что непреобразуемые типы C, такие как указатели или объекты C++, нельзя передать в конструктор ни из Python, ни из кода Cython. Если это необходимо, используйте вместо этого фабричную функцию или метод, который обрабатывает инициализация объекта. Часто помогает прямой вызов метода __new__() в этой функции для явно обходить вызов конструктора __init__() .

Пример см. в разделе Создание экземпляров из существующих указателей C/C++.

Примечание

Реализация метода __cinit__() в настоящее время исключает тип из автоматическое травление.

Методы завершения:

__dealloc__() и __del__()

Аналогом метода __cinit__() является метод __dealloc__() метод, который должен выполнять обратный метод __cinit__() . Любой Данные C, которые вы явно выделили (например,грамм. через malloc) в вашем Метод __cinit__() должен быть освобожден в вашем методе __dealloc__() .

Вы должны быть осторожны с тем, что вы делаете в методе __dealloc__() . К тому времени, когда ваш Вызван метод __dealloc__() , возможно, объект уже частично уничтожен и может быть не в допустимом состоянии с точки зрения Python, поэтому вам следует избегать вызова каких-либо операций Python, которые могут касаться объекта. В частности, не вызывайте никаких других методов объекта и не делайте ничего, что может вызвать воскрешение объекта.Лучше всего, если вы будете придерживаться только освобождение данных C.

Вам не нужно беспокоиться об освобождении атрибутов Python вашего объекта, потому что Cython сделает это за вас после вашего метода __dealloc__() возвращается.

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

Python 3.4 сделал возможным безопасное определение типов расширений финализаторы для объектов. При запуске модуля Cython на Python 3.4 и выше вы можете добавить метод __del__() к типам расширений в для выполнения операций очистки Python. Когда __del__() называется, объект все еще находится в допустимом состоянии (в отличие от __dealloc__() ), что позволяет использовать операции Python на его членов класса. На Python <3.4 __del__() вызываться не будет.

Арифметические методы

Методы арифметических операторов, такие как __add__() , раньше вели себя иначе от их аналогов Python в Cython 0.x, следуя низкоуровневой семантике функций слота C-API. Начиная с Cython 3.0 они называются одинаково как в Python, включая отдельные «обратные» версии этих методов ( __radd__() и т.д.).

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

Если требуется обратная совместимость, обычный метод оператора ( __add__ и т. д.) все еще можно реализовать для поддержки обоих вариантов, применяя проверку типа к аргументы. Обратный метод ( __radd__ и т.д.) всегда можно реализовать с self в качестве первого аргумента и будет игнорироваться более старыми версиями Cython, тогда как Цитон 3.x и более поздние версии будут вызывать только обычный метод с ожидаемым порядком аргументов, а в противном случае вместо этого вызовите обратный метод.

Кроме того, старое поведение Cython 0.x (или собственный C-API) по-прежнему доступно с директива c_api_binop_methods=True .

Если вы не можете обработать комбинацию типов, которые вам дали, вы должны вернуться Не выполнено . Это позволит реализации оператора Python сначала попытаться применить обратный оператор ко второму операнду, а в противном случае также сообщить соответствующую ошибку пользователю.

Это изменение в поведении также относится к арифметическому методу на месте __ipow__() . Он не применяется ни к каким другим методам на месте ( __iadd__() и т. д.) которые всегда принимают self в качестве первого аргумента.

Богатые сравнения

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

  • Используйте 6 Python специальные методы __eq__() , __lt__() и т.д.Это поддерживается, начиная с Cython 0.27, и работает точно так же, как и обычные классы Python.

  • Использовать один специальный метод __richcmp__() . Это реализует все расширенные операции сравнения в одном методе. Подпись def __richcmp__(self, other, int op) . Целочисленный аргумент op указывает, какая операция должна быть выполнена. как показано в таблице ниже:

    <

    Py_LT

    ==

    Py_EQ

    >

    Py_GT

    <=

    Py_LE

    !=

    Py_NE

    >=

    Py_GE

    Эти константы можно импортировать из cpython .Модуль объекта .

  • Используйте декоратор @cython.total_ordering , который является низкоуровневым повторная реализация functools.total_ordering декоратор специально для классов cdef . (Обычные классы Python могут использовать оригинальный декоратор functools .)

     @cython.total_ordering
    класс cdef ExtGe:
        cdef целое число х
    
        def __ge__(я, другой):
            если не экземпляр (другой, ExtGe):
                вернуть нереализованный
            вернуть себя.x >= (другое).x
    
        def __eq__(я, другой):
            return isinstance(other, ExtGe) и self.x == (other).x
     

Метод

__next__()

Типы расширений, желающие реализовать интерфейс итератора, должны определять метод вызвал __next__() , а не следующий. Система Python автоматически укажите следующий метод, который вызывает ваш __next__() . Делайте НЕ явно дайте вашему типу метод next() , иначе могут произойти плохие вещи.

Таблица специального метода

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

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

Общий

https://docs.python.org/3/reference/datamodel.html#специальные имена методов

Имя

Параметры

Тип возврата

Описание

__cinit__

самостоятельный, …

Базовая инициализация (без прямого эквивалента Python)

__инициализация__

самостоятельный, …

Дальнейшая инициализация

__dealloc__

самостоятельно

Базовое освобождение (без прямого эквивалента Python)

__cmp__

х, у

внутр.

3-стороннее сравнение (только Python 2)

__str__

самостоятельно

объект

ул(сам)

__repr__

самостоятельно

объект

репр(сам)

__хэш__

самостоятельно

Py_hash_t

Хэш-функция (возвращает 32/64-битное целое число)

__вызов__

самостоятельный, …

объект

сам(…)

__iter__

самостоятельно

объект

Итератор возврата для последовательности

__getattr__

себя, имя

объект

Получить атрибут

__getattribute__

себя, имя

объект

Получить атрибут безусловно

__setattr__

сам, имя, значение

Установить атрибут

__делатр__

себя, имя

Удалить атрибут

Расширенные операторы сравнения

https://docs.python.org/3/reference/datamodel.html#basic-настройка

Вы можете либо реализовать стандартные специальные методы Python, например, __eq__() или единственный специальный метод __richcmp__() . В зависимости от приложения тот или иной способ может быть лучше.

Имя

Параметры

Тип возврата

Описание

__экв__

сам, г

объект

сам == у

__ne__

сам, г

объект

self != y (возврат к __eq__ , если недоступен)

__lt__

сам, г

объект

сам < у

__gt__

сам, г

объект

сам > у

__ле__

сам, г

объект

сам <= у

__ge__

сам, г

объект

сам >= г

__richcmp__

сам, г, междунар

объект

Объединенный расширенный метод сравнения для всего вышеперечисленного (без прямого эквивалента Python)

Арифметические операторы

https://docs.python.org/3/reference/datamodel.html#emulatory-numeric-types

Имя

Параметры

Тип возврата

Описание

__add__, __radd__

собственное, другое

объект

двоичный + оператор

__sub__, __rsub__

собственное, другое

объект

двоичный - оператор

__мул__, __рмул__

собственное, другое

объект

* оператор

__div__, __rdiv__

собственное, другое

объект

/ оператор отделения старого образца

__floordiv__, __rfloordiv__

собственное, другое

объект

// оператор

__truediv__, __rruediv__

собственное, другое

объект

/ оператор подразделения нового стиля

__mod__, __rmod__

собственное, другое

объект

% оператор

__divmod__, __rdivmod__

собственное, другое

объект

комбинированный div и mod

__pow__, __rpow__

собственный, другой, [мод.]

объект

** оператор или pow(x, y, [mod])

__отрицательный__

самостоятельно

объект

унарный - оператор

__pos__

самостоятельно

объект

унарный + оператор

__абс__

самостоятельно

объект

абсолютное значение

__ненулевой__

самостоятельно

внутр.

преобразовать в логическое значение

__инверсия__

самостоятельно

объект

~ оператор

__lshift__, __rlshift__

собственное, другое

объект

<< оператор

__rshift__, __rrshift__

собственное, другое

объект

>> оператор

__и__, __ранд__

собственное, другое

объект

и оператор

__или__, __рор__

собственное, другое

объект

| оператор

__xor__, __rxor__

собственное, другое

объект

^ оператор

Обратите внимание, что Cython 0.x не использовал варианты __r...__ и вместо этого использовали двунаправленную сигнатуру слота C для обычных методов, таким образом делая первый аргумент неоднозначный (не типизированный «самостоятельно»). Начиная с Cython 3.0 вызовы операторов передаются соответствующим специальным методам. См. выше раздел об арифметических методах.

Числовые преобразования

https://docs.python.org/3/reference/datamodel.html#emulatory-numeric-types

Имя

Параметры

Тип возврата

Описание

__int__

самостоятельно

объект

Преобразование в целое число

__длинный__

самостоятельно

объект

Преобразовать в длинное целое

__поплавок__

самостоятельно

объект

Преобразование в число с плавающей запятой

__октябрь__

самостоятельно

объект

Преобразование в восьмеричное число

__hex__

самостоятельно

объект

Преобразование в шестнадцатеричный формат

__index__

самостоятельно

объект

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

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

https://docs.python.org/3/reference/datamodel.html#emulatory-numeric-types

Имя

Параметры

Тип возврата

Описание

__iadd__

отдельно, x

объект

+= оператор

__isub__

отдельно, x

объект

-= оператор

__имул__

отдельно, x

объект

*= оператор

__idiv__

отдельно, x

объект

/= оператор для деления старого образца

__ifloordiv__

отдельно, x

объект

//= оператор

__itruediv__

отдельно, x

объект

/= оператор для подразделения нового стиля

__imod__

отдельно, x

объект

%= оператор

__ipow__

сам, у, я

объект

**= оператор

__ilshift__

отдельно, x

объект

<<= оператор

__irshift__

отдельно, x

объект

>>= оператор

__и__

отдельно, x

объект

&= оператор

__ior__

отдельно, x

объект

|= оператор

__ixor__

отдельно, x

объект

^= оператор

Последовательности и сопоставления

https://docs.python.org/3/reference/datamodel.html#emulatory-container-types

Имя

Параметры

Тип возврата

Описание

__len__

самостоятельно

Py_ssize_t

лен(сам)

__getitem__

отдельно, x

объект

сам[х]

__setitem__

сам, х, у

сам[х] = у

__delitem__

отдельно, x

собственный[x]

__getslice__

сам, Py_ssize_t i, Py_ssize_t j

объект

сам[i:j]

__setslice__

сам, Py_ssize_t i, Py_ssize_t j, x

сам[i:j] = х

__delslice__

сам, Py_ssize_t i, Py_ssize_t j

самостоятельный [i:j]

__содержит__

отдельно, x

внутр.

х внутри себя

Буферный интерфейс [

PEP 3118 ] (без эквивалентов Python — см. примечание 1)

Имя

Параметры

Тип возврата

Описание

__getbuffer__

self, Py_buffer *view , целые флаги

__releasebuffer__

сам, Py_buffer *просмотр

Интерфейс буфера [устаревший] (без эквивалентов Python — см. примечание 1)

Имя

Параметры

Тип возврата

Описание

__getreadbuffer__

self, Py_ssize_t i, void **p

__getwritebuffer__

self, Py_ssize_t i, void **p

__getsegcount__

сам, Py_ssize_t *p

__getcharbuffer__

self, Py_ssize_t i, char **p

Объекты-дескрипторы (см. примечание 2)

https://docs.python.org/3/reference/datamodel.html#emulatory-container-types

Имя

Параметры

Тип возврата

Описание

__get__

сам, экземпляр, класс

объект

Получить значение атрибута

__set__

сам, экземпляр, значение

Установить значение атрибута

__удалить__

сам, экземпляр

Удалить атрибут

Примечание

(1) Интерфейс буфера был предназначен для использования кодом C и напрямую не доступны из Python.Это описано в Справочном руководстве Python/C API. Python 2.x в разделах 6.6 и 10.6. На смену ему пришла новая PEP 3118 буферный протокол в Python 2.6 и больше не доступен в Python 3. Руководство по новому API см. в разделе Реализация протокола буфера.

Примечание

(2) Объекты-дескрипторы являются частью механизма поддержки нового стиля. Классы Python. См. обсуждение дескрипторов в документации Python. См. также PEP 252 , «Как сделать типы более похожими на классы» и PEP 253 , «Подтипирование встроенных типов».

Введение в классы Python

Введение в классы Python $$ \newcommand{\tp}{\thinspace .} $$

Эта глава взята из книги HP Langtangen A Primer on Scientific Programming with Python, 5-е издание, Springer, 2016.

Некоторые методы класса имеют имена, начинающиеся и заканчивающиеся двойной подчеркивать. Эти методы допускают специальный синтаксис в программе и называются специальными методами . Конструктор __init__ один пример.Этот метод вызывается автоматически, когда экземпляр создан (путем вызова класса как функции), но нам не нужно явно напишите __init__ . Другие специальные методы позволяют производить арифметические операции с экземплярами, сравнивать экземпляры с > , >= , != и т. д., чтобы вызывать экземпляры, как мы называем обычные функции и проверить, оценивается ли экземпляр как True или False , упомянуть некоторые возможности.

Вызов специального метода

Вычисление значения математической функции, представленной классом Y из раздела Представление функции в виде класса с y в качестве имени например, выполняется записью y.value(t) . Если бы мы могли написать просто y(t) экземпляр y будет выглядеть как обычная функция. Такой синтаксис действительно возможен и предлагается специальным методом, названным __вызов__ . Написание y(t) подразумевает вызов

  г.__вызов__(т)
  
если в классе Y определен метод __call__ . Мы можем легко добавить этот специальный метод:
  класс Y(объект):
    ...
    def __call__(я, т):
        вернуть self.v0*t - 0,5*self.g*t**2
  
Предыдущий метод значения теперь является избыточным. Хорошее программирование Соглашение состоит в том, чтобы включать метод __call__ во все классы, которые представлять математическую функцию. Экземпляры с методами __call__ называются 90 127 вызываемыми 90 128 объектами, точно так же, как простые функции также вызываемые объекты.Синтаксис вызова для вызываемых объектов: то же самое, независимо от того, является ли объект функцией или классом пример. Учитывая объект a ,
 , если можно вызвать (а):
  
проверяет, ведут ли себя и как вызываемые, т. е. если и это функция Python или экземпляр с __call__ метод.

В частности, экземпляр класса Y может быть передан как f аргумент функции diff из раздела Вызов: функции с параметрами:

  у = у(v0=5)
dydt = diff(y, 0.1)
  
Внутри diff мы можем проверить, что f не функция, а экземпляр класса Y . Однако мы используем только f в звонках, например f(x) , и для этого экземпляр с Метод __call__ работает как обычная функция. Эта функция очень удобна.

В следующем разделе демонстрируется изящное применение вызова оператор __call__ в численном алгоритме.

Пример: Автомагическое дифференцирование

Проблема

Учитывая реализацию Python f(x) математической функции \(f(x)\), мы хотим создать объект, который ведет себя как функция Python для вычисления производной \( f'(x) \).3\) (ну ответ только примерный, с ошибкой в ​​7-м десятичном знаке, но приближение можно легко улучшить).

Maple, Mathematica и многие другие программные пакеты могут точно символическая математика, включая дифференцирование и интегрирование. То Пакет Python sympy для символьной математики делает тривиальным вычисление точной производной от большой класс функций \(f(x)\) и превратить результат в обычная функция Python. Однако математические функции, определенные алгоритмическим способом (т.г., решение другого математического задача), либо функции с ветвями, случайными числами и т.п. фундаментальные проблемы символического дифференцирования, а затем числового требуется дифференциация. Поэтому мы основываем вычисление производные в экземплярах производных по конечной разности формулы. Также возможно использование точного символьного дифференцирования через SymPy.

Раствор

Самая простая (но не лучшая) формула для числовой производной: $$ \begin{уравнение} f'(x)\приблизительно {f(x+h)-f(x)\over h}\tp \тег{2} \end{уравнение} $$ Идея состоит в том, что мы создаем класс для хранения функции. дифференцированный, назовите его f и размер шага h , который будет использоваться в (2).Эти переменные можно установить в конструктор. Оператор __call__ вычисляет производную с помощью из (1). Все это может быть закодировано в несколько строк:

  класс Производный (объект):
    def __init__(я, f, h=1E-5):
        селф.f = f
        self.h = число с плавающей запятой (ч)

    защита __call__(я, х):
        f, h = self.f, self.h # делаем краткие формы
        возврат (f(x+h) - f(x))/h
  
Обратите внимание, что мы превращаем h в число с плавающей запятой , чтобы избежать потенциальное целочисленное деление.3\):

  >>> из математики импортировать sin, cos, pi
>>> df = производная (грех)
>>> х = пи
>>> дф(х)
-1.000000082740371
>>> cos(x) # точно
-1,0
>>> определение г(т):
... вернуть t**3
...
>>> dg = производная (g)
>>> т = 1
>>> dg(t) # сравнить с 3 (точно)
3.000000248221113
  
Выражения df(x) и dg(t) выглядят как обычные функции Python, которые вычислить производную функций sin(x) и g(t) .Класс Производная работает для (почти) любой функции \(f(x)\).

Проверка

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

  по определению test_Derivative():
    # Формула точна для линейных функций, независимо от h
    f = лямбда x: a*x + b
    а = 3,5; б = 8
    dfdx = Производная(f, h=0.5)
    разность = абс(dfdx(4.5) - а)
    утверждать diff < 1E-14, 'ошибка в классе Derivative, diff=%s' % diff
  
Здесь мы использовали лямбда-функцию для компактного определения функции ф . Альтернативой может быть определение f стандартным способом.
  по умолчанию f(x):
    вернуть а * х + б
  
Особенностью f является то, что он запоминает переменные a и b , когда f отправляется в класс Производный (это замыкание, см. раздел «Закрытие»).Обратите внимание, что приведенная выше тестовая функция следует соглашения для тестовых функций обведенный в разделе А круг.

Применение: метод Ньютона

В каких ситуациях будет удобно автоматически производить Функция Python df(x) , которая является производной от другого Python функция f(x) ? Один пример возникает при решении нелинейных алгебраических задач. уравнения \( f(x)=0 \) методом Ньютона и мы, из-за лени, из-за нехватки времени или отсутствия подготовки не удается вывести \(f'(x)\) по рука.Рассмотрим функцию Ньютон для решения \( f(x)=0 \): Ньютон(f, x, dfdx, эпсилон=1.0E-7, N=100) . Конкретная реализация находится в файле модуля Ньютон.py. Аргументы Функция Python f для \( f(x) \), float x для начального предположения (начальное значение) \( x \), функция Python dfdx для \( f'(x) \), число с плавающей запятой эпсилон для точности \( \эпсилон \) корня: алгоритмы повторяется до тех пор, пока \( |f(x)| int N для максимального допустимое количество итераций.3=0 \тонкое пространство . \end{уравнение*} $$ Функция \(f(x)\) изображена на рис. 2. На следующем занятии используется Производный класс для быстрого создания производного, чтобы мы могли вызывать Метод Ньютона:

  >>> из классов импорт производных
>>> из Ньютона импортировать Ньютон
>>> определение f(x):
... вернуть 100000*(x - 0,9)**2 * (x - 1,1)**3
...
>>> df = производная(f)
>>> Ньютон(f, 1.01, df, эпсилон=1E-5)
(1.09876100680, 8, -7.51396442571e-06)
  
Выходная тройка содержит приближение к корню, количество итераций, а значение \( f \) в приближенном корне (мера ошибка в уравнении).{-3} \).) Используя точное производная дает почти тот же результат:

  >>> определение df_exact(x):
... вернуть 100000*(2*(x-0.9)*(x-1.1)**3 + \
... (х-0,9)**2*3*(х-1,1)**2)
...
>>> Ньютон(f, 1.01, df_exact, эпсилон=1E-5)
(1.0987610065618421, 8, -7.513968

99629e-06)

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

Решение, использующее SymPy

класс Производный основан на численном дифференцировании, но он можно создать такой же короткий класс, который может выполнять точную дифференциацию. В SymPy можно выполнить символьную дифференциацию выражения e относительно символической независимой переменной x на разл(е, х) .Предполагая, что функция пользователя f может быть оценена для символическая независимая переменная x , мы можем вызвать f(x) , чтобы получить выражение SymPy для формулы в f , а затем используйте diff для вычисления точной производной. После этого поворачиваем символическое выражение производной в обычном Функция Python (через lambdify ) и определите эту функцию как метод __call__ . Правильный код Python очень короткий:

  класс Derivative_sympy(объект):
    защита __init__(я, f):
        из sympy import Symbol, diff, lambdify
        х = Символ ('х')
        sympy_f = f(x) # сделать выражение sympy
        sympy_dfdx = diff(sympy_f, x)
        себя.__call__ = lambdify([x], sympy_dfdx)
  
Обратите внимание, как метод __call__ определяется путем назначения функции к нему (хотя функция, возвращаемая lambdify , является функцией только x , он работает для вызова obj(x) для экземпляра obj из введите Derivative_sympy ).

И демонстрация класса, и проверка реализации можно поместить в тестовую функцию:

  определение test_Derivative_sympy():
    определение g(t):
        вернуть т ** 3

    dg = Derivative_sympy(g)
    т = 2
    точно = 3*t**2
    вычислено = dg(t)
    тол = 1E-14
    утверждать абс (точное - вычисленное) < tol

    защита ч(у):
        возврат exp(-y)*sin(2*y)

    из sympy import exp, sin
    dh = Derivative_sympy(h)
    из математики импортировать пи, exp, sin, cos
    у = пи
    точно = -exp(-y)*sin(2*y) + exp(-y)*2*cos(2*y)
    вычислено = dh(y)
    утверждать абс (точное - вычисленное) < tol
  

Пример с g(t) должен быть простым для понимания.В конструкторе класса Derivative_sympy мы вызываем g(x) с символ x и g возвращают выражение SymPy x**3 . Затем метод __call__ становится функцией lambda x: 3*x**2 .

Однако функция h(y) заслуживает более подробного объяснения. Когда тогда конструктор класса Derivative_sympy делает вызов h(x) , с символ x , функция h вернет выражение SymPy exp(-x)*sin(2*x) , если exp и sin являются функциями SymPy.Поскольку мы делаем из sympy import exp, грех перед вызовом конструктор в классе Derivative_sympy , имена exp и sin определены в тестовой функции, и наша локальная функция h будет иметь доступ ко всем локальным переменным, так как это закрытие, как уже упоминалось выше и в разделе Закрытие. Это означает, что h имеет доступ к sympy.sin и sympy.cos . когда конструктор в классе Derivative_sympy вызывает h .После этого мы хотим провести некоторые численные вычисления и нам нужно exp , sin и cos из модуля math . Если бы мы попытались сделать Derivative_sympy(h) после импорта из math , h затем вызовет math.exp и math.sin с SymPy символ в качестве аргумента и вызовет TypeError , поскольку math.exp ожидает float , а не объект Symbol от SymPy.

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

Пример: автоматическая интеграция

Мы можем применить идеи из раздела «Пример: автомагическое дифференцирование» к сделать класс для вычисления интеграла функции численно.{n-1} f(a+ih) + \frac{1}{2}f(x)\right),\ \тег{3} \end{уравнение} $$ где \( h=(x-a)/n \). В приложении программе, мы хотим вычислить \( F(x;a) \) с помощью простого синтаксиса, такого как

  по умолчанию f(x):
    вернуть exp(-x**2)*sin(10*x)

а = 0; п = 200
F = Интеграл (f, a, n)
напечатать F(x)
  
Здесь f(x) — это интегрируемая функция Python, а F(x) ведет себя как функция Python, которая вычисляет значения \( F(x;a) \).

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

Рассмотрим простую реализацию правила трапеций. в функции Python:

  трапециевидный (f, a, x, n):
    ч = (х-а)/с плавающей запятой (п)
    я = 0.5*ф(а)
    для i в диапазоне (1, n):
        я += е (а + я * ч)
    I += 0,5*f(x)
    я *= ч
    вернуться я
  

Класс Интеграл должен иметь некоторые атрибуты данных и __call__ метод. Поскольку последний метод должен принимать в качестве аргумента x , другие параметры a , f и n должны быть атрибутами данных. То тогда реализация становится

  класс Интеграл(объект):
    def __init__(я, ж, а, n=100):
        себя.f, self.a, self.n = f, a, n

    защита __call__(я, х):
        вернуть трапецию (self.f, self.a, x, self.n)
  
Обратите внимание, что мы просто повторно используем трапециевидную функцию для выполнения расчет. В качестве альтернативы мы могли бы скопировать тело трапециевидную функцию в метод __call__ . Однако, если у нас уже есть этот алгоритм, реализованный и протестированный как функцию, лучше вызвать функцию. Затем класс известен как оболочка базовой функции.{2\pi}\sin x\, dx \) может выглядеть следующим образом:

  из математического импорта sin, pi

G = интеграл (sin, 0, 200)
значение = G(2*пи)
  
Эквивалентный расчет
  значение = трапециевидное (sin, 0, 2*pi, 200)
  

Проверка с помощью символьных вычислений

Мы всегда должны предоставлять тестовую функцию для проверки реализация. Чтобы избежать неизвестных ошибок аппроксимации правила трапеций, воспользуемся тем очевидным фактом, что линейные функции интегрируются точно по правилу.Хотя на самом деле легко выберите линейную функцию, проинтегрируйте ее и выясните, что такое интеграл мы также можем продемонстрировать, как автоматизировать такой процесс, SymPy. По существу, мы определяем выражение в SymPy, попросите SymPy интегрировать его, а затем включите результирующий символьный интеграл для простой функции Python для вычисления:

  >>> импортировать sympy как sp
>>> x = sp.Symbol('x')
>>> f_expr = sp.cos(x) + 5*x
>>> f_expr
5*х + кос(х)
>>> F_expr = sp.интегрировать (f_expr, x)
>>> F_выражение
5*х**2/2 + грех(х)
>>> F = sp.lambdify([x], F_expr) # преобразовать f_expr в F(x) func.
>>> Ф(0)
0,0
>>> Ф(1)
3.3414709848078967
  
Используя такую ​​функциональность для точной интеграции, мы можем написать наша тестовая функция как
  по определению test_Integral():
    # Правило трапеций точно для линейных функций
    импортировать sympy как sp
    х = sp.Symbol('x')
    f_выражение = 2*х + 5
    # Превратить выражение sympy в обычную функцию Python f(x)
    ф = сп.lambdify([x], f_expr)
    # Находим интеграл от f_expr и превращаем в обычную функцию Python F
    F_expr = sp.integrate (f_expr, x)
    F = sp.lambdify([x], F_expr)

    а = 2
    х = 6
    точный = F(x) - F(a)
    вычислено = Интеграл (f, a, n = 4)
    diff = abs (точное - вычисленное)
    тол = 1E-15
    утверждать diff < tol, 'ошибка в классе Integral, diff=%s' % diff
  
Если вы считаете, что использование SymPy для интеграции линейных функции, вы с тем же успехом можете сделать это сами и определить f = лямбда x: 2*x + 5 и F = лямбда x: x**2 + 5*x .

Примечание

Класс Интеграл неэффективен (но, вероятно, более чем достаточно быстро) для построения графика \(F(x;a)\) как функции \(x\). Упражнение 22: Ускорение повторных интегральных вычислений предлагает оптимизировать класс для этой цели.

Преобразование экземпляра в строку

Еще один полезный специальный метод — __str__ . Это называется, когда экземпляр класса необходимо преобразовать в строку. Это происходит, когда мы говорим, что печатает , а является экземпляром.Затем Python изучит экземпляр a . для метода __str__ , который должен возвращать строку. Если такой специальный метод найден, выводится возвращаемая строка, в противном случае печатается только имя класса. Пример будет иллюстрировать точка. Сначала мы пытаемся напечатать экземпляр класса и Y из раздела Представление функции в виде класса (где нет метода __str__ ):

  >>> напечатать у
<__основной__.Экземпляр Y по адресу 0xb751238c>
  
Это означает, что y является экземпляром y в __main__ . модуль (основная программа или интерактивный сеанс). Выход также содержит адрес, указывающий, где хранится экземпляр и в памяти компьютера.

Если мы хотим, чтобы print y распечатывал экземпляр y , нам нужно чтобы определить метод __str__ в классе Y :

  класс Y(объект):
    ...
    защита __str__(я):
        вернуть 'v0*t - 0,5*g*t**2; v0=%g' % self.v0
  
Как правило, __str__ заменяет нашу предыдущую формулу . и __call__ заменяет наш предыдущий метод value . Программисты Python с опытом, который мы сейчас приобрели, поэтому пишите класс Y только со специальными методами:
  класс Y(объект):
    защита __init__(я, v0):
        сам.v0 = v0
        селф.г = 9,81

    def __call__(я, т):
        вернуть себя.v0*t - 0,5*сам.г*t**2

    защита __str__(я):
        вернуть 'v0*t - 0,5*g*t**2; v0=%g' % self.v0
  
Давайте посмотрим на класс в действии:
  >>> у = Y(1,5)
>>> у(0,2)
0,1038
>>> напечатать у
v0*t - 0,5*g*t**2; v0=1,5
  
Что мы получили, используя специальные методы? Ну, мы можем еще только оцените формулу и запишите ее, но многие пользователи класса утверждают, что синтаксис более привлекателен, так как y(t) в коде означает \(y(t)\) в математике, и мы можем сделать print y , чтобы просмотреть формулу.Суть использования специальных методов заключается в достижении более удобный синтаксис. Следующие разделы иллюстрируют этот момент далее.

Обратите внимание, что метод __str__ вызывается всякий раз, когда мы делаем str(a) , и print a фактически print str(a) , то есть print a.__str__() .

Пример: телефонная книга со специальными методами

Пересмотрим класс Лицо из раздела Телефонная книга. Метод dump в этом классе лучше реализован как специальный метод __str__ .Это легко: мы просто меняем имя метода и заменить print s на return s .

Хранение экземпляров Person в словаре для формирования телефонной книги простой. Тем не менее, мы делаем словарь немного проще в использовании. если мы обернем вокруг него класс. То есть делаем класс PhoneBook который содержит словарь в качестве атрибута. Ан Метод добавить можно использовать для добавления нового человека:

  класс Телефонная книга (объект):
    защита __init__(сам):
        себя.contact = {} # dict экземпляров Person

    def add(self, name, mobile=None, office=None,
            частное = нет, электронная почта = нет):
        p = Человек (имя, мобильный, рабочий, личный, электронная почта)
        self.contacts[имя] = p
  
__str__ может распечатать телефонную книгу в алфавитном порядке:
  по определению __str__(я):
        с = ''
        для p в отсортированных (self.contacts):
            s += str(self.contacts[p]) + '\n'
        вернуть с
  
Чтобы получить экземпляр Person , мы используем __call__ . с именем человека в качестве аргумента:
  def __call__(я, имя):
        вернуть себя.контакты[имя]
  
Единственным преимуществом этого метода является более простой синтаксис: для телефонной книги b мы можем получить данные о NN по телефону b('NN') вместо доступа к внутреннему словарю b.contacts['NN'] .

Мы можем сделать простой демонстрационный код для телефонной книги с тремя имена:

  б = телефонная книга()
b.add('Оле Олсен', office='767828292',
      электронная почта = '[email protected]')
b.add('Ханс Хэнсон',
      офис='767828283', мобильный='9221')
б.добавить('На человека', мобильный='
9781')
напечатать b('На человека')
печатать б
  
Результат становится
  На человека
мобильный телефон: 
9781

Ганс Хэнсон
мобильный телефон: 9221
рабочий телефон: 767828283

Оле Олсен
рабочий телефон: 767828292
адрес электронной почты: [email protected]

На человека
мобильный телефон: 
9781
  
Вам настоятельно рекомендуется поработать с этой последней демонстрационной программой. вручную и имитировать то, что делает программа. То есть прыгать в код и запишите на листе бумаги, какие различные переменные содержать после каждого утверждения.Это важное и хорошее упражнение! Вы получаете удовольствие от мастер-классов, если получаете такой же результат как указано выше. Полная программа с классами Человек и PhoneBook и тест выше находится в файле Телефонная книга.py. Вы можете запускать эту программу, оператор за оператором, либо в онлайн-репетиторстве по Python, либо в отладчике (см. документ Отладка в Python [3]) для контроля правильности вашего понимания хода выполнения программы.

Примечание

Обратите внимание, что имена отсортированы по именам.Причина в том, что строки сортируются после первого символа, затем второй символ и так далее. Мы можем поставить наш собственный индивидуальный сорт функция. Одна из возможностей — разделить имя в слова и использовать последнее слово для сортировки:

  def last_name_sort(name1, name2):
    фамилия1 = имя1.split()[-1]
    фамилия2 = имя2.split()[-1]
    если фамилия1 < фамилия2:
        возврат -1
    Элиф Фамилия1 > Фамилия2:
        вернуть 1
    еще: # равенство
        вернуть 0

для p в отсортированном (self.контакты, last_name_sort):
   ...
  

Добавление объектов

Пусть a и b будут экземплярами некоторого класса C . Имеет ли это имеет смысл писать a + b ? Да, это имеет смысл, если класс C определил специальный метод __add__ :

  класс С(объект):
    ...
    __добавить__(я, другой):
        ...
  
Метод __add__ должен добавить экземпляры self и другие и вернуть результат как экземпляр.Поэтому, когда Python встречает a + b , он проверяет, является ли класс C имеет метод __add__ и интерпретирует a + b как вызов a.__add__(b) . Следующий пример, надеюсь, прояснит, для чего можно использовать эту идею.

Пример: Класс полиномов

Давайте создадим класс Многочлен для многочленов. Коэффициенты в многочлене могут быть заданы как конструктор в виде списка.i \) в многочлене.3 \ тонкое пространство . \end{уравнение*} $$ Можно добавлять многочлены (просто добавляя коэффициенты, соответствующие тем же силам) так что наш класс может иметь метод __add__ . Метод __call__ является естественным для оценки многочлена, задано значение \( x \). Класс указан ниже и объяснен после него.

Реализация

  класс Полиномиальный (объект):
    def __init__(я, коэффициенты):
        self.coeff = коэффициенты

    защита __call__(я, х):
        """Оцените многочлен."""
        с = 0
        для i в диапазоне (len (self.coeff)):
            s += self.coeff[i]*x**i
        вернуть с

    def __add__(я, другой):
        """Вернуть себя + другое как полиномиальный объект."""
        # Два случая:
        #
        # сам: X X X X X X X
        # другое: X X X
        #
        # или:
        #
        # сам: Х Х Х Х Х
        # другое: X X X X X X X X

        # Начните с самого длинного списка и добавьте другой
        если len(self.coeff) > len(other.coeff):
            результат_коэфф = я.i \) for \( i=0 \) на количество коэффициентов в списке.

 

Метод __add__ выглядит более продвинутым. Цель состоит в том, чтобы добавить два списки коэффициентов. Однако может случиться так, что списки неравная длина. Поэтому мы начинаем с самого длинного списка и добавляем другой список, элемент за элементом. Обратите внимание, что result_coeff начинается как копия из self.coeff : если нет, изменения в result_coeff при вычислении сумма будет отражена в сам.коэфф . Это означает, что self будет суммой самого себя и экземпляр другого экземпляра , или, другими словами, добавление двух экземпляров, p1+p2 , изменяет p1 - это не то, что нам нужно! Альтернатива Реализация класса Многочлен находится в Упражнении 24: Найдите ошибку в классе многочленов.

Метод вычитания __sub__ может быть реализован в соответствии со строками __add__ , но немного сложнее и оставлено как Упражнение 25.{i-1} \thinspace .\end{уравнение*} $$ Если \( c_i \) хранится как список c , список производных, скажем, его имя dc , выполняет dc[i-1] = i*c[i] for i от 1 до самый большой индекс c . Обратите внимание, что dc имеет на один элемент меньше чем c .

Существуют два различных способа реализации дифференциации функциональность, либо путем изменения полиномиальных коэффициентов, либо путем возвращая новый экземпляр Polynomial из метода таким образом, что исходный экземпляр полинома не поврежден.Мы позволяем p. Differentiate () быть реализация первого подхода, т. е. этот метод не вернуть что угодно, но коэффициенты Полином instance p изменены. Другой подход реализован с помощью p.derivative() , который возвращает новый объект Polynomial с коэффициентами соответствующий производной p .

Полная реализация двух методов приведен ниже:

  класс Полиномиальный(объект):
    ...
    деф дифференцировать (я):
        """Продифференцируйте этот полином на месте."""
        для i в диапазоне (1, len (self.coeff)):
            self.coeff[i-1] = i*self.coeff[i]
        дель self.coeff[-1]

    производная по определению (я):
        """Скопируйте этот полином и верните его производную."""
        dpdx = Polynomial(self.coeff[:]) # делаем копию
        dpdx.дифференцировать()
        вернуть дпдкс
  
Класс Polynomial с методом дифференцировать , а не методом Производный метод будет изменяемым (т.е., содержимое объекта может изменить) и разрешить изменение данных на месте, в то время как полином класс с производным , а не дифференцировать дал бы неизменяемый объект, где полином инициализируется в конструкторе никогда не изменяется. (технически можно ухватить коэфф переменная в экземпляре класса и изменить этот список.5 \thinspace .\end{уравнение*} $$

  >>> p1 = многочлен ([1, -1])
>>> p2 = многочлен ([0, 1, 0, 0, -6, -1])
>>> р3 = р1 + р2
>>> вывести p3.coeff
[1, 0, 0, 0, -6, -1]
>>> р4 = р1*р2
>>> вывести p4.coeff
[0, 1, -1, 0, -6, 5, 1]
>>> p5 = p2.производная()
>>> вывести p5.coeff
[1, 0, 0, -24, -5]
  
Одной из проверок реализации может быть сравнение p3 at (например) \( x=1/2 \) с \( p_1(x) + p_2(x) \):
  >>> х = 0.5
>>> p1_plus_p2_value = p1(x) + p2(x)
>>> p3_value = p3(x)
>>> напечатать p1_plus_p2_value - p3_value
0,0
  
Обратите внимание, что p1 + p2 сильно отличается от p1(x) + p2(x) . в В первом случае мы добавляем два экземпляра класса Polynomial , а в В последнем случае мы добавляем два экземпляра класса с плавающей запятой (начиная с p1(x) и p2(x) подразумевают вызов __call__ , и этот метод возвращает число с плавающей запятой объект).1 '
на 'х ' ; нулевая мощность должна быть удалена и заменена на 1 , начальные пробелы должны быть исправлены и т. д. Эти настройки могут быть реализованы с помощью заменить метод в строковых объектах и ​​путем составления фрагментов струны. Новая версия метода __str__ ниже содержит необходимые корректировки. Если вы обнаружите этот тип манипуляций со строками хитрый и сложный для понимания, вы можете смело пропустить дальше проверка улучшенного кода __str__ , поскольку подробности не необходимо для вашего настоящего изучения концепции класса и специальные методы.1 ', 'х') if s[0:3] == ' + ': # удалить начальный + с = с[3:] if s[0:3] == '-': # исправить пробелы для начального - с = '-' + с [3:] вернуть с
Программирование иногда превращается в кодирование (как думают) последовало общее решение рядом частных случаев, чтобы исправить оговорки в «общем» решении, точно так же, как мы испытали с методом __str__ выше. Эта ситуация часто требует дополнительных будущих исправлений и часто признак неоптимального решения задачи программирования.4

Проверка реализации

Всегда полезно включать тестовую функцию test_Polynomial() для проверки функциональности в классе Полином . Для этого построим несколько примеров сложения, умножения, и дифференцировать полиномы вручную и делать тесты этого класса Полином воспроизводит правильные результаты. Тестирование __str__ Метод оставлен как Упражнение 26: Проверка функциональности красивого вывода полиномов.

Ошибки округления могут быть проблемой в классе Полином : __add__ , производная и дифференцировать приведут к целым коэффициентам, если добавляемые полиномы имеют целые коэффициенты, а __mul__ всегда приводит к многочлену с коэффициентами, хранящимися в Массив numpy с элементами с плавающей запятой . Целочисленные коэффициенты в списках могут сравнивать с использованием == для списков, а коэффициенты в массивах numpy следует сравнивать с допуском.Можно либо вычесть numpy массивы и используйте метод max , чтобы найти наибольшее отклонение и сравните это с допуском, или можно использовать numpy.allclose(a, b, rtol=tol) для сравнения массивов a и b с (относительно) допуск до .

В качестве контрольных примеров выберем многочлены с целыми коэффициентами. так что __add__ , производное и дифференцировать может быть проверено проверкой равенства ( == ) списков coeff .Умножение в __mul__ должно использовать numpy.allclose .

Мы следуем соглашению, что все тесты находятся в форме утверждает успех , где успех является логическим выражением для теста. (Реальная версия тестовой функции в файл Polynomial.py добавляет в тест сообщение об ошибке msg : утверждает успех, msg .) Другая часть соглашения что функция начинается с test_ и функция не принимает аргументы.

Теперь наша тестовая функция становится

  по определению test_Polynomial():
    p1 = многочлен ([1, -1])
    p2 = многочлен ([0, 1, 0, 0, -6, -1])

    р3 = р1 + р2
    p3_exact = многочлен ([1, 0, 0, 0, -6, -1])
    утверждать p3.coeff == p3_exact.coeff

    р4 = р1*р2
    p4_exact = Многочлен (numpy.array ([0, 1, -1, 0, -6, 5, 1]))
    утверждать numpy.allclose (p4.coeff, p4_exact.coeff, rtol = 1E-14)

    p5 = p2.производная()
    p5_exact = многочлен ([1, 0, 0, -24, -5])
    утверждать п5.коэфф == p5_exact.coeff

    p6 = многочлен ([0, 1, 0, 0, -6, -1]) # p2
    p6.дифференцировать()
    p6_exact = p5_exact
    утверждать p6.coeff == p6_exact.coeff
  

Арифметические операции и другие специальные методы

Учитывая два экземпляра a и b , стандартная двоичная арифметика операции с a и b определяются следующим специальные методы:

  • a + b : a.__add__(b)
  • а-б : а.__sub__(б)
  • а*б : а.__мул__(б)
  • a/b : a.__div__(b)
  • a**b : a.__pow__(b)
Некоторые другие специальные методы также часто полезны:
  • длина a , len(a) : a.__len__()
  • абсолютное значение a , abs(a) : a.__abs__()
  • а == б : а.__экв__(б)
  • а>б : а.__gt__(b)
  • а >= б : а.__ge__(б)
  • a < b : a.__lt__(b)
  • a <= b : a.__le__(b)
  • а != б : а.__ne__(б)
  • : а.__отрицательный__()
  • вычисление a как логическое выражение (как в тесте if a: ) подразумевает вызов специального метода a.__bool__() , который должен возвращать True или False - если __bool__ не определен, __len__ вызывается, чтобы узнать, равна ли длина нулю ( False ) или нет ( 9008 )
Мы можем реализовать такие методы в классе Polynomial , см. Упражнение 25: Реализация вычитания полиномов. Раздел Пример: Класс для векторов на плоскости содержит примеры реализации перечисленных выше специальных методов.

Специальные методы преобразования строк

Посмотрите на этот класс с помощью метода __str__ :

  >>> класс MyClass(объект):
... def __init__(я):
... само.данные = 2
... защита __str__(я):
... вернуть 'In __str__: %s' % str(self.data)
...
>>> а = МойКласс()
>>> распечатать
В __str__: 2
  
Надеюсь, вы хорошо понимаете, почему мы получаем этот вывод (если нет, вернитесь к раздел Превращение экземпляра в строку).

Но что произойдет если мы напишем просто в командной строке в интерактивной оболочке?

  >>> а
Экземпляр <__main__.MyClass по адресу 0xb75125ac>
  
При написании всего в интерактивном сеансе Python ищет специальный метод __repr__ в a .Этот метод аналогичен __str__ тем, что превращает экземпляр в строку, но есть соглашение, что __str__ - это красивая печать содержимого экземпляра, в то время как __repr__ является полным представление содержимого экземпляра. Для многих Классы Python, в том числе int , float , complex , список , кортеж и dict , __repr__ и __str__ дают идентичный результат.В нашем классе MyClass __repr__ отсутствует, и нам нужно добавить его, если мы хотим
  >>> а
  
чтобы написать содержимое, например , напечатайте .

Учитывая экземпляр a , str(a) подразумевает вызов a.__str__() и repr(a) подразумевает вызов a.__repr__() . Это означает, что

  >>> а
  
на самом деле является вызовом repr(a) и
  >>> распечатать
  
на самом деле является оператором print str(a) .

Простое средство в классе MyClass состоит в том, чтобы определить

  деф __repr__(сам):
    return self.__str__() # или вернуть str(self)
  
Однако, как мы объясним ниже, __repr__ лучше всего определяется по-другому.

Воссоздание объектов из строк

Функция Python eval(e) оценивает допустимое выражение Python. содержится в строке и . По соглашению __repr__ возвращает строку, такую ​​что eval , применяемый к строке, воссоздает экземпляр.Например, в случае класса Y из раздела Представление функции в виде класса, __repr__ должен вернуть 'Y(10)' , если переменная v0 имеет значение 10 . Тогда eval('Y(10)') будет таким же, как если бы мы закодировали Y(10) непосредственно в программе или в интерактивном сеансе.

Ниже мы показываем примеры методов __repr__ в классах Y (раздел Представление функции в виде класса), Многочлен (пример раздела: Класс для многочленов), и MyClass (выше):

  класс Y(объект):
    ...
    защита __repr__(сам):
        вернуть 'Y(v0=%s)' % self.v0

полиномиальный класс (объект):
    ...
    защита __repr__(сам):
        return 'Полином (коэффициенты =% s)' % self.coeff

класс MyClass (объект):
    ...
    защита __repr__(сам):
        вернуть 'МойКласс()'
  
С этими определениями eval(repr(x)) воссоздает объект x если это один из трех типов выше. В частности, мы можем записать в файл x , а затем воссоздать его. x из информации о файле:
  # somefile - это некоторый файловый объект
какой-то файл.написать (представить (х))
какой-то файл.close()
...
данные = какой-то файл.readline()
x2 = eval(data) # воссоздать объект
  
Теперь x2 будет равно x ( x2 == x оценивается как True ).

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

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

Рэйф Кеттлер

Copyright © 2012 Rafe Kettler

Версия 1.17

PDF-версию этого руководства можно загрузить с моего сайта или Github.Руководство по магическим методам имеет репозиторий git по адресу http://www.github.com/RafeKettler/magicmethods. О любых проблемах можно сообщить там, вместе с комментариями (или даже вкладами!).

Содержание

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

Это руководство является кульминацией нескольких месяцев публикаций в блоге.Тема магических методов .

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

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

Надеюсь, вам понравится.Используйте его в качестве учебника, переподготовки или справочника; он просто задуман как удобное руководство по магическим методам Python.

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

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

Собрав все вместе, вот пример __init__ и __del__ в действии:

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

класс ФайлОбъект:
    '''Оболочка файловых объектов, обеспечивающая закрытие файла при удалении'''.

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

    защита __del__(я):
        self.file.close()
        файл self.file  

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

.
 , если instance.equals(other_instance):
    # сделай что-нибудь  

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

 , если экземпляр == другой_экземпляр:
    #сделай что-нибудь  

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

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

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

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

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

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

    def __new__(cls, слово):
        # Обратите внимание, что мы должны использовать __new__. Это потому, что str является неизменяемым
        # тип, поэтому мы должны инициализировать его заранее (при создании)
        если '' в слове:
            print "Значение содержит пробелы. Усекается до первого пробела."
            word = word[:word.index(' ')] # Word теперь состоит из всех символов до первого пробела
        обратная ул.__new__(cls, слово)

    def __gt__(я, другой):
        вернуть len(я) > len(другой)
    def __lt__(я, другой):
        вернуть len(себя) < len(другой)
    def __ge__(я, другой):
        вернуть len(я) >= len(другой)
    def __le__(я, другой):
        вернуть len(себя) <= len(другой)  

Теперь мы можем создать два Word s (используя Word('foo') и Word('bar') ) и сравнить их по длине. Однако обратите внимание, что мы не определяли __eq__ и __ne__ .Это связано с тем, что это приведет к некоторому странному поведению (в частности, Word('foo') == Word('bar') будет оцениваться как true). Не имеет смысла проверять равенство на основе длины, поэтому мы прибегаем к реализации равенства str .

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

Числовые магические методы

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

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

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

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

Теперь мы рассмотрим типичные бинарные операторы (и одну или две функции): +, -, * и тому подобное.Они, по большей части, довольно очевидны.

__add__(я, другой)
Реализует дополнение.
__sub__(сам, другой)
Реализует вычитание.
__mul__(сам, другой)
Реализует умножение.
__floordiv__(сам, другой)
Реализует целочисленное деление с использованием оператора // .
__div__(сам, другой)
Реализует деление с помощью оператора /.
__truediv__(сам, другой)
Реализует истинное деление. Обратите внимание, что это работает только тогда, когда действует из __future__ import Division .
__mod__(сам, другой)
Реализует по модулю с использованием оператора % .
__divmod__(сам, другой)
Реализует поведение для длинного деления с помощью встроенной функции divmod() .
__pow__
Реализует поведение для показателей с использованием оператора ** .
__lshift__(сам, другой)
Реализует побитовый сдвиг влево с помощью оператора << .
__rshift__(сам, другой)
Реализует побитовый сдвиг вправо с помощью оператора >> .
__и__(я, другой)
Реализует побитово и с помощью оператора и .
__или__(сам, другой)
Реализует побитовое или с помощью | оператор. оператор.
Отраженные арифметические операторы

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

  некоторый_объект + другой  

Это было "обычное" дополнение. Отраженный эквивалент — то же самое, за исключением того, что операнды поменялись местами:

  другое + некоторый_объект  

Таким образом, все эти магические методы делают то же самое, что и их обычные эквиваленты, за исключением того, что они выполняют операцию с другим в качестве первого операнда и с самим собой в качестве второго, а не наоборот.В большинстве случаев результат отраженной операции такой же, как и ее нормальный эквивалент, поэтому вы можете просто определить __radd__ как вызов __add__ и так далее. Обратите внимание, что объект слева от оператора ( other в примере) не должен определять (или возвращать NotImplemented ) для своего определения неотраженной версии операции. Например, в примере some_object.__radd__ будет вызываться только в том случае, если other не определяет __add__ .

__radd__(сам, другой)
Реализует отраженное дополнение.
__rsub__(сам, другой)
Реализует отраженное вычитание.
__rmul__(сам, другой)
Реализует отраженное умножение.
__rfloordiv__(сам, другой)
Реализует целочисленное деление с использованием оператора // .
__rdiv__(сам, другой)
Реализует отраженное деление с помощью оператора /.
__rruediv__(сам, другой)
Орудия отражают истинное деление. Обратите внимание, что это работает только тогда, когда действует из __future__ import Division .
__rmod__(сам, другой)
Реализует отражение по модулю с помощью оператора % .
__rdivmod__(сам, другой)
Реализует поведение для длинного деления с использованием встроенной функции divmod() при вызове divmod(other, self) .
__rpow__
Реализует поведение для отраженных показателей с помощью оператора ** .
__rlshift__(сам, другой)
Реализует отраженный побитовый сдвиг влево с помощью оператора << .
__rrshift__(сам, другой)
Реализует отраженный побитовый сдвиг вправо с помощью оператора >> .
__rand__(я, другой)
Реализует побитовое отражение и использует оператор и . оператор.
Расширенное назначение

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

  х = 5
x += 1 # другими словами x = x + 1  

Каждый из этих методов должен возвращать значение, которому должна быть присвоена переменная в левой части (например, для a += b , __iadd__ может вернуть a + b , которое будет присвоено ).Вот список:

__iadd__(сам, другой)
Реализует сложение с присваиванием.
__isub__(сам, другой)
Реализует вычитание с присваиванием.
__imul__(сам, другой)
Реализует умножение с присваиванием.
__ifloordiv__(сам, другой)
Реализует целочисленное деление с присваиванием с использованием оператора //= .
__idiv__(сам, другой)
Реализует деление с присваиванием с помощью оператора /= .
__itruediv__(сам, другой)
Реализует истинное деление с присваиванием. Обратите внимание, что это работает только тогда, когда действует из __future__ import Division .
__imod__(сам, другой)
Реализует по модулю с присваиванием с помощью оператора %= .
__ipow__
Реализует поведение для показателей с присваиванием с использованием оператора **= .
__ilshift__(сам, другой)
Реализует побитовый сдвиг влево с присваиванием с использованием оператора <<= .
__irshift__(сам, другой)
Реализует побитовый сдвиг вправо с присваиванием с помощью оператора >>= .= .
Магические методы преобразования типов

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

__int__(сам)
Реализует преобразование типа в int.
__long__(сам)
Реализует преобразование типа в long.
__поплавок__(сам)
Реализует преобразование типа в float.
__complex__(я)
Реализует преобразование типа в сложный.
__октябрь__(сам)
Реализует преобразование типа в восьмеричный.
__hex__(сам)
Реализует преобразование типа в шестнадцатеричный.
__index__(собственный)
Реализует преобразование типа в int, когда объект используется в выражении среза. Если вы определяете пользовательский числовой тип, который может использоваться при нарезке, вы должны определить __index__ .
__trunc__(сам)
Вызывается при вызове math.trunc(self) . __trunc__ должен возвращать значение `self, усеченное до целочисленного типа (обычно длинного).
__принуждение__(сам, другой)
Метод реализации арифметики смешанного режима. __coerce__ должен возвращать None , если преобразование типов невозможно. В противном случае он должен вернуть пару (две кортежа) из self и other , обработанных так, чтобы они имели один и тот же тип.

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

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

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

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

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

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

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

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

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

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

  класс AccessCounter(объект):
    '''Класс, который содержит значение и реализует счетчик доступа.Счетчик увеличивается при каждом изменении значения.'''

    защита __init__(я, значение):
        super(AccessCounter, self).__setattr__('счетчик', 0)
        super(AccessCounter, self).__setattr__('value', val)

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

    def __delattr__(я, имя):
        если имя == 'значение':
            super(AccessCounter, self).__setattr__('счетчик', self.counter + 1)
        super(AccessCounter, self).__delattr__(имя)  

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

Требования

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

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

Магия контейнеров

Без дальнейших ожиданий, вот волшебные методы, которые используют контейнеры:

__len__(сам)
Возвращает длину контейнера. Часть протокола как для неизменяемых, так и для изменяемых контейнеров.
__getitem__(я, ключ)
Определяет поведение при доступе к элементу, используя нотацию self[key] . Это также является частью изменяемых и неизменяемых протоколов контейнеров.Он также должен вызвать соответствующие исключения: TypeError , если тип ключа неверен, и KeyError , если для ключа нет соответствующего значения.
__setitem__(я, ключ, значение)
Определяет поведение при назначении элемента, используя нотацию self[nkey] = value . Это часть протокола изменяемого контейнера. Опять же, вы должны поднять KeyError и TypeError , где это уместно.
__delitem__(я, ключ)
Определяет поведение при удалении элемента (т.грамм. del self[key] ). Это только часть протокола изменяемого контейнера. Вы должны вызывать соответствующие исключения, когда используется недопустимый ключ.
__iter__(сам)
Должен возвращать итератор для контейнера. Итераторы возвращаются в ряде контекстов, в первую очередь встроенной функцией iter() и когда контейнер зацикливается с использованием формы для x in container: . Итераторы являются собственными объектами, и они также должны определять метод __iter__ , который возвращает self .
__обратный__(сам)
Вызывается для реализации поведения встроенной функции reversed() . Должен возвращать обратную версию последовательности. Реализуйте это только в том случае, если класс последовательности упорядочен, например, список или кортеж.
__содержит__(я, предмет)
__contains__ определяет поведение тестов на принадлежность с использованием в и не в . Вы спросите, почему это не часть протокола последовательности? Потому что, когда __contains__ не определено, Python просто перебирает последовательность и возвращает True , если находит искомый элемент.
__missing__(я, ключ)
__missing__ используется в подклассах dict . Он определяет поведение при доступе к ключу, которого нет в словаре (так, например, если бы у меня был словарь d и я сказал d["george"] , когда "george" не является ключом в словаре будет вызываться d.__missing__("george") ).
Пример

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

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

    def __init__(я, значения=Нет):
        если значения Нет:
            самостоятельные значения = []
        еще:
            self.values ​​= значения

    защита __len__(я):
        вернуть len(self.values)

    def __getitem__(я, ключ):
        # если ключ имеет недопустимый тип или значение, значения списка вызовут ошибку
        вернуть self.values[ключ]

    def __setitem__(я, ключ, значение):
        себя.значения [ключ] = значение

    def __delitem__(я, ключ):
        del self.values[ключ]

    защита __iter__(я):
        вернуть iter(self.values)

    защита __reversed__(я):
        вернуть в обратном порядке (self.values)

    def добавить (я, значение):
        self.values.append(значение)
    Голова защиты (я):
        # получить первый элемент
        вернуть self.values[0]
    защита хвоста (я):
        # получить все элементы после первого
        вернуть self.values[1:]
    определение инициализации (я):
        # получить элементы до последнего
        вернуть себя.значения[:-1]
    деф последний(я):
        # получить последний элемент
        вернуть self.values[-1]
    падение защиты (я, n):
        # получить все элементы, кроме первых n
        вернуть self.values[n:]
    деф взять (я, п):
        # получить первые n элементов
        вернуть self.values[:n]  

Вот он, (относительно) полезный пример того, как реализовать собственную последовательность. Конечно, есть и более полезные приложения пользовательских последовательностей, но довольно многие из них уже реализованы в стандартной библиотеке (включая батарейки, верно?), например, Counter , OrderedDict и NamedTuple .

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

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

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

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

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

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

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

.
  класс Объект:
    '''Класс для представления объекта. Может вызываться для обновления позиции объекта.'''

    def __init__(я, размер, x, y):
        self.x, self.y = x, y
        собственный размер = размер

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

    # отрезать...  

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

  с открытым ('foo.txt') в качестве строки:
    # выполнить какое-то действие с баром  

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

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

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

  класс Ближний:
    '''Менеджер контекста для автоматического закрытия объекта с помощью метода close
    в операторе with.'''

    защита __init__(я, объект):
        селф.объект = объект

    защита __enter__(сам):
        вернуть себя.obj # привязан к цели

    def __exit__(я, тип_исключения, значение_исключения, трассировка):
        пытаться:
           self.obj.close ()
        кроме AttributeError: # obj нельзя закрыть
           print 'Не закрывается.'
           return True # исключение успешно обработано  

Вот пример Closer в действии, демонстрирующий использование FTP-подключения (закрываемый сокет):

  >>> из импорта magicmethods Closer
>>> из ftplib импортировать FTP
>>> с Closer(FTP('ftp.somesite.com')) как соединение:
... соед.дир()
...
# вывод опущен для краткости
>>> соед.дир()
# длинное сообщение AttributeError, нельзя использовать закрытое соединение
>>> с Closer(int(5)) as i:
... я += 1
...
Не закрывающийся.
>>> я
6  

Видите, как наша оболочка изящно справляется как с правильным, так и с неправильным использованием? В этом сила менеджеров контекста и магических методов. Обратите внимание, что стандартная библиотека Python включает модуль contextlib, содержащий менеджер контекста, contextlib.close() , который делает примерно то же самое (без какой-либо обработки случая, когда у объекта нет метода close() ).

См. http://docs.python.org/2/library/abc.html.

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

Чтобы быть дескриптором, класс должен иметь хотя бы одно из __get__ , __set__ и __delete__ . Давайте посмотрим на эти волшебные методы:

__get__(я, экземпляр, владелец)
Определите поведение при получении значения дескриптора.Экземпляр — это экземпляр объекта-владельца. owner - это сам класс владельца.
__set__(я, экземпляр, значение)
Определите поведение при изменении значения дескриптора. Экземпляр — это экземпляр класса владельца, а значение — это значение, на которое устанавливается дескриптор.
__delete__(я, экземпляр)
Определите поведение при удалении значения дескриптора. Экземпляр — это экземпляр объекта-владельца.

Теперь пример полезного применения дескрипторов: преобразование единиц измерения.

  класс Метр(объект):
    '''Описание счетчика'''.

    def __init__(я, значение=0.0):
        self.value = число с плавающей запятой (значение)
    def __get__(я, экземпляр, владелец):
        вернуть self.value
    def __set__(я, экземпляр, значение):
        self.value = число с плавающей запятой (значение)

класс Фут (объект):
    '''Описание стопы'''.

    def __get__(я, экземпляр, владелец):
        вернуть экземпляр.метр * 3,2808
    def __set__(я, экземпляр, значение):
        instance.meter = число с плавающей запятой (значение) / 3,2808

класс Расстояние (объект):
    '''Класс для представления расстояния, содержащий два дескриптора для футов и
    метров.'''
    метр = метр()
    фут = фут()  

Иногда, особенно при работе с изменяемыми объектами, вы хотите иметь возможность копировать объект и вносить изменения, не затрагивая то, из чего вы копировали. Здесь в игру вступает копия Python . Однако (к счастью) модули Python не обладают разумом, поэтому нам не нужно беспокоиться о восстании роботов на основе Linux, но мы должны указать Python, как эффективно копировать вещи.

__копия__(на себя)
Определяет поведение для copy.copy() для экземпляров вашего класса. copy.copy() возвращает неглубокую копию вашего объекта — это означает, что хотя сам экземпляр является новым экземпляром, на все его данные ссылаются — т. е. копируется сам объект, но его данные по-прежнему ссылаются (и, следовательно, изменения данных в неглубокой копии могут вызвать изменения в оригинале).
__deepcopy__(я, мемодикт={})
Определяет поведение для копии .deepcopy() для экземпляров вашего класса. copy.deepcopy() возвращает глубокую копию вашего объекта - оба объекта и копируются. memodict — это кеш ранее скопированных объектов — это оптимизирует копирование и предотвращает бесконечную рекурсию при копировании рекурсивных структур данных. Если вы хотите глубоко скопировать отдельный атрибут, вызовите copy.deepcopy() для этого атрибута с memodict в качестве первого аргумента.

Каковы варианты использования этих магических методов? Как всегда, в любом случае, когда вам нужен более детальный контроль, чем то, что дает вам поведение по умолчанию.Например, если вы пытаетесь скопировать объект, который хранит кеш в виде словаря (который может быть большим), может не иметь смысла копировать и кеш — если кеш может быть разделен в памяти между экземплярами, тогда так должно быть.

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

Pickle настолько важен, что у него есть не только собственный модуль ( pickle ), но и собственный протокол и волшебные методы, которые с ним связаны. Но сначала коротко о том, как травить существующие типы (можете пропустить, если уже знаете).

Маринование: быстрое замачивание в рассоле

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

  импортный рассол

данные = {'foo': [1, 2, 3],
        'бар': ("Привет", "мир!"),
        'баз': правда}
jar = открыть('data.pkl', 'wb')
pickle.dump(data, jar) # записываем обработанные данные в файл jar
jar.close()  

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

  импортный рассол

pkl_file = open('data.pkl', 'rb') # подключиться к маринованным данным
data = pickle.load(pkl_file) # загружаем в переменную
распечатать данные
pkl_file.close()  

Что происходит? Именно то, что вы ожидаете. Точно так же, как у нас было данных все это время.

А теперь предостережение: травление не идеально. Файлы Pickle легко повреждаются случайно и намеренно. Pickling может быть более безопасным, чем использование простых текстовых файлов, но его все же можно использовать для запуска вредоносного кода.Он также несовместим с разными версиями Python, поэтому не рассчитывайте распространять консервированные объекты и ожидать, что люди смогут их открыть. Однако он также может быть мощным инструментом для кэширования и других распространенных задач сериализации.

Травление собственных объектов

Травление подходит не только для встроенных типов. Это для любого класса, который следует протоколу рассола. В протоколе pickle есть четыре необязательных метода для объектов Python, чтобы настроить их действие (это немного отличается для расширений C, но это не входит в нашу задачу):

__getinitargs__(сам)
Если вы хотите, чтобы __init__ вызывался, когда ваш класс распаковывается, вы можете определить __getinitargs__ , который должен возвращать кортеж аргументов, которые вы хотите передать в __init__ .Обратите внимание, что этот метод будет работать только для классов старого стиля.
__getnewars__(сам)
Для классов нового стиля вы можете влиять на то, какие аргументы будут переданы в __new__ при распаковке. Этот метод также должен возвращать кортеж аргументов, которые затем будут переданы в __new__ .
__getstate__(сам)
Вместо сохранения атрибута __dict__ объекта можно вернуть пользовательское состояние, которое будет сохранено при мариновании объекта.Это состояние будет использоваться __setstate__ , когда объект будет распакован.
__setstate__(я, состояние)
Когда объект распаковывается, если определено __setstate__ , состояние объекта будет передано ему, а не напрямую применено к объекту __dict__ . Это идет рука об руку с __getstate__ : когда оба определены, вы можете представлять состояние маринования объекта, как хотите, с чем угодно.
__reduce__(сам)
При определении типов расширений (т.е., типы, реализованные с использованием C API Python), вы должны сообщить Python, как их распарывать, если вы хотите, чтобы они их рассолили. __reduce__() вызывается при мариновании определяющего его объекта. Он может либо возвращать строку, представляющую глобальное имя, которое Python будет искать и анализировать, либо кортеж. Кортеж содержит от 2 до 5 элементов: вызываемый объект, который вызывается для воссоздания объекта, кортеж аргументов для этого вызываемого объекта, состояние, которое должно быть передано в __setstate__ (необязательно), итератор, выдающий элементы списка для обработки ( необязательный) и итератор, выдающий элементы словаря для обработки (необязательный).
__reduce_ex__(сам)
__reduce_ex__ существует для совместимости. Если он определен, __reduce_ex__ будет вызываться вместо __reduce__ при травлении. __reduce__ также можно определить для более старых версий API травления, которые не поддерживали __reduce_ex__ .

Пример

Наш пример — это Slate , который запоминает, какие у него были значения и когда эти значения были записаны в него.Однако этот конкретный лист становится пустым каждый раз, когда он травится: текущее значение не будет сохранено.

  время импорта

Шифер класса:
    '''Класс для хранения строки и журнала изменений и забвения его значения при
    маринованный.'''

    def __init__(я, значение):
        самостоятельная ценность = ценность
        self.last_change = время.asctime()
        self.history = {}

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

    деф print_changes (я):
        print 'Журнал изменений для объекта Slate:'
        для k, v в self.history.items():
            напечатать '%s\t %s' % (k, v)

    защита __getstate__(я):
        # Намеренно не возвращайте self.value или self.last_change.
        # Мы хотим иметь "чистый лист" при распаковке.
        вернуть собственную историю

    def __setstate__(я, состояние):
        # Сделать self.history = state и last_change и значение undefined
        себя.история = состояние
        self.value, self.last_change = Нет, Нет  

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

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

Магический метод При вызове (пример) Пояснение
__new__(cls [,...]) экземпляр = MyClass(arg1, arg2) __new__ вызывается при создании экземпляра
__init__(сам [,...]) экземпляр = MyClass(arg1, arg2) __init__ вызывается при создании экземпляра
__cmp__(сам, другой) сам == другой , сам > другой и т. д. Вызывается для любого сравнения
__pos__(сам) Унарный плюс
__отрицательный__(сам) - сам Унарный знак минус
__invert__(себя) ~сам Побитовая инверсия
__index__(собственный) х[я] Преобразование при использовании объекта в качестве индекса
__ненулевой__(сам) логический (собственный) Логическое значение объекта
__getattr__(я, имя) самостоятельно.имя # имя не существует Доступ к несуществующему атрибуту
__setattr__(я, имя, значение) самоназвание = значение Присвоение атрибуту
__delattr__(я, имя) имя пользователя Удаление атрибута
__getattribute__(я, имя) собственное имя Доступ к любому атрибуту
__getitem__(я, ключ) сам[ключ] Доступ к элементу с помощью индекса
__setitem__(self, key, val) сам[ключ] = значение Назначение элементу с помощью индекса
__delitem__(я, ключ) собственный [ключ] Удаление элемента с использованием индекса
__iter__(сам) для х внутри себя Итерация
__contains__(собственность, значение) значение в себе , значение не в себе Тесты на принадлежность с использованием в
__call__(я [,...]) сам (аргументы) "Вызов" экземпляра
__enter__(себя) с собой как x: с менеджерами контекста оператора
__exit__(self, exc, val, trace) с собой как x: с менеджерами контекста оператора
__getstate__(я) рассол.дамп (pkl_file, сам) Травление
__setstate__(сам) данные = pickle.load(pkl_file) Травление

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

Здесь мы документируем несколько основных мест, где Python 3 отличается от 2.x с точки зрения его объектной модели:

  • Поскольку различие между строкой и юникодом было устранено в Python 3, __unicode__ исчезло, а __bytes__ (который ведет себя аналогично __str__ и __unicode__ в 2.7) существует новый встроенный модуль для построения байтовых массивов.
  • Поскольку в Python 3 для деления по умолчанию используется истинное деление, __div__ отсутствует в Python 3
  • __coerce__ отсутствует из-за избыточности с другими магическими методами и запутанного поведения
  • __cmp__ отсутствует из-за избыточности с другими магическими методами
  • __nonzero__ был переименован в __bool__

Специальные методы изменят то, как вы строите классы в Python

Знакомство с наиболее полезными специальными методами

Фото Криса Рида на Unsplash

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

В этой статье я буду использовать в качестве примера класс Point , который представляет 2D-точку. Вот метод __init__ :

  class  Point: 
def __init__(self, x, y):
self.x = x
self.y = y

Есть два специальных метода, которые вызываются, когда мы хотим представить класс: __repr__(self) и __str__(self) .

У них разные варианты использования:

  • __repr__ в идеале должен возвращать объект, который можно использовать для воссоздания текущего состояния класса. Возвращаемые объекты не обязательно должны быть строкой. Однако, если это строка, она должна иметь вид "<..description..>" .
  • __str__ , с другой стороны, всегда должен возвращать строку.Это метод, который вызывается всякий раз, когда мы преобразуем объект в строку (например, когда мы используем print ).

По умолчанию метод __str __ определяется как:

  def  __str __ (Self): 
Возвращение Self .__ Rep __ (Self):

Итак, мы можем переопределить только __REPR__ , если он возвращает строку. Например, для нашего класса Point :

  def  __repr__(self): 
return ""

Но что, если мы хотим вернуть другой тип (например, кортеж) при вызове __repr__ ? Затем нам нужно переопределить оба метода:

  def  __repr__(self): 
return (self.x, self.y) def __str__(self):
return "Point(" + str( self.x) + ", "+ str(self.y) + ")"

Вы когда-нибудь хотели использовать == для проверки равенства двух экземпляров? Вот как это сделать.

Каждый раз, когда вы пишете a == b , внутренне вызывается метод a.__eq__(b) . Поэтому, чтобы иметь возможность сравнивать объекты пользовательского класса, нам нужно только переопределить этот метод. Давайте посмотрим, как это сделать:

  def  __eq__(self, other): 
if not isinstance(other, Point):
return False
return self.x == other.x and self .y == other.y

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

Существует еще один метод, __ne__ , который вызывается всякий раз, когда мы используем != . Однако нам не нужно его реализовывать, так как по умолчанию он использует определение __eq__ для возврата правильного результата.

Теперь, когда мы можем проверить, равны ли два объекта, мы можем захотеть упорядочить их. Но для этого мы должны уметь использовать операторы < , <= , > , >= .Посмотрим, как они работают под капотом.

Как и оператор равенства, есть несколько встроенных функций, которые вызываются всякий раз, когда мы сравниваем два объекта. Это: __lt__ , __le__ , __gt__ , __ge__ . Поэтому нам нужно переопределить эти четыре метода. Обратите внимание, что вы можете определить один из них, а затем рекурсивно указать другие, но это сделает код немного медленнее. Вот как мы можем реализовать общий порядок для класса Point : .x > other.x или (self.x == other.x и self.y > self.y) def __lt__(self, other):
если не isinstance(other, Point):
поднять TypeError
return self.x < other.x or (self.x == other.x and self.y < self.y) def __ge__(self, other):
# Определено рекурсивно
return self > other or self == other def __le__(self, other):
# Определено рекурсивно
return self < other or self == other

Примечание: __eq__ , иначе эти методы не сработают.

Теперь мы можем захотеть выполнить некоторые арифметические операции с нашими объектами. Их также можно реализовать с помощью пяти специальных методов:

  • __add__(self, other) вызывается при использовании a+b
  • __sub__(self, other) вызывается при использовании ab
  • __mul__(self, other) вызывается при использовании a*b
  • __truediv__(self, other) вызывается при использовании a/b
  • _ вызывается, когда мы используем a**b

Давайте посмотрим, как складывать и вычитать две точки:

  def  __add__(self, other): 
if not isinstance(other, Point):
поднять TypeError
возврат Point(self.x+other.x, self.y+other.y) def __sub__(self, other):
if not isinstance(other, Point):
поднять TypeError
вернуть Point(self.x-other .x, self.y-other.y)

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

Предположим, вы хотите использовать свой класс в качестве ключа в словаре. Если вы попытаетесь, вы получите эту ошибку:

 TypeError: unhashable type: 'Point' 

На самом деле объект можно использовать в качестве ключа только в том случае, если t является хэшируемым.Но как сделать объект хешируемым? Вам нужно реализовать метод __hash__(self) .

Самый простой способ сделать это — создать кортеж, содержащий все поля, определяющие объект, а затем вернуть его хэш. Обратите внимание, что атрибуты, которые вы используете для хеширования, должны быть константами. Таким образом, нам также потребуется переопределить метод __init__ , чтобы сделать x и y закрытыми. Кроме того, класс, который определяет __hash__ , должен также определять __eq__ .

  class  Point: 
def __init__(self, x, y):
self.__x = x
self.__y = y @property
def x(self):
70 return . @property
def y(self):
return self.__y def __eq__(self, other):
if not isinstance(other, Point):
return False
79 == other.x и self.y == other.y def __hash__(self):
return hash((self.x, self.y))

Мы знаем, как использовать метод len(x) для получения длины списка. Но что, если мы хотим использовать его для получения длины пользовательского объекта? В этом случае нам нужно переопределить метод __len__(self) . Единственным ограничением является то, что это должно быть целое число

. Например, мы можем захотеть определить длину объекта Point как манхэттенское расстояние от начала координат. Таким образом, мы можем реализовать этот метод:

  def  __len__(self): 
return abs(self.x)+abs(self.y)

и теперь мы можем использовать метод len для получения манхэттенского расстояния:

 >>> len( Point(1, -3)) 
4

Иногда нам нужно использовать наш класс как логическое значение. Например, точка может иметь значение True , если она не является исходной или находится за пределами определенной области. Для этого мы преобразуем объект в bool , мы используем bool(Point(1, 2)) . Этот метод вызывает внутренний метод __bool__(self) .Поэтому нам нужно переопределить этот метод, чтобы создать пользовательское поведение:

  def  __bool__(self): 
# Вернуть False, если это источник
return self.