如何在Python中通用使用sin、cos、tan(包括用户定义类型)?
编辑:让我试着重新表述并改进我的问题。旧版本在下面。
我想要的是一种表达和使用自由函数的方式,这种方式可以适用于不同类型的情况。举几个例子:
abs(x) # maps to x.__abs__()
next(x) # maps to x.__next__() at least in Python 3
-x # maps to x.__neg__()
在这些例子中,函数的设计允许用户使用自定义类型来调整它们的行为,通过调用一个非静态的方法来完成。这很好。它让我们可以编写一些函数,这些函数并不在乎具体的参数类型,只要这些参数“感觉”像是某个概念的对象就可以。
反例:那些不能轻松通用的函数:
math.exp # only for reals
cmath.exp # takes complex numbers
假设我想写一个通用函数,用于对一系列数字类对象应用指数函数exp。我应该用哪个exp函数呢?我该如何选择正确的?
def listexp(lst):
return [math.exp(x) for x in lst]
显然,这对复数列表是行不通的,尽管复数也有一个exp函数(在cmath中)。而且这对任何用户自定义的数字类类型也不适用,因为它们可能有自己特殊的exp函数。
所以,我想要的是一种方法,可以在这两方面都处理这个问题——理想情况下,不需要特别处理很多情况。作为一个不在乎参数具体类型的通用函数的编写者,我希望能够使用与涉及的类型相关的正确数学函数,而不必显式处理这个问题。作为一个用户自定义类型的编写者,我希望能够暴露出特殊的数学函数,这些函数经过增强,可以处理存储在这些对象中的额外数据(类似于复数的虚部)。
那么,做这件事的首选模式/协议/习惯是什么?我还没有测试numpy
。但我下载了它的源代码。就我所知,它为数组提供了一个sin函数。不幸的是,我还没有在源代码中找到它的实现。不过,看看他们是如何为数组当前存储的数字类型选择正确的sin函数的,还是很有趣的。
在C++中,我会依赖函数重载和ADL(参数依赖查找)。由于C++是静态类型的,因此这种(名称查找、重载解析)完全在编译时处理,这并不奇怪。我想,我可以在运行时用Python和Python提供的反射工具模拟这一点。但我也知道,把一种编码风格引入另一种语言可能不是个好主意,并且在新语言中可能不太符合习惯。所以,如果你有其他的想法,我非常乐意听取。
我想,在某个时刻,我需要以可扩展的方式手动进行一些依赖类型的调度。也许可以写一个“tgmath”(类型通用数学)模块,支持实数和复数,并允许其他人注册他们的类型和特殊函数……你们怎么看?Python大师们对此有什么看法?
谢谢!
编辑:显然,我并不是唯一一个对通用函数和依赖类型的重载感兴趣的人。还有PEP 3124,但它已经草拟了四年。
问题的旧版本:
我在Java和C++方面有很强的背景,最近刚开始学习Python。我想知道的是:我们如何扩展数学函数(至少是它们的名称),使它们能够在其他用户自定义类型上工作?这些函数是否提供了我可以利用的扩展点/钩子(类似于迭代器协议,其中next(obj)
实际上委托给obj.__next__
等)?
在C++中,我只需用新的参数类型重载函数,让编译器根据参数表达式的静态类型来判断哪个函数是要调用的。但由于Python是一个非常动态的语言,因此没有重载的概念。在Python中,做这件事的首选方式是什么?
此外,当我编写自定义函数时,我希望避免长长的链式调用:
if isinstance(arg,someClass):
suchandsuch
elif ...
我可以使用什么模式来让代码看起来更漂亮,更符合Python的风格?
我想,我基本上是在试图解决Python中缺乏函数重载的问题。至少在C++中,重载和参数依赖查找是良好C++风格的重要组成部分。
是否可以让
x = udt(something) # object of user-defined type that represents a number
y = sin(x) # how do I make this invoke custom type-specific code for sin?
t = abs(x) # works because abs delegates to __abs__() which I defined.
工作?我知道我可以把sin做成类的非静态方法。但那样我就失去了通用性,因为对于其他类型的数字对象来说,调用方式是sin(x)
而不是x.sin()
。
添加一个__float__
方法是不可接受的,因为我在对象中保留了额外的信息,比如“自动微分”的导数。
谢谢!
编辑:如果你对代码的样子感兴趣,可以看看这个。在理想的情况下,我希望能够以类型通用的方式使用sin/cos/sqrt。我认为这些函数是对象接口的一部分,即使它们是“自由函数”。在__somefunction
中,我没有用math.
或__main__.
来限定函数。它之所以能工作,是因为我在自定义函数中通过装饰器手动回退到math.sin
(等等)。但我认为这是一种不太优雅的解决方案。
5 个回答
在一个模块里定义你自己的版本。这就像在cmath库中为复杂数字做的那样,以及在numpy库中为数组做的那样。
如果你想让 math.sin()
这个函数返回你自己定义的类型,看来你可能要失望了。因为 Python 的 math
库其实是一个快速的本地浮点数学库的简单封装,遵循的是 IEEE 754 标准。如果你想在代码内部保持一致性,并且使用鸭子类型(即根据对象的行为而不是类型来判断),你至少可以在自己的代码中添加一些扩展功能,弥补 Python 的不足。
def sin(x):
try:
return x.__sin__()
except AttributeError:
return math.sin(x)
现在你可以导入这个 sin
函数,并在你之前使用 math.sin
的地方随意使用它。虽然这没有像 math.sin
自动识别你的鸭子类型那么方便,但至少在你的代码中可以保持一致。
你可以这样做,但它是反向的。你需要在你新创建的类型里实现 __float__()
方法,然后 sin()
函数就能和你的类一起工作了。
换句话说,你不是让正弦函数适应其他类型,而是让这些类型适应正弦函数。
这样做更好,因为它能保持一致性。如果你的对象和浮点数之间没有明显的对应关系,那可能就没有合理的方式来理解这个类型的 sin()
函数。
[抱歉如果我之前漏掉了“__float__ 不起作用”的部分;也许你是为了回应这个才加上的?无论如何,想要证明你想要的事情是不可能的,Python 有 cmath 库,可以为复数添加 sin()
等函数...]