在 Python 中,是不是有一种方便的方法来重载 C++ 中的方法? [复制]

Posted

技术标签:

【中文标题】在 Python 中,是不是有一种方便的方法来重载 C++ 中的方法? [复制]【英文标题】:In Python, is there a convenient way to overload methods like in C++? [duplicate]在 Python 中,是否有一种方便的方法来重载 C++ 中的方法? [复制] 【发布时间】:2021-02-04 15:15:28 【问题描述】:

例如,假设我们有一个实现矩阵的 Matrix 类。

我们希望能够使用“+”运算符来添加 2 个矩阵,或者将 1 个矩阵与 1 个数字相加(这实际上将被定义为将数字添加到矩阵的每个元素中)。

在 C++ 中,我们可以做到(用 '...' 跳过细节):

class Matrix

    ...

    Matrix operator + (Matrix A, Matrix B)
    
        ... // process in some way
    

    Matrix operator + (Matrix A, double x)
    
        ... // process in some other way
    

但据我所知,在 Python 中,多个定义会覆盖之前的定义。

class Matrix:

    ...

    def __add__(A, B):
        ... # process in some way

    def __add__(A, x):
        ... # process in some other way

这不起作用:只有最后一个方法定义处于活动状态。

所以我能想到的第一个解决方案是只定义一个方法并解析参数(根据它们的类型、数量或关键字)。

例如,通过类型检查,我们可以做类似的事情(我们称之为技术#1):

class Matrix:
    
    ...

    def __add__(A, X):
        if isinstance(X, Matrix)
            ... # process in some way
        elif isinstance(X, (int, float, complex))
            ... # process in some other way
        else:
            raise TypeError("unsupported type for right operand")

但我经常读到 type-checking 不是“pythonic”,还有什么?

此外,在这种情况下,总是有两个参数,但更一般地说,如果我们希望能够处理不同数量的参数

为了清楚起见,假设我们有一个名为“mymethod”的方法,我们希望能够调用,比如:

mymethod(type1_arg1)
mymethod(type2_arg1)
mymethod(type3_arg1, type4_arg2)

我们也假设:

每个版本都处理同一个对象,所以它必须是一个唯一的方法, 每个处理都不同,参数类型很重要。

----------------------------------- - - - - - - 编辑 - - - - - - - - - - - - - - - - - - - --------------------

感谢大家对此话题的关注。

@thegreatemu 在下面提出了一个很好的类型检查替代方案(我们称之为技术#2)。它使用 duck-typing 范例和 exception 处理。但据我所知,它仅在所有情况下的参数数量相同时才有效。

根据我从此处和链接主题的答案中了解到的情况,当我们想要处理不同数量的参数时,我们可以使用关键字参数。这是一个例子(我们称之为技术#3)。

class Duration:
    """Durations in H:M:S format."""

    def __init__(hours, mins=None, secs=None):
        """Works with decimal hours or H:M:S format."""
        
        if mins and secs: # direct H:M:S
            ... # checks
            self.hours = hours
            self.mins = mins
            self.secs = secs

        else: # decimal
           ... # checks
           ... # convert decimal hours to H:M:S

或者我们也可以使用可变长度参数 *args(或**kwargs)。这是带有 *args 和 长度检查 的替代版本(我们称之为技术 #4)。

class Duration:
    """Durations in H:M:S format."""

    def __init__(*args): # direct H:M:S
        """Works with decimal hours or H:M:S format."""
        
        if len(args) == 3 # direct H:M:S format
            ... # checks
            self.hours = args[0]
            self.mins = args[1]
            self.secs = args[2]

        elif len(args) == 1: # decimal
           ... # checks
           ... # convert decimal hours to H:M:S
           
        else:
            raise TypeError("expected 1 or 3 arguments")

在这种情况下,技术#3 和#4 之间的最佳选择是什么?可能是口味问题。

但正如我在原帖中含糊提到的,也可以使用装饰器。特别是,链接的主题提到了重载模块,它提供了一个类似于 C++ 重载的 API,带有 @overload 装饰器。见PEP3124。 它看起来确实很方便(这就是技巧 #5)!​​

【问题讨论】:

您使用默认参数执行最后一部分。 def mymethod(self, arg1, arg2=defaultValue): 我使用 isinstance 并且实际上更喜欢它,因为它明确表明根据传入的对象类型有不同的功能。关于你的第二个问题,使用默认值或传入*args**kwargs “使用装饰器”作为解决方案的描述相当模糊。它基本上(几乎完全)等同于“使用功能”。无论如何,不​​,Python 没有方法重载。 @juanpa.arrivillaga 确实,我在阅读教程时遇到了不同的技术,但我添加了似乎对我的问题最友好的技术。 【参考方案1】:

在 python 中类型检查的替代方法是“duck typing”。 “鸭子打字”一词的由来是“如果它看起来像鸭子,走路像鸭子,叫起来像鸭子,那它可能就是鸭子”。

所以如果我有一个Vector 类,代表一个二维欧几里得向量:

class Vector(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        return Vector(self.x+other.x, self.y+other.y)

现在,如果我有一个Coordinate 对象,该对象也具有xy 属性,我可以将它添加到我的Vector 中,它的行为至少会与预期的一样。如果我尝试将Vector 添加到浮点数,我会得到AttributeError,在这种情况下,这就是我想要的,因为将标量添加到向量在数学上没有意义。但是假设我想做一些类似 numpy 的广播的事情,所以向向量添加一个标量会将它添加到 x 和 y。那么__add__ 方法会变成:

    def __add__(self, other):
        try:
            x, y = self.x + other.x, self.y + other.y
        except AttributeError:
            x, y = self.x + other, self.y + other
        return Vector(x, y)

现在我可以接受任何像Vector 一样嘎嘎作响的对象(即任何具有xy 属性的对象)或ints、floats 或任何可以明智的对象添加到属性中,因此如果有人尝试添加Decimal,它将“正常工作”。

在实际的实现中,你应该多做一些异常捕获来生成清晰的错误信息,还应该实现__radd__方法等。

【讨论】:

谢谢,这很有趣。虽然对我来说,使用这样的异常感觉有点 hacky。它在 Python 中是常见且推荐的做法吗?在这种情况下,与类型检查相比有什么好处吗? 我来自 C++,并认为 python 中的异常方法也很奇怪,但它似乎是最“pythonic”的处理方式。您可以等效地执行if hasattr(other, 'x') 等。类型检查的优势在于能够处理您没有想到的类。您的重载案例不适用于Decimals,但鸭子类型的方法可以 尽管它并没有真正为我们需要不同数量参数的情况提供解决方案,但这仍然是最有帮助的答案,所以我会将其标记为已接受。【参考方案2】:

不,如果您在同一范围内多次声明一个方法,则使用最后一个定义。即使您有不同数量的参数和相同的函数定义,也只会使用最后一个。

这由函数名决定。

【讨论】:

以上是关于在 Python 中,是不是有一种方便的方法来重载 C++ 中的方法? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

在 Swiftui 中是不是有一种简单的方法可以通过捏合来放大图像?

在 Pandas 中是不是有一种 pythonic 的方法来做一个列联表?

python 3中是不是有一种方法可以使两个用户定义的函数只需要输入一次

在 Django 中是不是有一种惯用的方法来编写不显眼的 JavaScript 和/或进行 AJAX 表单提交?

重载和重写有啥区别

TypeScript 中是不是有一种编译时方法来声明一个 keyof 类型,只有映射到特定类型的键? [复制]