在Python中,有没有像C++一样方便的方法重载方法?
In Python, is there a convenient way to overload methods like in C++?
例如,假设我们有一个实现矩阵的矩阵 class。
我们希望能够使用“+”运算符将 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 之间最好的是什么?也许是品味问题。
不过我在原文post中隐约提到过,也可以使用装饰器。特别是,链接的主题提到了 重载模块 ,它提供了一个 API 类似于带有 @overload
装饰器的 C++ 重载。参见 PEP3124。
它看起来确实很方便(这就是技巧 #5)!
不,如果您在同一范围内多次声明一个方法,则使用最后一个定义。即使您有不同数量的参数和相同的函数定义,也只会使用最后一个。
这是由函数名决定的
python 中类型检查的替代方法是“鸭式输入”。 “鸭子打字”一词的起源是“如果它看起来像鸭子,走路像鸭子,叫声也像鸭子,那么它可能是鸭子。”
因此,如果我有一个 Vector
class,代表一个二维欧几里德向量:
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
对象,它也有 x
和 y
属性,我可以将它添加到我的 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
一样嘎嘎作响的对象(即任何具有 x
和 y
属性的对象)或 int
s,float
s,或任何可以明智地添加到属性的对象,因此如果有人试图添加 Decimal
.
,它将“正常工作”
在真正的实现中,应该多做异常捕获,生成清晰的错误信息,还应该实现__radd__
方法等
例如,假设我们有一个实现矩阵的矩阵 class。
我们希望能够使用“+”运算符将 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 之间最好的是什么?也许是品味问题。
不过我在原文post中隐约提到过,也可以使用装饰器。特别是,链接的主题提到了 重载模块 ,它提供了一个 API 类似于带有 @overload
装饰器的 C++ 重载。参见 PEP3124。
它看起来确实很方便(这就是技巧 #5)!
不,如果您在同一范围内多次声明一个方法,则使用最后一个定义。即使您有不同数量的参数和相同的函数定义,也只会使用最后一个。
这是由函数名决定的
python 中类型检查的替代方法是“鸭式输入”。 “鸭子打字”一词的起源是“如果它看起来像鸭子,走路像鸭子,叫声也像鸭子,那么它可能是鸭子。”
因此,如果我有一个 Vector
class,代表一个二维欧几里德向量:
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
对象,它也有 x
和 y
属性,我可以将它添加到我的 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
一样嘎嘎作响的对象(即任何具有 x
和 y
属性的对象)或 int
s,float
s,或任何可以明智地添加到属性的对象,因此如果有人试图添加 Decimal
.
在真正的实现中,应该多做异常捕获,生成清晰的错误信息,还应该实现__radd__
方法等