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

how to tell Python that we always want to interpret an object of type Foo as an object of type Bar when there are conflicts?

我是新手,所以请原谅非标准术语,让我知道是否应该添加代码以使这个问题更清楚。

假设我们尝试在 Python 中制作 class "Rational"。 (我知道已经内置了一个,但为了这个问题的目的而忽略它。)

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

其中 ab 是有理数。

现在,可能会发生这样的情况,在其他地方,有人想要计算 a + b,其中 a 是有理数,而 b 是整数。我们可以通过修改 Rational class 中的 __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 时有效。首先 argument 仍然必须是 Rational;没有办法写一个 Rational class 中的方法允许我们添加 a + b 其中 a 是 int 和 be 是有理数。
  2. 这是重复的。我们真正想要的是一些技术能够 以某种方式在全球范围内说一次,“每当你试图做一个 对多个对象的操作,其中一些是有理数,一些 其中是整数,通过映射 n 将整数视为有理数 到有理数 (n, 1)."

有这种技术吗? (我标记了这个强制转换,因为我认为这就是其他上下文中所谓的强制转换,但我的理解是 Python 中不推荐使用强制转换。)

您可以通过在 class 的初始值设定项中进行映射来避免重复。这是一个处理整数的简单演示。正确处理 float 将留作 reader 的练习。 :) 但是,我 已经 展示了如何轻松实现 __radd____iadd__,这是处理+=.

我的代码保留了您代码中的 rational 作为 class 名称,尽管 Python 中的 class 名称通常是 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 方法供内部使用;通常的惯例是在这些名称前加上一个下划线。

您可以创建自己的 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)