Главная страница
Навигация по странице:

  • Собственные классы коллекций

  • Создание классов, включающих коллекции

  • Программирование на Python 3. Руководство издательство СимволПлюс


    Скачать 3.74 Mb.
    НазваниеРуководство издательство СимволПлюс
    Дата10.11.2022
    Размер3.74 Mb.
    Формат файлаpdf
    Имя файлаПрограммирование на Python 3.pdf
    ТипРуководство
    #780382
    страница36 из 74
    1   ...   32   33   34   35   36   37   38   39   ...   74

    __new__()
    __init__()
    __eq__()
    __repr__()
    __str__()
    __hash__()
    __format__()
    FuzzyBool
    __new__()
    __init__()
    __eq__()
    __repr__()
    __str__()
    __hash__()
    __format__()
    __bool__()
    __invert__()
    __and__()
    __iand__()
    Обозначения унаследован реализован
    переопределен
    Рис. 6.5. Дерево наследования альтернативного класса FuzzyBool

    Собственные классы
    301
    При создании нового класса изменяемых объектов мы обычно полага
    емся на метод object.__new__(), который создает неинициализирован
    ную заготовку объекта. Но в случае создания классов неизменяемых объектов нам необходимо выполнить создание и инициализацию за один шаг, потому что неизменяемый объект не может изменяться по
    сле того, как будет создан.
    Метод __new__() вызывается еще до того, как объект будет создан (так,
    именно созданием объекта и занимается метод __new__()), поэтому ме
    тод не получает объект в виде аргумента self, в конце концов, объект еще не существует. В действительности метод __new__() – это метод класса, он похож на обычный метод класса за исключением того, что он вызывается в контексте класса, а не в контексте экземпляра, и в пер
    вом аргументе ему передается класс. Имя аргумента cls, в котором пе
    редается класс, определяется только соглашениями, так же как и имя self используется для обозначения самого объекта исключительно в соответствии с общепринятыми соглашениями.
    Итак, когда мы записываем инструкцию f = FuzzyBool(0.7), за кулиса
    ми интерпретатор вызывает метод FuzzyBool.__new__(FuzzyBool, 0.7),
    чтобы создать новый объект – например, fuzzy, а затем метод fuz
    zy.__init__()
    , чтобы выполнить его инициализацию, и в результате возвращает ссылку на объект fuzzy – ту самую ссылку, которая будет записана в переменную f. Большая часть работы метода __new__() вы
    полняется реализацией базового класса object.__new__(), а все, что де
    лаем мы, – это гарантируем попадание значения в заданный диапазон.
    Методы класса определяются с помощью встроенной функции class
    method()
    , используемой как декоратор. Но нам совсем необязательно брать на себя труд писать @classmethod перед инструкцией def __new__(),
    потому что интерпретатор уже знает, что этот метод всегда является методом класса. Декоратор необходимо использовать, только когда создаются другие методы класса, как будет показано в последнем раз
    деле главы.
    Теперь, когда мы познакомились с методами класса, можно познако
    миться с другими разновидностями методов, существующими в языке
    Python. Методы класса получают в первом аргументе, который пере
    дается интерпретатором Python автоматически, свой класс. Обычные методы получают в первом аргументе, который передается интерпре
    татором Python автоматически, экземпляр класса, относительно кото
    рого был вызван метод. Статические методы не имеют первого аргу
    мента, добавляемого автоматически. Все перечисленные разновидно
    сти методов могут получать любое число аргументов (в виде второго и последующих аргументов – в случае методов класса и обычных мето
    дов, и в виде первого и последующих аргументов – в случае статиче
    ских методов).
    def __invert__ (self):
    return FuzzyBool(1.0  float(self))

    302
    Глава 6. Объектно/ориентированное программирование
    Этот метод реализует поддержку битового оператора НЕ (

    ), как и пре
    жде. Примечательно, что теперь вместо обращения к частному атрибу
    ту, в котором хранилось значение FuzzyBool, мы используем сам объект self
    . Это стало возможным благодаря наследованию класса float, а это означает, что экземпляр FuzzyBool может использоваться везде, где ожидается объект типа float, естественно, за исключением методов,
    реализация которых была «исключена» из класса FuzzyBool.
    def __and__(self, other):
    return FuzzyBool(min(self, other))
    def __iand__(self, other):
    return FuzzyBool(min(self, other))
    Реализация логических операций также не изменилась (хотя про
    граммный код претерпел некоторые изменения). Здесь так же, как и в методе __invert__(), можно непосредственно использовать объекты self и other, как если бы они были объектами типа float. Мы опустили методы реализации операции ИЛИ, так как они отличаются только именами (__or__() и __ior__()) и тем, что вместо функции min() исполь
    зуют функцию max().
    def __repr__(self):
    return ("{0}({1})".format(self.__class__.__name__,
    super().__repr__()))
    Нам потребовалось переопределить реализацию метода __repr__(), по
    тому что метод float.__repr__() просто возвращает число в виде стро
    ки, тогда как нам необходимо, чтобы было указано имя класса, чтобы репрезентативную форму представления можно было использовать в вызове функции eval(). Мы не можем просто передать методу str.for
    mat()
    объект self во втором аргументе, так как это приведет к беско
    нечной рекурсии метода __repr__(), поэтому мы вызываем реализацию базового класса.
    Нам не требуется переопределять метод __str__(), потому что вполне достаточно версии базового класса float.__str__(), которая и будет ис
    пользоваться в отсутствие метода FuzzyBool.__str__().
    def __bool__(self):
    return self > 0.5
    def __int__(self):
    return round(self)
    Когда объект типа float используется в логическом контексте, он бу
    дет рассматриваться как False, когда его значение равно 0.0, и True –
    в противном случае. Это не соответствует поведению объектов класса
    FuzzyBool
    , поэтому мы переопределили этот метод. Точно так же функ
    ция int(self) будет просто отбрасывать дробную часть, превращая в 0
    любое значение, кроме 1.0, поэтому здесь мы используем функцию

    Собственные классы
    303
    round()
    , чтобы округлять до 0 любое значение, меньшее или равное
    0.5, и до 1 – значения, большие 0.5.
    Мы не стали переопределять методы __hash__(), __format__() и те мето
    ды, которые обеспечивают поддержку операторов сравнения, посколь
    ку реализации этих методов в классе float корректно работают приме
    нительно к классу FuzzyBool.
    Методы, которые мы переопределили, обеспечивают полную реализа
    цию класса FuzzyBool, и для этого потребовалось меньше программного кода, чем было представлено в предыдущем подподразделе. Однако этот новый класс FuzzyBool наследует более 30 методов, которые для него не имеют смысла. Например, ни один из арифметических опера
    торов или операторов сдвига (+,

    , *, /, <<, >> и т. д.) неприменим к объ
    ектам класса FuzzyBool. Ниже показано, как «исключается» реализа
    ция операции сложения:
    def __add__(self, other):
    raise NotImplementedError()
    Мы могли бы написать точно такой же программный код для методов
    __iadd__()
    и __radd__(), чтобы полностью исключить возможность сло
    жения. (Обратите внимание, что NotImplementedError – это стандартное исключение и оно полностью отличается от объекта NotImplemented.)
    Чтобы более точно имитировать поведение встроенных классов языка
    Python, вместо исключения NotImplementedError можно возбуждать ис
    ключение TypeError. Ниже показано, как можно было бы заставить ме
    тод FuzzyBool.__add__() вести себя так же, как встроенные классы, ко
    торые используются в недопустимой операции:
    def __add__(self, other):
    raise TypeError("unsupported operand type(s) for +: "
    "'{0}' and '{1}'".format(
    self.__class__.__name__, other.__class__.__name__))
    Реализация одноместных операций, которые требуется исключить,
    так чтобы они имитировали поведение встроенных типов, выглядит немного проще:
    def __neg__(self):
    raise TypeError("bad operand type for unary : '{0}'".format(
    self.__class__.__name__))
    При реализации операторов сравнения используется намного более простой прием. Например, исключить поддержку оператора == мы могли бы так:
    def __eq__(self, other):
    return NotImplemented
    Если метод, реализующий оператор сравнения (<, <=, ==, !=, >=, >), воз
    вращает встроенный объект NotImplemented, то при попытке использо
    вать этот метод интерпретатор сначала попытается выполнить сравни

    304
    Глава 6. Объектно/ориентированное программирование вание, поменяв операнды местами (на случай, если у объекта other имеется подходящий метод сравнения), а затем, если этот прием не по
    может, возбудит исключение TypeError с сообщением, поясняющим,
    что данная операция не поддерживается для операндов этих типов. Но во всех остальных методах, не имеющих отношения к сравнению и ко
    торые требуется исключить, мы должны возбуждать либо исключение
    NotImplementedError
    , либо исключение TypeError, как это было сделано в методах __add__() и __neg__() выше.
    Однако было бы слишком утомительно исключать каждый нежела
    тельный метод, как было показано выше, хотя такой подход работает и в программном коде выглядит понятнее. Теперь мы рассмотрим бо
    лее совершенную методику исключения методов – она используется в модуле FuzzyBoolAlt, но для вас, пожалуй, было бы лучше перейти к следующему разделу (стр. 306) и вернуться сюда, если потребуется взглянуть на практический пример.
    Ниже приводится программный код, выполняющий исключение двух ненужных нам одноместных операций:
    for name, operator in (("__neg__", ""),
    ("__index__", "index()")):
    message = ("bad operand type for unary {0}: '{{self}}'"
    .format(operator))
    exec("def {0}(self): raise TypeError(\"{1}\".format("
    "self=self.__class__.__name__))".format(name, message))
    Встроенная функция exec() динамически выполняет программный код из передаваемого ей объекта. В дан
    ном случае – это строка, но это могут быть объекты и не
    которых других типов. По умолчанию программный код выполняется в объемлющем контексте, в данном слу
    чае – внутри определения класса FuzzyBool, поэтому вы
    полняемые инструкции def будут создавать методы класса FuzzyBool, что нам и требуется. Программный код будет выполняться всего один раз, во время импортиро
    вания модуля FuzzyBoolAlt. Ниже приводится программ
    ный код, который будет сгенерирован первым кортежем
    ("__neg__", "

    ")
    :
    def __neg__(self):
    raise TypeError("bad operand type for unary : '{self}'"
    .format(self=self.__class__.__name__))
    Здесь мы возбуждаем исключение с текстом сообщения, соответствую
    щим тому, которое используется интерпретатором Python для своих собственных типов. Программный код, обрабатывающий двухмест
    ные методы и nместные функции (такие как pow()), следует тому же шаблону, но с другим сообщением об ошибке. Для полноты картины ниже приводится программный код, который мы использовали:
    Динамиче
    ское програм
    мирование, стр. 406

    Собственные классы
    305
    for name, operator in (("__xor__", "^"), ("__ixor__", "^="),
    ("__add__", "+"), ("__iadd__", "+="), ("__radd__", "+"),
    ("__sub__", ""), ("__isub__", "="), ("__rsub__", ""),
    ("__mul__", "*"), ("__imul__", "*="), ("__rmul__", "*"),
    ("__pow__", "**"), ("__ipow__", "**="),
    ("__rpow__", "**"), ("__floordiv__", "//"),
    ("__ifloordiv__", "//="), ("__rfloordiv__", "//"),
    ("__truediv__", "/"), ("__itruediv__", "/="),
    ("__rtruediv__", "/"), ("__divmod__", "divmod()"),
    ("__rdivmod__", "divmod()"), ("__mod__", "%"),
    ("__imod__", "%="), ("__rmod__", "%"),
    ("__lshift__", "<<"), ("__ilshift__", "<<="),
    ("__rlshift__", "<<"), ("__rshift__", ">>"),
    ("__irshift__", ">>="), ("__rrshift__", ">>")):
    message = ("unsupported operand type(s) for {0}: "
    "'{{self}}'{{join}} {{args}}".format(operator))
    exec("def {0}(self, *args):\n"
    " types = [\"'\" + arg.__class__.__name__ + \"'\" "
    "for arg in args]\n"
    " raise TypeError(\"{1}\".format("
    "self=self.__class__.__name__, "
    "join=(\" and\" if len(args) == 1 else \",\"),"
    "args=\", \".join(types)))".format(name, message))
    Программный код получился немного более сложным, чем прежде, по
    тому что для двухместных операторов мы должны выводить сообще
    ния, где перечислены оба типа, как type1 and type2, а в случае трех и более типов, мы должны выводить их как type1, type2, type3, чтобы имитировать поведение встроенных типов. Ниже приводится про
    граммный код, сгенерированный для первого кортежа ("__xor__", "^"):
    def __xor__(self, *args):
    types = ["'" + arg.__class__.__name__ + "'" for arg in args]
    raise TypeError("unsupported operand type(s) for ^: "
    "'{self}'{join} {args}".format(
    self=self.__class__.__name__,
    join=(" and" if len(args) == 1 else ","),
    args=", ".join(types)))
    Два цикла for ... in, которые мы использовали здесь, можно просто копировать и затем добавлять или удалять одноместные операторы и методы в первом цикле и двухместные и nместные операторы и мето
    ды – во втором, чтобы исключать реализации ненужных методов.
    Теперь, благодаря этому последнему фрагменту программного кода,
    если бы у нас имелись два объекта FuzzyBool, f и g, и мы попытались сложить их, используя выражение f + g, то получили бы исключение
    TypeError с сообщением «unsupported operand type(s) for +: 'FuzzyBool'
    and 'FuzzyBool'» (неподдерживаемые типы операндов для оператора
    +: 'FuzzyBool' и 'FuzzyBool'), то есть именно то, что нам и требуется.

    306
    Глава 6. Объектно/ориентированное программирование
    Способ создания классов, который мы использовали первым при реа
    лизации класса FuzzyBool, распространен намного шире и пригоден для создания практически любых типов. Однако если требуется создать класс неизменяемых объектов, основной способ такой реализации за
    ключается в переопределении метода object.__new__(), наследовании одного из неизменяемых типов языка Python, таких как float, int, str или tuple, с последующей реализацией всех необходимых методов. Не
    достаток такого подхода заключается в том, что может потребоваться исключить реализации некоторых методов, а это приведет к наруше
    нию полиморфизма; поэтому в большинстве случаев вариант на основе агрегирования, использованного в первой реализации класса Fuzzy
    Bool
    , является намного более предпочтительным.
    Собственные классы коллекций
    В подразделах этого раздела мы рассмотрим порядок создания клас
    сов, способных хранить большие объемы данных. Первый класс, кото
    рый будет рассмотрен, – это класс Image, один из тех, что способны хра
    нить изображения. Этот класс является типичным представителем многих классов, используемых для хранения данных, в том смысле,
    что он не только обеспечивает доступ к данным в памяти, но также име
    ет методы сохранения данных на диск и загрузки данных с диска. Вто
    рой и третий классы, которые мы изучим, SortedList и SortedDict, пред
    назначены, чтобы восполнить редкий, вызывающий удивление недо
    статок – отсутствие изначально отсортированных коллекций в стан
    дартной библиотеке Python.
    Создание классов, включающих коллекции
    Простейшим представлением 2мерных цветных изображений являет
    ся двухмерный массив, каждый элемент которого хранит значение цвета. То есть чтобы представить изображение размером 100
    ×100, нам придется хранить 10 000 значений цвета. При создании класса Image
    (в файле Image.py) мы выбрали потенциально более эффективный под
    ход. Класс Image хранит одно значение цвета для фона плюс те пиксели изображения, цвет которых отличается от цвета фона. Реализовано это с помощью словаря, своего рода разреженного массива, каждый ключ которого представляет координаты (x, y), а значение определяет цвет в точке с этими координатами. Если представить изображение размером 100
    ×100, в котором половина пикселей имеют цвет фона, то нам потребуется хранить всего 5 000 + 1 значение цвета, что дает су
    щественную экономию занимаемой памяти.
    Модуль Image.py следует уже знакомому нам шаблону:
    он начинается со строки «shebang», далее следует ин
    формация об авторских правах в виде комментариев, за
    тем – строка документирования модуля, и затем – инст
    Модуль
    pickle
    , стр. 341

    Собственные классы коллекций
    307
    рукции импорта, в данном случае импортируются модули os и pickle.
    Мы коротко опишем модуль pickle, когда будем обсуждать вопросы со
    хранения и загрузки изображений. Вслед за инструкциями импорта следуют объявления наших собственных исключений:
    class ImageError(Exception): pass class CoordinateError(ImageError): pass
    Мы показали только первые два класса исключений, остальные (Load
    Error
    , SaveError, ExportError и NoFilenameError) создаются точно так же и наследуют исключение ImageError. Пользователи класса Image могут выбирать между обработкой конкретных исключений и обработкой базового исключения ImageError.
    Остальную часть модуля занимает определение класса Image, и в самом конце находятся три стандартные строки, запускающие выполнение доктестов модуля. Прежде чем перейти к знакомству с классом и его методами, посмотрим, как его можно использовать:
    border_color = "#FF0000" # красный square_color = "#0000FF" # синий width, height = 240, 60
    midx, midy = width // 2, height // 2
    image = Image.Image(width, height, "square_eye.img")
    for x in range(width):
    for y in range(height):
    if x < 5 or x >= width  5 or y < 5 or y >= height  5:
    image[x, y] = border_color elif midx  20 < x < midx + 20 and midy  20 < y < midy + 20:
    image[x, y] = square_color image.save()
    image.export("square_eye.xpm")
    Обратите внимание, что при установке значений цвета в изображении допускается использовать оператор доступа к элементам ([]). Квадрат
    ные скобки могут также использоваться для получения и удаления
    (фактически для установки в цвет фона) значений цвета в точках с оп
    ределенными координатами (x, y). Координаты передаются в виде еди
    ного кортежа (благодаря оператору запятой), как если бы мы записали обращение в виде: image[(x, y)]. В языке Python легко можно добиться такого вида интеграции с синтаксисом – достаточно лишь реализовать соответствующие специальные методы, которыми в данном случае яв
    ляются методы реализации оператора доступа к элементу: __get
    item__()
    , __setitem__() и __delitem__().
    Для представления цветов в классе Image используются строки шестна
    дцатеричных цифр. Цвет фона должен определяться при создании изображения, в противном случае по умолчанию используется белый цвет. Класс Image может сохранять на диск и загружать с диска изобра
    жения в своем собственном формате, но он также может экспортиро
    вать изображения в формат .xpm, с которым способны работать многие

    1   ...   32   33   34   35   36   37   38   39   ...   74


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