我知道Python不支持方法重载,但我遇到了一个问题,我似乎无法用Python的好方法来解决。
我正在创造一款角色需要射击各种子弹的游戏,但是我该如何编写不同的函数去创造这些子弹呢?例如,假设我有一个函数,它创建了一颗以给定速度从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。
以下是一些问题的答案:
不,我不能创建一个子弹类层次结构,因为那太慢了。管理项目符号的实际代码是用C编写的,我的函数是围绕C API的包装器。
我知道关键字参数,但检查各种参数组合是令人讨厌的,但默认参数帮助分配,如加速度=0
这种类型的行为通常是通过多态性(在OOP语言中)解决的。每一种子弹都有自己的飞行轨迹。例如:
class Bullet(object):
def __init__(self):
self.curve = None
self.speed = None
self.acceleration = None
self.sprite_image = None
class RegularBullet(Bullet):
def __init__(self):
super(RegularBullet, self).__init__()
self.speed = 10
class Grenade(Bullet):
def __init__(self):
super(Grenade, self).__init__()
self.speed = 4
self.curve = 3.5
add_bullet(Grendade())
def add_bullet(bullet):
c_function(bullet.speed, bullet.curve, bullet.acceleration, bullet.sprite, bullet.x, bullet.y)
void c_function(double speed, double curve, double accel, char[] sprite, ...) {
if (speed != null && ...) regular_bullet(...)
else if (...) curved_bullet(...)
//..etc..
}
向存在的c_function传递尽可能多的参数,然后根据初始c函数中的值确定调用哪个c函数。所以,Python应该只调用一个c函数。那个c函数查看参数,然后可以适当地委托给其他c函数。
本质上,您只是将每个子类用作不同的数据容器,但是通过在基类上定义所有潜在的参数,子类可以自由地忽略它们不做任何操作的参数。
当出现新的类型的项目符号时,您可以简单地在基础上再定义一个属性,更改一个python函数,以便它传递额外的属性,以及一个c_function,以适当地检查参数和委托。我想听起来还不算太糟。
对于函数重载,可以使用“自己动手”的解决方案。下面这个摘自Guido van Rossum关于多方法的文章(因为在Python中,多方法和重载之间几乎没有区别):
registry = {}
class MultiMethod(object):
def __init__(self, name):
self.name = name
self.typemap = {}
def __call__(self, *args):
types = tuple(arg.__class__ for arg in args) # a generator expression!
function = self.typemap.get(types)
if function is None:
raise TypeError("no match")
return function(*args)
def register(self, types, function):
if types in self.typemap:
raise TypeError("duplicate registration")
self.typemap[types] = function
def multimethod(*types):
def register(function):
name = function.__name__
mm = registry.get(name)
if mm is None:
mm = registry[name] = MultiMethod(name)
mm.register(types, function)
return mm
return register
它的用法是
from multimethods import multimethod
import unittest
# 'overload' makes more sense in this case
overload = multimethod
class Sprite(object):
pass
class Point(object):
pass
class Curve(object):
pass
@overload(Sprite, Point, Direction, int)
def add_bullet(sprite, start, direction, speed):
# ...
@overload(Sprite, Point, Point, int, int)
def add_bullet(sprite, start, headto, speed, acceleration):
# ...
@overload(Sprite, str)
def add_bullet(sprite, script):
# ...
@overload(Sprite, Curve, speed)
def add_bullet(sprite, curve, speed):
# ...
目前最严格的限制是:
不支持方法,只支持非类成员的函数;
继承没有被处理;
不支持Kwargs;
注册新函数应该在导入时完成,这是不线程安全的
根据定义,在python中重载函数是不可能的(详细信息请阅读下文),但您可以使用简单的装饰器实现类似的功能
class overload:
def __init__(self, f):
self.cases = {}
def args(self, *args):
def store_function(f):
self.cases[tuple(args)] = f
return self
return store_function
def __call__(self, *args):
function = self.cases[tuple(type(arg) for arg in args)]
return function(*args)
你可以这样用
@overload
def f():
pass
@f.args(int, int)
def f(x, y):
print('two integers')
@f.args(float)
def f(x):
print('one float')
f(5.5)
f(1, 2)
修改它以适应您的用例。
概念的澄清
function dispatch: there are multiple functions with the same name. Which one should be called? two strategies
static/compile-time dispatch (aka. "overloading"). decide which function to call based on the compile-time type of the arguments. In all dynamic languages, there is no compile-time type, so overloading is impossible by definition
dynamic/run-time dispatch: decide which function to call based on the runtime type of the arguments. This is what all OOP languages do: multiple classes have the same methods, and the language decides which one to call based on the type of self/this argument. However, most languages only do it for the this argument only. The above decorator extends the idea to multiple parameters.
为了澄清这一点,假设我们用一种假想的静态语言定义函数
void f(Integer x):
print('integer called')
void f(Float x):
print('float called')
void f(Number x):
print('number called')
Number x = new Integer('5')
f(x)
x = new Number('3.14')
f(x)
使用静态分派(重载),您将看到“number被调用”两次,因为x已被声明为number,这就是重载所关心的。在动态分派中,你会看到“integer called, float called”,因为它们是函数被调用时x的实际类型。
Python在呈现方法时支持“方法重载”。事实上,你刚刚描述的东西在Python中实现起来很简单,有很多不同的方式,但我认为:
class Character(object):
# your character __init__ and other methods go here
def add_bullet(self, sprite=default, start=default,
direction=default, speed=default, accel=default,
curve=default):
# do stuff with your arguments
在上面的代码中,default是这些参数的一个合理的默认值,即None。然后,您可以只使用感兴趣的参数调用该方法,Python将使用默认值。
你也可以这样做:
class Character(object):
# your character __init__ and other methods go here
def add_bullet(self, **kwargs):
# here you can unpack kwargs as (key, values) and
# do stuff with them, and use some global dictionary
# to provide default values and ensure that ``key``
# is a valid argument...
# do stuff with your arguments
另一种替代方法是直接将所需函数直接挂钩到类或实例:
def some_implementation(self, arg1, arg2, arg3):
# implementation
my_class.add_bullet = some_implementation_of_add_bullet
还有一种方法是使用抽象工厂模式:
class Character(object):
def __init__(self, bfactory, *args, **kwargs):
self.bfactory = bfactory
def add_bullet(self):
sprite = self.bfactory.sprite()
speed = self.bfactory.speed()
# do stuff with your sprite and speed
class pretty_and_fast_factory(object):
def sprite(self):
return pretty_sprite
def speed(self):
return 10000000000.0
my_character = Character(pretty_and_fast_factory(), a1, a2, kw1=v1, kw2=v2)
my_character.add_bullet() # uses pretty_and_fast_factory
# now, if you have another factory called "ugly_and_slow_factory"
# you can change it at runtime in python by issuing
my_character.bfactory = ugly_and_slow_factory()
# In the last example you can see abstract factory and "method
# overloading" (as you call it) in action