Python函数重载

我知道,Python不支持方法重载,但我遇到了一个问题,我似乎无法以一个很好的Pythonic方式解决。

我正在制作一个angular色需要拍摄各种子弹的游戏,但是如何编写用于创build这些子弹的不同function? 例如,假设我有一个函数,它创build一个子弹以一定的速度从A点移动到B点。 我会写这样一个函数:

def add_bullet(sprite, start, headto, speed): ... Code ... 

但是我想编写其他函数来创build子弹如:

  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_bullet1add_bullet2或者add_bullet_with_really_long_name

为了解决一些问题:

  1. 不,我不能创build一个子弹类层次结构,因为这太慢了。 pipe理项目符号的实际代码是用C语言编写的,我的函数是C API的包装器。

  2. 我知道关键字参数,但检查参数的各种组合是越来越讨厌,但默认参数帮助分配像acceleration=0

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 

你可以使用“roll-your-own”解决scheme来重载函数。 这个是从Guido van Rossum关于multimethods 的文章中复制的 (因为mm和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): # ... 

目前最严格的限制是:

  • 方法不被支持,只有不是类成员的函数;
  • inheritance不被处理;
  • kwargs不支持;
  • 注册新函数应该在导入时完成,而且不是线程安全的

你所要求的,被称为多派遣 。 请参阅演示不同types调度的Julia语言示例。

然而,在看之前,我们将首先解决为什么重载不是你想要的python。

为什么不重载?

首先需要理解重载的概念以及为什么它不适用于python。

在编译时使用能够区分数据types的语言时,可以在编译时进行select。 为编译时select创build这样的替代函数通常被称为重载函数。 ( 维基百科 )

Python是一种dynamictypes的语言,所以重载的概念并不适用于它。 然而,所有的东西都不会丢失,因为我们可以在运行时创build这样的替代function

在将数据types标识推迟到运行时的编程语言中,必须根据dynamic确定的函数参数types,在运行时select其他函数。 以这种方式select其替代实现的function通常被称为多方法 。 ( 维基百科 )

所以我们应该能够在python中执行多方法 ,或者也可以称为多派遣

多派遣

多方法也称为多派遣

多派或多派是一些面向对象的编程语言的特征,其中一个函数或方法可以基于多个参数的运行时(dynamic)typesdynamic调度。 ( 维基百科 )

Python不支持这个框1 。 但是,正如它发生的那样,有一个叫做multipledispatch的极好的python包,就是这么做的。

以下是我们如何使用multidispatch 2包来实现您的方法:

 >>> from multipledispatch import dispatch >>> from collections import namedtuple >>> from types import * # we can test for lambda type, eg: >>> type(lambda a: 1) == LambdaType True >>> Sprite = namedtuple('Sprite', ['name']) >>> Point = namedtuple('Point', ['x', 'y']) >>> Curve = namedtuple('Curve', ['x', 'y', 'z']) >>> Vector = namedtuple('Vector', ['x','y','z']) >>> @dispatch(Sprite, Point, Vector, int) ... def add_bullet(sprite, start, direction, speed): ... print("Called Version 1") ... >>> @dispatch(Sprite, Point, Point, int, float) ... def add_bullet(sprite, start, headto, speed, acceleration): ... print("Called version 2") ... >>> @dispatch(Sprite, LambdaType) ... def add_bullet(sprite, script): ... print("Called version 3") ... >>> @dispatch(Sprite, Curve, int) ... def add_bullet(sprite, curve, speed): ... print("Called version 4") ... >>> sprite = Sprite('Turtle') >>> start = Point(1,2) >>> direction = Vector(1,1,1) >>> speed = 100 #km/h >>> acceleration = 5.0 #m/s >>> script = lambda sprite: sprite.x * 2 >>> curve = Curve(3, 1, 4) >>> headto = Point(100, 100) # somewhere far away >>> add_bullet(sprite, start, direction, speed) Called Version 1 >>> add_bullet(sprite, start, headto, speed, acceleration) Called version 2 >>> add_bullet(sprite, script) Called version 3 >>> add_bullet(sprite, curve, speed) Called version 4 

1. Python 3目前支持单个发送

2.注意不要在multithreading环境中使用多个 dispatch,否则会出现奇怪的行为。

一个可能的select是使用multipledispatch模块详细在这里: http : //matthewrocklin.com/blog/work/2014/02/25/Multiple-Dispatch

而不是这样做:

 def add(self, other): if isinstance(other, Foo): ... elif isinstance(other, Bar): ... else: raise NotImplementedError() 

你可以这样做:

 from multipledispatch import dispatch @dispatch(int, int) def add(x, y): return x + y @dispatch(object, object) def add(x, y): return "%s + %s" % (x, y) 

由此产生的用法:

 >>> add(1, 2) 3 >>> add(1, 'hello') '1 + hello' 

这种types的行为通常使用多态来解决(在OOP语言中)。 每种types的子弹将负责知道它是如何旅行。 例如:

 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.. } 

将许多parameter passing给存在的c_function,然后根据初始c函数中的值确定要调用哪个c函数。 所以,python应该只能调用一个c函数。 一个c函数查看参数,然后可以适当地委托给其他c函数。

你基本上只是使用每个子类作为一个不同的数据容器,但通过定义基类的所有可能的参数,子类可以自由地忽略那些他们什么都不做的东西。

当一种新型的子popup现时,你可以简单地在基础上定义一个属性,改变一个python函数以传递额外的属性,一个c_function检查参数和适当的委托。 听起来不错,我猜。

在Python 3.4中添加了PEP-0443。 单派遣通用函数 。

这是来自PEP的简短API描述。

要定义一个generics函数,用@singledispatch装饰器来修饰它。 请注意,分派发生在第一个参数的types上。 相应地创build你的function:

 from functools import singledispatch @singledispatch def fun(arg, verbose=False): if verbose: print("Let me just say,", end=" ") print(arg) 

要将重载的实现添加到函数中,请使用generics函数的register()属性。 这是一个装饰器,接受一个types参数并装饰一个实现该types操作的函数:

 @fun.register(int) def _(arg, verbose=False): if verbose: print("Strength in numbers, eh?", end=" ") print(arg) @fun.register(list) def _(arg, verbose=False): if verbose: print("Enumerate this:") for i, elem in enumerate(arg): print(i, elem) 

我认为你的基本要求是在Python中有一个类似C / C ++的语法,并且可以让头痛最less。 虽然我喜欢亚历山大Poluektov的答案,它不适用于类。

以下应该为class级工作。 它通过区分非关键字参数的数量来工作(但不支持按types区分):

 class TestOverloading(object): def overloaded_function(self, *args, **kwargs): # Call the function that has the same number of non-keyword arguments. getattr(self, "_overloaded_function_impl_" + str(len(args)))(*args, **kwargs) def _overloaded_function_impl_3(self, sprite, start, direction, **kwargs): print "This is overload 3" print "Sprite: %s" % str(sprite) print "Start: %s" % str(start) print "Direction: %s" % str(direction) def _overloaded_function_impl_2(self, sprite, script): print "This is overload 2" print "Sprite: %s" % str(sprite) print "Script: " print script 

它可以像这样使用:

 test = TestOverloading() test.overloaded_function("I'm a Sprite", 0, "Right") print test.overloaded_function("I'm another Sprite", "while x == True: print 'hi'") 

输出:

这是过载3
雪碧:我是雪碧
开始:0
方向:对

这是超负荷2
雪碧:我是另一个雪碧
脚本:
而x == True:print'hi'

在定义中使用多个关键字参数,或者创build一个Bullet层次结构,将其实例传递给函数。

我认为带有相关多态性的Bullet类层次结构是一条路。 您可以通过使用元类有效地重载基类构造函数,以便调用基类将导致创build相应的子类对象。 下面是一些示例代码来说明我的意思的本质。

 class BulletMeta(type): def __new__(cls, classname, bases, classdict): """ Create Bullet class or a subclass of it. """ classobj = type.__new__(cls, classname, bases, classdict) if classname == 'Bullet': # base class definition? classobj.registry = {} # initialize class registry else: try: alias = classdict['alias'] except KeyError: raise TypeError("Bullet subclass %s has no 'alias'" % classname) if alias in Bullet.registry: # unique? raise TypeError("Bullet subclass %s's alias attribute " "%r already in use" % (classname, alias)) # register subclass under the specified alias classobj.registry[alias] = classobj return classobj # instance factory for subclasses # subclasses should only be instantiated by calls to the base class # with their subclass's alias as the first arg def __call__(cls, alias, *args, **kwargs): if cls != Bullet: raise TypeError("Bullet subclass %r objects should not to " "be explicitly constructed." % cls.__name__) elif alias not in cls.registry: # Bullet subclass? raise NotImplementedError("Unknown Bullet subclass %r" % str(alias)) # create designated subclass object (call its __init__ method) subclass = cls.registry[alias] return type.__call__(subclass, *args, **kwargs) class Bullet(object): __metaclass__ = BulletMeta # Presumably you'd define some abstract methods that all here # that would be supported by all subclasses. # These definitions could just raise NotImplementedError() or # implement the functionality is some sub-optimal generic way. # For example: def fire(self, *args, **kwargs): raise NotImplementedError(self.__class__.__name__ + ".fire() method") # abstract base class's __init__ should never be called # (if subclasses need to call super class's __init__(), # then then it might be implemented) def __init__(self, *args, **kwargs): raise NotImplementedError("Bullet is an abstract base class") # subclass definitions class Bullet1(Bullet): alias = 'B1' def __init__(self, sprite, start, direction, speed): print 'creating %s object' % self.__class__.__name__ def fire(self, trajectory): print 'Bullet1 object fired with %s trajectory' % trajectory class Bullet2(Bullet): alias = 'B2' def __init__(self, sprite, start, headto, spead, acceleration): print 'creating %s object' % self.__class__.__name__ class Bullet3(Bullet): alias = 'B3' def __init__(self, sprite, script): # script controlled bullets print 'creating %s object' % self.__class__.__name__ class Bullet4(Bullet): alias = 'B4' def __init__(self, sprite, curve, speed): # for bullets with curved paths print 'creating %s object' % self.__class__.__name__ class Sprite: pass class Curve: pass b1 = Bullet('B1', Sprite(), (10,20,30), 90, 600) # creating Bullet1 object b2 = Bullet('B2', Sprite(), (-30,17,94), (1,-1,-1), 600, 10) # creating Bullet2 object b3 = Bullet('B3', Sprite(), 'bullet42.script') # creating Bullet3 object b4 = Bullet('B4', Sprite(), Curve(), 720) # creating Bullet4 object b1.fire('uniform gravity') # Bullet1 object fired with uniform gravity trajectory b2.fire('uniform gravity') # NotImplementedError: Bullet2.fire() method 

通过传递关键字参数 。

 def add_bullet(**kwargs): #check for the arguments listed above and do the proper things 

使用默认值的关键字参数。 例如

 def add_bullet(sprite, start=default, direction=default, script=default, speed=default): 

在直线子弹与弯曲子弹的情况下,我会添加两个函数: add_bullet_straightadd_bullet_curved

python中的重载方法很棘手。 但是,可能会使用传递字典,列表或原始variables。

我已经为我的用例尝试了一些东西,这可以帮助理解人们重载方法。

我们来举个例子:

一个类的重载方法可以调用不同类的方法。

def add_bullet(sprite=None, start=None, headto=None, spead=None, acceleration=None):

从远程类传递参数:

add_bullet(sprite = 'test', start=Yes,headto={'lat':10.6666,'long':10.6666},accelaration=10.6}

add_bullet(sprite = 'test', start=Yes,headto={'lat':10.6666,'long':10.6666},speed=['10','20,'30']}

所以,对于列表,字典或者方法重载的原始variables,正在实现处理。

尝试一下你的代码。