Python function overloading
我知道python不支持方法重载,但我遇到了一个问题,我似乎无法用一种好的python方法来解决这个问题。
我正在制作一个游戏,一个角色需要发射各种子弹,但我如何编写不同的功能来创建这些子弹?例如,假设我有一个函数,可以创建一个子弹,以给定的速度从A点移动到B点。我会写一个这样的函数:
1 2 | def add_bullet(sprite, start, headto, speed): ... Code ... |
但我想编写其他用于创建项目符号的函数,如:
1 2 3 4 5 | 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 ... |
号
等等,有很多变化。有没有更好的方法可以做到这一点,而不使用如此多的关键字参数,因为它变得有点难看的快。重命名每个函数也很糟糕,因为您得到了
要解决一些问题:
不,我不能创建项目符号类层次结构,因为这太慢了。管理项目符号的实际代码是C语言,而我的函数是围绕C API的包装器。
我知道关键字参数,但检查各种参数组合会让人恼火,但默认参数有助于分配,如
python确实支持"方法重载"。事实上,您刚才描述的在Python中以许多不同的方式实现是很简单的,但是我将使用:
1 2 3 4 5 6 7 | 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 |
在上述代码中,
你也可以这样做:
1 2 3 4 5 6 7 8 9 10 | 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 |
。
另一种选择是直接将所需函数挂钩到类或实例:
1 2 3 | def some_implementation(self, arg1, arg2, arg3): # implementation my_class.add_bullet = some_implementation_of_add_bullet |
另一种方法是使用抽象的工厂模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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 |
。
你所要求的,叫做多重调度。请参阅Julia语言示例,其中演示了不同类型的分派。
然而,在研究这个问题之前,我们将首先讨论一下为什么重载并不是您在Python中真正想要的。
为什么不超载?首先,我们需要了解重载的概念,以及为什么它不适用于Python。
When working with languages that can discriminate data types at
compile-time, selecting among the alternatives can occur at
compile-time. The act of creating such alternative functions for
compile-time selection is usually referred to as overloading a
function. (Wikipedia)
号
Python是一种动态类型语言,因此重载的概念不适用于它。但是,所有这些都不会丢失,因为我们可以在运行时创建这样的可选函数:
In programming languages that defer data type identification until
run-time the selection among alternative
functions must occur at run-time, based on the dynamically determined
types of function arguments. Functions whose alternative
implementations are selected in this manner are referred to most
generally as multimethods. (Wikipedia)
号
因此,我们应该能够在Python中执行多方法,或者,正如它被称为多调度一样。
多次调度多方法也称为多调度:
Multiple dispatch or multimethods is the feature of some
object-oriented programming languages in which a function or method
can be dynamically dispatched based on the run time (dynamic) type of
more than one of its arguments. (Wikipedia)
号
python在box1之外不支持这个功能。但是,碰巧有一个名为multipledispatch的优秀python包可以做到这一点。
解决方案下面是我们如何使用multipleDispatch2包来实现您的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | >>> from multipledispatch import dispatch >>> from collections import namedtuple >>> from types import * # we can test for lambda type, e.g.: >>> 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 |
您可以使用"滚动您自己的"解决方案进行函数重载。这是从guido van rossum关于多方法的文章中复制的(因为mm和python中的重载几乎没有区别):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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 |
使用方法是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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;
- 注册新函数应该在导入时完成,这不是线程安全的。
一种可能的选择是使用multipleedispatch模块,如下所述:http://matthewrocklin.com/blog/work/2014/02/25/multiple-dispatch
而不是这样做:
1 2 3 4 5 6 7 | def add(self, other): if isinstance(other, Foo): ... elif isinstance(other, Bar): ... else: raise NotImplementedError() |
号
您可以这样做:
1 2 3 4 5 6 7 8 | 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) |
号
使用结果:
1 2 3 4 5 | >>> add(1, 2) 3 >>> add(1, 'hello') '1 + hello' |
号
在python中添加了pep-0443。单调度通用函数。
以下是PEP的简短API描述。
要定义一个通用函数,请用@singledispatch decorator来修饰它。注意,调度发生在第一个参数的类型上。相应地创建您的函数:
1 2 3 4 5 6 | from functools import singledispatch @singledispatch def fun(arg, verbose=False): if verbose: print("Let me just say,", end="") print(arg) |
。
要向函数添加重载实现,请使用泛型函数的register()属性。这是一个修饰器,获取类型参数并修饰实现该类型操作的函数:
1 2 3 4 5 6 7 8 9 10 11 12 | @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) |
这种类型的行为通常使用多态性来解决(在OOP语言中)。每种类型的子弹都有责任知道它是如何运动的。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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_函数,然后根据初始c函数中的值确定要调用的c函数。所以,Python应该只调用一个C函数。一个C函数查看参数,然后可以适当地委托给其他C函数。
本质上,您只是将每个子类用作不同的数据容器,但是通过在基类上定义所有可能的参数,子类可以自由地忽略它们不做任何事情的参数。
当出现一种新类型的项目符号时,您只需在基础上再定义一个属性,更改一个python函数,使其传递额外的属性,以及一个检查参数和适当委托的c_函数。我想听起来不错。
通过传递关键字参数。
1 2 | def add_bullet(**kwargs): #check for the arguments listed above and do the proper things |
号
我认为您的基本要求是在Python中有一个C/C++类似的语法,但最不头痛。虽然我喜欢亚历山大波卢埃克托夫的答案,但它不适合上课。
以下内容适用于课堂。它通过区分非关键字参数的数量来工作(但不支持按类型区分):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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 |
它可以这样简单地使用:
1 2 3 4 5 | test = TestOverloading() test.overloaded_function("I'm a Sprite", 0,"Right") test.overloaded_function("I'm another Sprite","while x == True: print 'hi'") |
号
输出:
This is overload 3
Sprite: I'm a Sprite
Start: 0
Direction: RightThis is overload 2
Sprite: I'm another Sprite
Script:
while x == True: print 'hi'
号
我认为一个具有相关多态性的
更新
代码已经修改为在python 2和3下运行以保持相关。这样做的方式可以避免使用Python的显式元类语法,因为这在两个版本之间是不同的。
为了实现这一目标,创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | 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 != 'BulletMetaBase': if classname == 'Bullet': # Base class definition? classobj.registry = {} # Initialize subclass 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 def __call__(cls, alias, *args, **kwargs): """ Bullet subclasses instance factory. Subclasses should only be instantiated by calls to the base class with their subclass' alias as the first arg. """ 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(BulletMeta('BulletMetaBase', (object,), {})): # 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__() for some # reason then it would need to 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) b2 = Bullet('B2', Sprite(), (-30,17,94), (1,-1,-1), 600, 10) b3 = Bullet('B3', Sprite(), 'bullet42.script') b4 = Bullet('B4', Sprite(), Curve(), 720) b1.fire('uniform gravity') b2.fire('uniform gravity') |
输出:
1 2 3 4 5 6 7 8 9 10 11 | creating Bullet1 object creating Bullet2 object creating Bullet3 object creating Bullet4 object Bullet1 object fired with uniform gravity trajectory Traceback (most recent call last): File"python-function-overloading.py", line 93, in <module> b2.fire('uniform gravity') # NotImplementedError: Bullet2.fire() method File"python-function-overloading.py", line 49, in fire raise NotImplementedError(self.__class__.__name__ +".fire() method") NotImplementedError: Bullet2.fire() method |
。
要么在定义中使用多个关键字参数,要么创建一个
在Python中,重载方法是很棘手的。但是,也可以使用传递dict、list或基元变量。
我已经为我的用例做了一些尝试,这有助于理解人们超载方法。
让我们举个例子:
具有从不同类调用方法的类重载方法。
1 | def add_bullet(sprite=None, start=None, headto=None, spead=None, acceleration=None): |
。
从远程类传递参数:
1 | add_bullet(sprite = 'test', start=Yes,headto={'lat':10.6666,'long':10.6666},accelaration=10.6} |
。
或
1 | add_bullet(sprite = 'test', start=Yes, headto={'lat':10.6666,'long':10.6666},speed=['10','20,'30']} |
因此,正在通过方法重载对列表、字典或基元变量进行处理。
试试看你的密码。
使用带默认值的关键字参数。例如。
1 | def add_bullet(sprite, start=default, direction=default, script=default, speed=default): |
对于直弹头和弯曲弹头,我增加了两个功能: