Как сказать Python, что мы всегда хотим интерпретировать объект типа Foo как объект типа Bar при возникновении конфликтов?

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

Допустим, мы пытаемся сделать класс "Rational" в Python. (Я знаю, что он уже встроен, но для целей этого вопроса проигнорируйте.)

Мы можем использовать __add__ а также __mul__например, научить Python интерпретировать код вида a + b или же a * b,

где a а также b Рациональные

Теперь может случиться так, что где-то еще кто-то хочет вычислить a + b, где a рационально, но b является целым числом Это мы можем сделать, изменив наш __add__ код в классе Rational для включения оператора if, например,

def __add__(self, b):
    if isinstance(b, int):
        brat = rational(b, 1)
        return self + brat
    else:
        y = rational(self.num*b.den + b.num*self.den , b.den*self.den)
        y = y.lowest_terms()
        return y

Мы можем аналогичным образом изменить наш __mul__ код, наш __div__ код и т. д. Но с таким решением есть как минимум две проблемы:

  1. Это работает только тогда, когда второй аргумент int, Первым аргументом все еще должен быть Rational; нет никакого способа написать метод в классе Rational, который позволяет нам добавлять a + b где a является int, а be является рациональным.
  2. Это повторяется. Что мы действительно хотим, так это чтобы какой-то метод мог сказать один раз, глобально, каким-то образом: "всякий раз, когда вы пытаетесь выполнить операцию над несколькими объектами, некоторые из которых являются Rational, а некоторые целыми, обрабатывайте целые числа как Rational как отображение n в Rational(n, 1)."

Существует ли такая техника? (Я отметил это принуждение, потому что я думаю, что это то, что называется принуждением в других контекстах, но я понимаю, что принуждение в Python устарело.)

2 ответа

Решение

Вы можете избежать повторения, выполнив сопоставление в инициализаторе класса. Вот простая демонстрация, которая обрабатывает целые числа. обращение float Правильно будет оставлено в качестве упражнения для читателя.:) Однако я показал, как легко реализовать __radd__, а также __iadd__, который является магическим методом (он же метод Дандер), который обрабатывает +=,

Мой код сохраняет rational из вашего кода в качестве имени класса, хотя имена классов в Python обычно CamelCase.

def gcd(a, b):
    while b > 0:
        a, b = b, a%b
    return a

class rational(object):
    def __init__(self, num, den=1):
        if isinstance(num, rational):
            self.copy(num)
        else:
            self.num = num
            self.den = den

    def copy(self, other):
        self.num = other.num 
        self.den = other.den

    def __str__(self):
        return '{0} / {1}'.format(self.num, self.den)

    def lowest_terms(self):
        g = gcd(self.num, self.den)
        return rational(self.num // g, self.den // g)

    def __add__(self, other):
        other = rational(other)
        y = rational(self.num*other.den + other.num*self.den, other.den*self.den)
        return y.lowest_terms()

    def __radd__(self, other):
        return rational(other) + self

    def __iadd__(self, other):
        self.copy(self + rational(other))
        return self


a = rational(1, 4)
b = rational(2, 5)
c = a + b
print a
print b
print c
print c + 5
print 10 + c
c += 10
print c

выход

1 / 4
2 / 5
13 / 20
113 / 20
213 / 20
213 / 20

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

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

# This is in the 'mm' module

_registry = {}

class MultiMethod(object):
    def __init__(self, name):
        self.name = name
        self.typemap = {}
    def __call__(self, *args):
        types = tuple(arg.__class__ for arg in args)
        function = self.typemap.get(types)
        if function is None:
            raise TypeError("no match")
        return function(*args)
    def register(self, types, function):
        if types in self.typemap:
            raise TypeError("duplicate registration")
        print('registering: {!r} for args: {}'.format(function.__name__, types))
        self.typemap[types] = function

def multimethod(*types):
    def register(function):
        name = function.__name__
        mm = _registry.get(name)
        if mm is None:
            mm = _registry[name] = MultiMethod(name)
        mm.register(types, function)
        return mm
    return register

def associative_multimethod(*types):
    def register(function):
        name = function.__name__
        mm = _registry.get(name)
        if mm is None:
            mm = _registry[name] = MultiMethod(name)
        mm.register(types[::-1], lambda a, b: function(b, a))
        mm.register(types, function)
        return mm
    return register

Это позволит вам написать код так:

from mm import associative_multimethod, multimethod

class Rational(object):
    pass

@multimethod(int, int)
def foo(a, b):
    print('...code for two ints...')

@associative_multimethod(int, Rational)
def foo(a, b):
    print('...code for int and Rational...')

@multimethod(Rational, Rational)
def foo(a, b):
    print('...code two Rationals...')

a, b, c, d = 1, 2, Rational(), Rational()

foo(a, b)
foo(a, c)
foo(c, a)
foo(c, d)
Другие вопросы по тегам