Python函数重载
我知道 Python 不支持方法重载,但我遇到了一个问题,我似乎无法以一种很好的 Pythonic 方式解决它。
我正在制作一个游戏,其中一个角色需要射击各种子弹,但是如何编写不同的函数来创建这些子弹?例如,假设我有一个函数,可以创建一颗以给定速度从 A 点移动到 B 点的子弹。我会编写这样的函数:
def add_bullet(sprite, start, headto, speed):
# Code ...
但我想编写其他函数来创建项目符号,例如:
def add_bullet(sprite, start, direction, speed):
def add_bullet(sprite, start, headto, spead, acceleration):
def add_bullet(sprite, script): # For bullets that are controlled by a script
def add_bullet(sprite, curve, speed): # for bullets with curved paths
# And so on ...
等等,有很多变体。有没有更好的方法来做到这一点而不使用这么多关键字参数导致它变得有点难看快。重命名每个函数也非常糟糕,因为您会得到 add_bullet1
、add_bullet2
或 add_bullet_with_really_long_name
。
要解决一些问题:
不,我无法创建 Bullet 类层次结构,因为那太慢了。管理项目符号的实际代码是用 C 编写的,我的函数是 C API 的包装器。
我了解关键字参数,但检查各种参数组合很烦人,但默认参数有助于分配,例如
acceleration=0
I know that Python does not support method overloading, but I've run into a problem that I can't seem to solve in a nice Pythonic way.
I am making a game where a character needs to shoot a variety of bullets, but how do I write different functions for creating these bullets? For example suppose I have a function that creates a bullet travelling from point A to B with a given speed. I would write a function like this:
def add_bullet(sprite, start, headto, speed):
# Code ...
But I want to write other functions for creating bullets like:
def add_bullet(sprite, start, direction, speed):
def add_bullet(sprite, start, headto, spead, acceleration):
def add_bullet(sprite, script): # For bullets that are controlled by a script
def add_bullet(sprite, curve, speed): # for bullets with curved paths
# And so on ...
And so on with many variations. Is there a better way to do it without using so many keyword arguments cause its getting kinda ugly fast. Renaming each function is pretty bad too because you get either add_bullet1
, add_bullet2
, or add_bullet_with_really_long_name
.
To address some answers:
No I can't create a Bullet class hierarchy because thats too slow. The actual code for managing bullets is in C and my functions are wrappers around C API.
I know about the keyword arguments but checking for all sorts of combinations of parameters is getting annoying, but default arguments help allot like
acceleration=0
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(21)
根据定义,在 python 中重载函数是不可能的(请继续阅读以了解详细信息),但是您可以使用简单的装饰器实现类似的功能
您可以像这样使用它
修改它使其适应您的用例。
this
参数执行此操作。上面的装饰器将这个想法扩展到了多个参数。为了澄清这一点,假设我们用假设的静态语言定义了函数,
通过静态分派(重载),您将看到“number called”两次,因为
x
已被声明为Number< /code>,这就是重载所关心的。使用动态调度,您将看到“调用整数,调用浮点”,因为这些是调用函数时
x
的实际类型。It is impossible by definition to overload a function in python (read on for details), but you can achieve something similar with a simple decorator
You can use it like this
Modify it to adapt it to your use case.
self/this
argument. However, most languages only do it for thethis
argument only. The above decorator extends the idea to multiple parameters.To clear up, assume that we define, in a hypothetical static language, the functions
With static dispatch (overloading) you will see "number called" twice, because
x
has been declared asNumber
, and that's all overloading cares about. With dynamic dispatch you will see "integer called, float called", because those are the actual types ofx
at the time the function is called.Python 3.8 添加了 functools.singledispatchmethod
输出
输出:
Python 3.8 added functools.singledispatchmethod
Output
Output:
通过传递关键字参数。
By passing keyword args.
您可以轻松地在Python中实现函数重载。下面是一个使用
floats
和integers
的示例:代码背后的主要思想是一个类包含您想要实现的不同(重载)函数,以及一个字典作为
路由器
工作,根据输入type(x)
将您的代码引导至正确的函数。PS1。对于自定义类,例如
Bullet1
,您可以按照类似的模式初始化内部字典,例如self.D = {Bullet1: self.f_Bullet1, ...}
。其余代码是相同的。PS2。所提出的解决方案的时间/空间复杂度也很好,每个操作的平均成本为
O(1)
。You can easily implement function overloading in Python. Here is an example using
floats
andintegers
:The main idea behind the code is that a class holds the different (overloaded) functions that you would like to implement, and a Dictionary works as a
router
, directing your code towards the right function depending on the inputtype(x)
.PS1. In case of custom classes, like
Bullet1
, you can initialize the internal dictionary following a similar pattern, such asself.D = {Bullet1: self.f_Bullet1, ...}
. The rest of the code is the same.PS2. The time/space complexity of the proposed solution is good as well, with an average cost of
O(1)
per operation.我认为你的基本要求是在 Python 中拥有类似 C/C++ 的语法,并且尽可能减少头痛。虽然我喜欢 Alexander Poluektov 的答案,但它不适用于课程。
以下应该适用于课程。它的工作原理是通过非关键字参数的数量来区分(但它不支持通过类型来区分):
并且可以像这样简单地使用:
输出:
I think your basic requirement is to have a C/C++-like syntax in Python with the least headache possible. Although I liked Alexander Poluektov's answer it doesn't work for classes.
The following should work for classes. It works by distinguishing by the number of non-keyword arguments (but it doesn't support distinguishing by type):
And it can be used simply like this:
Output:
您可以使用以下 Python 代码来实现此目的:
You can achieve this with the following Python code:
在定义中使用多个关键字参数,或者创建一个
Bullet
层次结构,将其实例传递给函数。Either use multiple keyword arguments in the definition, or create a
Bullet
hierarchy whose instances are passed to the function.如何在Python中重载?
我知道这是一个老问题,但这个主题仍然非常相关,而且我还没有读过一个清晰简洁的答案,所以我想我自己提供一个。
首先,安装包:
然后,使用包中的
overload
装饰器来定义函数的多个实现:参数的数量和类型决定调用哪个版本的函数。
在这里您可以找到完整的文档。
How to overload in python?
I know this is an old question, but the topic is still very relevant, and I haven't read a single clear and concise answer, so I thought I'd provide one myself.
First, install the package:
Then, use the
overload
decorator from the package to define multiple implementations of the function:The number and the types of the arguments determine which version of the function is called.
Here you can find the complete documentation.
我认为具有关联多态性的 Bullet 类层次结构是正确的选择。您可以使用元类有效地重载基类构造函数,以便调用基类导致创建适当的子类对象。下面是一些示例代码来说明我的意思的本质。
已更新
代码已修改为在 Python 2 和 3 下运行,以保持相关性。这样做的方式避免了使用 Python 的显式元类语法,该语法在两个版本之间有所不同。
为了实现该目标,在创建
Bullet
基类时显式调用元类(而不是使用__metaclass__=
类属性或通过metaclass
关键字参数,具体取决于 Python 版本)。输出:
I think a
Bullet
class hierarchy with the associated polymorphism is the way to go. You can effectively overload the base class constructor by using a metaclass so that calling the base class results in the creation of the appropriate subclass object. Below is some sample code to illustrate the essence of what I mean.Updated
The code has been modified to run under both Python 2 and 3 to keep it relevant. This was done in a way that avoids the use Python's explicit metaclass syntax, which varies between the two versions.
To accomplish that objective, a
BulletMetaBase
instance of theBulletMeta
class is created by explicitly calling the metaclass when creating theBullet
baseclass (rather than using the__metaclass__=
class attribute or via ametaclass
keyword argument depending on the Python version).Output:
使用带有默认值的关键字参数。例如,
在直子弹与弯子弹的情况下,我会添加两个函数:
add_bullet_straight
和add_bullet_curved
。Use keyword arguments with defaults. E.g.
In the case of a straight bullet versus a curved bullet, I'd add two functions:
add_bullet_straight
andadd_bullet_curved
.在 Python 中重载方法很棘手。然而,可以使用传递字典、列表或原始变量。
我已经为我的用例尝试了一些方法,这可以帮助理解人们重载这些方法。
让我们举个例子:
一个类重载方法,调用不同类的方法。
传递来自远程类的参数:
或
因此,通过方法重载实现了对列表、字典或基元变量的处理。
尝试一下您的代码。
Overloading methods is tricky in Python. However, there could be usage of passing the dict, list or primitive variables.
I have tried something for my use cases, and this could help here to understand people to overload the methods.
Let's take your example:
A class overload method with call the methods from different class.
Pass the arguments from the remote class:
Or
So, handling is being achieved for list, Dictionary or primitive variables from method overloading.
Try it out for your code.
Plum 以简单的 Python 方式支持它。从下面的自述文件中复制示例。
Plum supports it in a straightforward pythonic way. Copying an example from the README below.
我的解决方案
My solution
从 Python 3.10 开始,您可以使用 match-case 语句来执行此操作。
就像这样:
只需确保在元组或列表中输入参数即可。像这样:
From Python 3.10, you can use the match-case statement to do this.
Like so:
Just make sure that you input your arguments in a tuple or list. Like this:
您所要求的称为多重调度。请参阅 Julia 语言示例,其中演示了不同类型的调度。
然而,在了解这一点之前,我们首先要解决为什么重载在 Python 中并不是您真正想要的。
为什么不超载?
首先,需要了解重载的概念以及为什么它不适用于 Python。
Python 是一个 动态类型语言,所以重载的概念根本不存在适用于它。然而,一切并没有丢失,因为我们可以在运行时创建这样的替代函数:
所以我们应该能够在 Python 中执行多种方法——或者,也称为:多重调度。
多重分派
多重方法也称为多重分派:
Python 不支持开箱即用1,但是,碰巧有一个名为 multipledispatch 的优秀 Python 包可以做到这一点。
解决方案
以下是我们如何使用 multipledispatch2 包来实现您的方法:
What you are asking for is called multiple dispatch. See Julia language examples which demonstrates different types of dispatches.
However, before looking at that, we'll first tackle why overloading is not really what you want in Python.
Why Not Overloading?
First, one needs to understand the concept of overloading and why it's not applicable to Python.
Python is a dynamically typed language, so the concept of overloading simply does not apply to it. However, all is not lost, since we can create such alternative functions at run-time:
So we should be able to do multimethods in Python—or, as it is alternatively called: multiple dispatch.
Multiple dispatch
The multimethods are also called multiple dispatch:
Python does not support this out of the box1, but, as it happens, there is an excellent Python package called multipledispatch that does exactly that.
Solution
Here is how we might use multipledispatch2 package to implement your methods:
正如您所介绍的,Python 确实支持“方法重载”。事实上,你刚才描述的内容在 Python 中以多种不同的方式实现是微不足道的,但我会选择:
在上面的代码中,
default
是这些参数的合理默认值,或者 <代码>无。然后,您可以仅使用您感兴趣的参数调用该方法,Python 将使用默认值。您还可以这样做:
另一种选择是将所需的函数直接挂钩到类或实例:
另一种方法是使用抽象工厂模式:
Python does support "method overloading" as you present it. In fact, what you just describe is trivial to implement in Python, in so many different ways, but I would go with:
In the above code,
default
is a plausible default value for those arguments, orNone
. You can then call the method with only the arguments you are interested in, and Python will use the default values.You could also do something like this:
Another alternative is to directly hook the desired function directly to the class or instance:
Yet another way is to use an abstract factory pattern:
您可以使用“自己动手”的解决方案来实现函数重载。这篇文章是从 Guido van Rossum 的关于多方法的文章复制的(因为有Python 中的多方法和重载之间差别不大):
不
目前最严格的限制是:
You can use "roll-your-own" solution for function overloading. This one is copied from Guido van Rossum's article about multimethods (because there is little difference between multimethods and overloading in Python):
The usage would be
Most restrictive limitations at the moment are:
一个可能的选择是使用 multipledispatch 模块,如下所示:
http://matthewrocklin.com/blog/work/2014/02/25 /Multiple-Dispatch
使用
您可以这样做:
结果用法:
A possible option is to use the multipledispatch module as detailed here:
http://matthewrocklin.com/blog/work/2014/02/25/Multiple-Dispatch
Instead of doing this:
You can do this:
With the resulting usage:
在 Python 3.4 PEP-0443 中。添加了单调度通用函数。
以下是 PEP 的简短 API 描述。
要定义通用函数,请使用
@singledispatch
装饰器对其进行装饰。请注意,分派发生在第一个参数的类型上。相应地创建函数:要向函数添加重载实现,请使用通用函数的 register() 属性。这是一个装饰器,采用类型参数并装饰实现该类型操作的函数:
In Python 3.4 PEP-0443. Single-dispatch generic functions was added.
Here is a short API description from PEP.
To define a generic function, decorate it with the
@singledispatch
decorator. Note that the dispatch happens on the type of the first argument. Create your function accordingly:To add overloaded implementations to the function, use the register() attribute of the generic function. This is a decorator, taking a type parameter and decorating a function implementing the operation for that type:
添加了
@overload
装饰器带有类型提示(PEP 484)。虽然这不会改变 Python 的行为,但它确实使理解正在发生的事情以及 mypy 检测错误变得更容易。
请参阅:类型提示 和 PEP 484
The
@overload
decorator was added with type hints (PEP 484).While this doesn't change the behaviour of Python, it does make it easier to understand what is going on, and for mypy to detect errors.
See: Type hints and PEP 484
这种类型的行为通常可以使用多态性来解决(在OOP语言中) 。每种类型的子弹都有责任了解它的行进方式。例如:
将尽可能多的参数传递给存在的c_function,然后根据初始c函数中的值确定要调用哪个c函数。因此,Python 应该只调用一个 c 函数。该 C 函数查看参数,然后可以适当地委托给其他 C 函数。
本质上,您只是将每个子类用作不同的数据容器,但是通过在基类上定义所有潜在的参数,子类可以自由地忽略它们不执行任何操作的参数。
当出现一种新类型的项目符号时,您只需在基础上再定义一个属性,更改一个 python 函数以使其传递额外的属性,以及一个适当检查参数和委托的 c_function 即可。我想听起来还不错。
This type of behaviour is typically solved (in OOP languages) using polymorphism. Each type of bullet would be responsible for knowing how it travels. For instance:
Pass as many arguments to the c_function that exist, and then do the job of determining which c function to call based on the values in the initial c function. So, Python should only ever be calling the one c function. That one c function looks at the arguments, and then can delegate to other c functions appropriately.
You're essentially just using each subclass as a different data container, but by defining all the potential arguments on the base class, the subclasses are free to ignore the ones they do nothing with.
When a new type of bullet comes along, you can simply define one more property on the base, change the one python function so that it passes the extra property, and the one c_function that examines the arguments and delegates appropriately. It doesn't sound too bad I guess.