如何告诉Python我们总是希望在存在冲突时将类型为Foo的对象解释为Bar类型的对象?

时间:2015-10-25 13:33:08

标签: python coercion

我是新手,请原谅非标准术语,如果我应该添加代码以便让这个问题更清楚,请告诉我。

让我们说我们尝试上课#34; Rational"在Python中。 (我知道其中一个已经内置,但为了这个问题的目的而忽略了它。)

例如,我们可以使用__add____mul__来教授Python如何解释a + ba * b形式的代码,

其中ab是Rational。

现在,可能会发生这样的情况,在其他地方,人们想要计算a + b,其中a是理性的,但b是整数。我们可以通过修改Rational类中的__add__代码来包含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类中允许我们在其中添加a + b的方法 一个int和be是一个Rational。
  2. 这是重复的。我们真正想要的是一些技术能够 在某种程度上,在某种程度上,只要你想要做一次,就说一次 对多个对象的操作,其中一些是Rational和一些 其中是整数,通过映射n将整数视为Rational 到Rational(n,1)。"
  3. 这种技术存在吗? (我已经标记了这种强制,因为我认为这就是其他语境中的强制行为,但我的理解是在Python中不推荐使用强制行为。)

2 个答案:

答案 0 :(得分:2)

您可以通过在类初始化程序中进行映射来避免重复。这是一个处理整数的简单演示。正确处理float将留给读者练习。 :)但是,我已经展示了如何轻松实现__radd____iadd__,这是处理{{1}的神奇方法(又名dunder方法) }}

我的代码保留代码中的+=作为类名,即使Python中的类名通常是CamelCase。

rational

<强>输出

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 方法供内部使用;通常的惯例是在单个下划线前加上这些名称。

答案 1 :(得分:1)

不是强制参数,而是一种更通用的方法,你可以创建自己的 multimethods 模块,类似于几年前写的标题为Five-minute Multimethods in Python的文章中描述的模块。 Guido van Rossum。这样可以避免大量重复代码。这是一个增强版本,支持“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)