我知道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
12 回答
当你提出它时,Python确实支持“方法重载” . 事实上,你刚才描述的内容在Python中以很多不同的方式实现是微不足道的,但我会选择:
在上面的代码中,
default
是这些参数的合理默认值,或None
. 然后,您可以仅使用您感兴趣的参数调用该方法,Python将使用默认值 .你也可以这样做:
另一种方法是直接将所需函数挂接到类或实例:
另一种方法是使用抽象工厂模式:
您要求的是多次调度 . 请参阅Julia语言示例,它们演示了不同类型的调度 .
但是,在看这个之前,我们首先要解决为什么在python中你不想要重载的原因 .
为什么不重载?
首先需要了解重载的概念以及为什么它不适用于python .
Python是一种dynamically类型的语言,因此重载的概念根本不适用于它 . 但是,所有这些都不会丢失,因为我们可以在运行时创建这样的替代函数:
所以我们应该能够在python中执行多方法,或者,或者称为多方式调度 .
多次派遣
多方法也称为多次派遣:
Python不支持开箱即用1 . 但是,正如它发生的那样,有一个名为multipledispatch的优秀python包正是如此 .
解决方案
以下是我们如何使用multipledispatch 2包来实现您的方法:
2.注意不要在多线程环境中使用multipledispatch,否则会产生奇怪的行为 .
您可以使用"roll-your-own"解决方案进行功能重载 . 这个是从Guido van Rossum's article复制的关于multimethods(因为在python中mm和重载之间几乎没有区别):
用法是
目前最严格的限制是:
不支持
方法,只支持非类成员的函数;
继承未得到处理;
kwargs不受支持;
注册新函数应该在导入时完成,事情不是线程安全的
一个可能的选择是使用multipledispatch模块,如下所示:http://matthewrocklin.com/blog/work/2014/02/25/Multiple-Dispatch
而不是这样做:
你可以这样做:
由此产生的用法:
在Python 3.4中添加了PEP-0443. Single-dispatch generic functions .
以下是PEP的简短API描述 .
要定义泛型函数,请使用@singledispatch装饰器进行装饰 . 请注意,调度发生在第一个参数的类型上 . 相应地创建您的功能:
要向函数添加重载实现,请使用泛型函数的register()属性 . 这是一个装饰器,它接受一个类型参数并装饰一个实现该操作的函数对于那种类型:
通常使用多态性来解决这种类型的行为(在OOP语言中) . 每种类型的子弹都有责任了解它的行进方式 . 例如:
将尽可能多的参数传递给存在的c_function,然后根据初始c函数中的值确定要调用哪个c函数 . 所以,python应该只调用一个c函数 . 一个c函数查看参数,然后可以适当地委托给其他c函数 .
您基本上只是将每个子类用作不同的数据容器,但是通过定义基类上的所有潜在参数,子类可以自由地忽略它们不做任何操作的子类 .
当一种新类型的子弹出现时,您可以简单地在基础上定义一个属性,更改一个python函数以便它传递额外属性,以及一个c_function来检查参数和委托 . 我觉得听起来不太糟糕 .
由passing keyword args .
我认为你的基本要求是在python中使用类似C / C的语法,并且最不可能 . 虽然我喜欢Alexander Poluektov的答案,但它不适用于课程 .
以下应适用于课程 . 它的工作原理是区分非关键字参数的数量(但不支持按类型区分):
它可以像这样使用:
输出:
在定义中使用多个关键字参数,或者创建一个实例传递给函数的
Bullet
层次结构 .我认为具有相关多态性的
Bullet
类层次结构是可行的方法 . 您可以通过使用元类有效地重载基类构造函数,以便调用基类导致创建适当的子类对象 . 下面是一些示例代码来说明我的意思的本质 .Updated
代码已被修改为在Python 2和3下运行以保持相关性 . 这样做是为了避免使用Python的显式元类语法,这种语法在两个版本之间有所不同 .
为了实现该目标,
BulletMeta
类的BulletMetaBase
实例是通过在创建Bullet
基类时显式调用元类而创建的(而不是使用__metaclass__=
类属性或通过metaclass
关键字参数,具体取决于Python版本) .输出:
使用带有默认值的关键字参数 . 例如 .
在直子弹与弯曲子弹的情况下,我将添加两个函数:
add_bullet_straight
和add_bullet_curved
.在python中重载方法很棘手 . 但是,可以使用传递dict,list或原始变量 .
我已经为我的用例尝试了一些东西,这可以帮助理解人们重载方法 .
我们举个例子:
一个类重载方法,调用来自不同类的方法 .
从远程类传递参数:
要么
因此,正在从方法重载实现列表,字典或原始变量的处理 .
试试你的代码吧 .