Как сказать 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__
код и т. д. Но с таким решением есть как минимум две проблемы:
- Это работает только тогда, когда второй аргумент
int
, Первым аргументом все еще должен быть Rational; нет никакого способа написать метод в классе Rational, который позволяет нам добавлятьa + b
где a является int, а be является рациональным. - Это повторяется. Что мы действительно хотим, так это чтобы какой-то метод мог сказать один раз, глобально, каким-то образом: "всякий раз, когда вы пытаетесь выполнить операцию над несколькими объектами, некоторые из которых являются 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)