2015-10-25 4 views
1

Я новичок, поэтому, пожалуйста, извините нестандартную терминологию и дайте мне знать, если я должен добавить код, чтобы сделать этот вопрос более ясным.Как сказать Python, что мы всегда хотим интерпретировать объект типа Foo как объект типа Bar при конфликтах?

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

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

, где a и b - Rationals.

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

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 - это Rational.
  2. Это повторяющийся. Мы действительно хотим, чтобы какой-то метод мог сказать один раз, глобально, в некотором роде, «всякий раз, когда вы пытаетесь выполнить операцию на нескольких объектах, некоторые из которых являются Rationals, а некоторые являются целыми числами, обрабатывают целые числа как Rationals путем сопоставления n с Rational (n, 1). "

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

+2

комментарий на 1 .: есть ['__radd__()'] (https://docs.python.org/2/reference/datamodel.html#object.__radd__) (и аналогичный), который вы можете реализовать для своих «Rationals» таким образом, что «integer + rational» будет работать. –

ответ

2

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

Мой код сохраняет 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 метод для внутреннего использования; обычное соглашение состоит в том, чтобы добавлять такие имена с одним подчеркиванием.

+0

'dunder' ?! никогда не слышал об этом. любить это! –

1

Вместо принуждать аргументы, более общий подход был бы для вас могли бы сделать создать свой собственный мультиметоды модуль, аналогичный тому, который описан в статье под названием Five-minute Multimethods in Python написал несколько лет назад Гвидо ван Россум. Это позволит избежать много повторяющегося кода. Вот версия ней усовершенствован для поддержки «associative_multimethod» функции, которые принимают свои аргументы в обратном порядке:

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