1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/wizardforcel-sicp-py-zh

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
2.7.md 28 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
gitlife-traslator Отправлено 29.11.2024 03:42 9c9d71c

2.7. Методы общего назначения

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

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

2.7.1 Преобразование строк

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

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

Python требует, чтобы все объекты могли генерировать два различных строковых представления: одно — понятное человеку текстовое представление, а другое — представление, которое может быть интерпретировано Python как выражение. Функция str возвращает удобочитаемую строку. Когда это возможно, функция repr возвращает выражение Python, которое можно оценить как эквивалентный объект. Документация функции repr объясняет эту функцию:

repr(object) -> string

Возвращает каноническое строковое представление объекта.
Для большинства типов объектов eval(repr(object)) == object.

Результатом вызова функции repr для значения выражения является то, что Python печатает в интерактивной сессии.

>>> 12e12
12000000000000.0
>>> print(repr(12e12))
12000000000000.0

Когда нет выражения, которое могло бы быть оценено как исходное значение, Python создаёт прокси:

>>> repr(min)
'<built-in function min>'

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

>>> from datetime import date
>>> today = date(2011, 9, 12)
>>> repr(today)
'datetime.date(2011, 9, 12)'
>>> str(today)
'2011-09-12'

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

Передача сообщений предлагает решение этой проблемы: функция repr вызывает функцию с именем __repr__ на своём параметре.

>>> today.__repr__()
'datetime.date(2011, 9, 12)'

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

Функция str реализуется аналогичным образом: она вызывает функцию с именем __str__ на своём параметре.

>>> today.__str__()
'2011-09-12'

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

2.7.2 Множественное представление

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

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

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

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

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

Подобно рациональным числам, комплексные числа естественно представляются парами. Комплексные числа можно рассматривать как двумерное пространство с двумя ортогональными осями, действительной осью и мнимой осью. С этой точки зрения комплексное число z = x + y * i (где i*i = -1) можно рассматривать как точку на плоскости, имеющую действительную часть x и мнимую часть y. Сложение комплексных чисел включает в себя сложение их действительных и мнимых частей.

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

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

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

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

В примере с комплексными числами интерфейс требует реализации четырёх арифметических операций: real, imag, magnitude и angle. Мы можем использовать эти сообщения для реализации сложения и умножения.

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

  • ComplexRI конструирует комплексные числа из действительной и мнимой частей.
  • ComplexMA конструирует комплексные числа из модуля и угла.

Используя эти сообщения и конструкторы, мы можем реализовать арифметику комплексных чисел:

>>> def add_complex(z1, z2):
        return ComplexRI(z1.real + z2.real, z1.imag + z2.imag)
>>> def mul_complex(z1, z2):
        return ComplexMA(z1.magnitude * z2.magnitude, z1.angle + z2.angle)

Термины «абстрактный тип данных» (ADT) и «интерфейс» связаны тонко. ADT описывает класс вещей с полным абстрактным представлением, в то время как интерфейс соответствует классу вещей, которые могут совместно использовать поведение. ``` import atan2

class ComplexRI(object): def init(self, real, imag): self.real = real self.imag = imag

@property
def magnitude(self):
    return (self.real ** 2 + self.imag ** 2) ** 0.5

@property
def angle(self):
    return atan2(self.imag, self.real)

def __repr__(self):
    return 'ComplexRI({0}, {1})'.format(self.real, self.imag)

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

```python
from math import sin, cos

class ComplexMA(object):
    def __init__(self, magnitude, angle):
        self.magnitude = magnitude
        self.angle = angle

    @property
    def real(self):
        return self.magnitude * cos(self.angle)

    @property
    def imag(self):
        return self.magnitude * sin(self.angle)

    def __repr__(self):
        return 'ComplexMA({0}, {1})'.format(self.magnitude, self.angle)

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

from math import pi

add_complex(ComplexRI(1, 2), ComplexMA(2, pi/2))
mul_complex(ComplexRI(0, 1), ComplexRI(0, 1))

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

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

Чтобы сделать наш код более читаемым, мы можем захотеть использовать операторы + и * при выполнении сложения и умножения комплексных чисел. Добавление следующих методов в два класса комплексных чисел позволит использовать эти операторы и функции operator модуля add и mul.

ComplexRI.__add__ = lambda self, other: add_complex(self, other)
ComplexMA.__add__ = lambda self, other: add_complex(self, other)
ComplexRI.__mul__ = lambda self, other: mul_complex(self, other)
ComplexMA.__mul__ = lambda self, other: mul_complex(self, other)

Теперь мы можем использовать префиксные символы для наших пользовательских классов.

ComplexRI(1, 2) + ComplexMA(2, 0)
ComplexRI(0, 1) * ComplexRI(0, 1)

Расширенное чтение. Чтобы решить выражение, содержащее оператор +, Python проверяет специальные методы __add__ левого операнда и __radd__ правого операнда. Если один из них найден, этот метод вызывается с другим операндом в качестве значения аргумента.

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

2.7.3 Обобщённые функции

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

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

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

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

from fractions import gcd

class Rational(object):
    def __init__(self, numer, denom):
        g = gcd(numer, denom)
        self.numer = numer // g
        self.denom = denom // g

    def __repr__(self):
        return 'Rational({0}, {1})'.format(self.numer, self.denom)

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

def add_rational(x, y):
    nx, dx = x.numer, x.denom
    ny, dy = y.numer, y.denom
    return Rational(nx * dy + ny * dx, dx * dy)

def mul_rational(x, y):
    return Rational(x.numer * y.numer, x.denom * y.denom)

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

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

def iscomplex(z):
    return type(z) in (ComplexRI, ComplexMA)

def isrational(z):
    return type(z) == Rational

Здесь мы полагаемся на факт, что каждый объект знает свой тип, и мы можем использовать функцию Python type, чтобы получить тип. Даже если функция type недоступна, мы всё равно можем реализовать iscomplex и isrational, используя Rational, ComplexRI и ComplexMA.

Рассмотрим следующую реализацию add, в которой явно проверяются типы двух параметров. Здесь мы не будем явно использовать специальные методы Python, такие как __add__.

def add_complex_and_rational(z, r):
    pass

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/wizardforcel-sicp-py-zh.git
git@api.gitlife.ru:oschina-mirror/wizardforcel-sicp-py-zh.git
oschina-mirror
wizardforcel-sicp-py-zh
wizardforcel-sicp-py-zh
master