Bul*_*ets 175 python overloading
我知道Python不支持方法重载,但我遇到了一个问题,我似乎无法以一种漂亮的Pythonic方式解决这个问题.
我正在制作一个角色需要射击各种子弹的游戏,但是如何编写不同的功能来制作这些子弹呢?例如,假设我有一个函数可以创建一个以给定速度从A点到B点行进的子弹.我会写一个这样的函数:
def add_bullet(sprite, start, headto, speed):
... Code ...
Run Code Online (Sandbox Code Playgroud)
但我想写其他功能来创建子弹,如:
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 ...
Run Code Online (Sandbox Code Playgroud)
等等有很多变化.有没有更好的方法来做到这一点,而不使用这么多的关键字参数导致它快速变得有点难看.重命名每个功能,因为你要么是非常糟糕过add_bullet1,add_bullet2或add_bullet_with_really_long_name.
要解决一些问题:
不,我无法创建Bullet类层次结构,因为它太慢了.管理项目符号的实际代码在C中,我的函数是围绕C API的包装器.
我知道关键字参数,但检查各种参数组合变得烦人,但默认参数有助于分配 acceleration=0
And*_*yuk 116
您要求的是多次调度.请参阅演示不同类型的调度的Julia语言示例.
但是,在看这个之前,我们首先要解决为什么在python中你不想要重载的原因.
首先需要了解重载的概念以及为什么它不适用于python.
使用可以在编译时区分数据类型的语言时,可以在编译时选择备选方案.为编译时选择创建这样的替代函数的行为通常被称为重载函数.(维基百科)
Python是一种动态类型语言,因此重载的概念根本不适用于它.但是,一切都不会丢失,因为我们可以在运行时创建这样的替代函数:
在将数据类型识别推迟到运行时的编程语言中,基于动态确定的函数参数类型,在运行时必须在备选函数之间进行选择.以这种方式选择其替代实现的函数通常被称为多方法.(维基百科)
所以我们应该能够在python中执行多方法,或者,或者称为多方式调度.
多方法也称为多次发送:
多个调度或多方法是一些面向对象编程语言的特征,其中可以基于多个参数的运行时(动态)类型动态地调度函数或方法.(维基百科)
Python不支持开箱即用1.但是,正如它所发生的那样,有一个很好的python包叫做multipledispatch,就是这样做的.
以下是我们如何使用multipledispatch 2包来实现您的方法:
>>> 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
Run Code Online (Sandbox Code Playgroud)
1. Python 3目前支持单一调度
2.注意不要在多线程环境中使用 multipledispatch,否则会产生奇怪的行为.
Esc*_*alo 105
当你提出它时,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
Run Code Online (Sandbox Code Playgroud)
在上面的代码中,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
Run Code Online (Sandbox Code Playgroud)
另一种方法是直接将所需函数挂接到类或实例:
def some_implementation(self, arg1, arg2, arg3):
# implementation
my_class.add_bullet = some_implementation_of_add_bullet
Run Code Online (Sandbox Code Playgroud)
另一种方法是使用抽象工厂模式:
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
Run Code Online (Sandbox Code Playgroud)
Ale*_*tov 87
您可以使用"自己动手"解决方案进行功能重载.这个是从Guido van Rossum关于multimethods 的文章中复制的(因为在python中mm和重载之间几乎没有区别):
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
Run Code Online (Sandbox Code Playgroud)
用法是
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):
# ...
Run Code Online (Sandbox Code Playgroud)
最严格的限制,目前主要有:
Dav*_*e C 35
一个可能的选择是使用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()
Run Code Online (Sandbox Code Playgroud)
你可以这样做:
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)
Run Code Online (Sandbox Code Playgroud)
由此产生的用法:
>>> add(1, 2)
3
>>> add(1, 'hello')
'1 + hello'
Run Code Online (Sandbox Code Playgroud)
Inf*_*ion 16
在Python 3.4中添加了PEP-0443.单调度泛型函数.
以下是PEP的简短API描述.
要定义泛型函数,请使用@singledispatch装饰器进行装饰.请注意,调度发生在第一个参数的类型上.相应地创建你的功能:
from functools import singledispatch
@singledispatch
def fun(arg, verbose=False):
if verbose:
print("Let me just say,", end=" ")
print(arg)
Run Code Online (Sandbox Code Playgroud)
要向函数添加重载实现,请使用泛型函数的register()属性.这是一个装饰器,它接受一个类型参数并装饰一个实现该类型操作的函数:
@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)
Run Code Online (Sandbox Code Playgroud)
Jos*_*ton 11
通常使用多态性来解决这种类型的行为(在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..
}
Run Code Online (Sandbox Code Playgroud)
将尽可能多的参数传递给存在的c_function,然后根据初始c函数中的值确定要调用哪个c函数.所以,python应该只调用一个c函数.一个c函数查看参数,然后可以适当地委托给其他c函数.
您实际上只是将每个子类用作不同的数据容器,但通过定义基类上的所有潜在参数,子类可以自由地忽略它们不执行任何操作的子类.
当一种新类型的子弹出现时,您可以简单地在基础上定义一个属性,更改一个python函数以便它传递额外属性,以及一个c_function,它会检查参数并适当地委托.我觉得听起来不太糟糕.
blu*_*ote 10
根据定义,在 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)
Run Code Online (Sandbox Code Playgroud)
你可以像这样使用它
@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)
Run Code Online (Sandbox Code Playgroud)
修改它以适应您的用例。
概念的澄清
self/this参数的类型决定调用哪一个。但是,大多数语言仅针对this参数执行此操作。上面的装饰器将这个想法扩展到多个参数。为了清楚起见,假设我们用一种假设的静态语言定义了函数
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)
Run Code Online (Sandbox Code Playgroud)
使用静态调度(重载),您将看到“被调用的数字”两次,因为x已被声明为Number,而这就是重载所关心的。使用动态分派,您将看到“整数调用,浮点调用”,因为这些x是调用函数时的实际类型。
Python 3.8 添加了functools.singledispatchmethod
将方法转换为单分派通用函数。
要定义泛型方法,请使用 @singledispatchmethod 装饰器来装饰它。请注意,分派发生在第一个非 self 或非 cls 参数的类型上,相应地创建您的函数:
from functools import singledispatchmethod
class Negator:
@singledispatchmethod
def neg(self, arg):
raise NotImplementedError("Cannot negate a")
@neg.register
def _(self, arg: int):
return -arg
@neg.register
def _(self, arg: bool):
return not arg
negator = Negator()
for v in [42, True, "Overloading"]:
neg = negator.neg(v)
print(f"{v=}, {neg=}")
Run Code Online (Sandbox Code Playgroud)
输出
v=42, neg=-42
v=True, neg=False
NotImplementedError: Cannot negate a
Run Code Online (Sandbox Code Playgroud)
@singledispatchmethod 支持与其他装饰器(例如 @classmethod)嵌套。请注意,为了允许dispatcher.register,singledispatchmethod必须是最外层的装饰器。这是 Negator 类,其 neg 方法是类绑定的:
from functools import singledispatchmethod
class Negator:
@singledispatchmethod
@staticmethod
def neg(arg):
raise NotImplementedError("Cannot negate a")
@neg.register
def _(arg: int) -> int:
return -arg
@neg.register
def _(arg: bool) -> bool:
return not arg
for v in [42, True, "Overloading"]:
neg = Negator.neg(v)
print(f"{v=}, {neg=}")
Run Code Online (Sandbox Code Playgroud)
输出:
v=42, neg=-42
v=True, neg=False
NotImplementedError: Cannot negate a
Run Code Online (Sandbox Code Playgroud)
相同的模式可用于其他类似的装饰器:staticmethod、abstractmethod 等。
通过传递关键字args.
def add_bullet(**kwargs):
#check for the arguments listed above and do the proper things
Run Code Online (Sandbox Code Playgroud)
我认为您的基本要求是在 Python 中使用类似 C/C++ 的语法,并且尽可能减少头痛。虽然我喜欢Alexander Poluektov 的回答,但它不适用于课堂。
以下应该适用于类。它的工作原理是通过非关键字参数的数量来区分(但它不支持按类型区分):
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
Run Code Online (Sandbox Code Playgroud)
它可以像这样简单地使用:
test = TestOverloading()
test.overloaded_function("I'm a Sprite", 0, "Right")
print
test.overloaded_function("I'm another Sprite", "while x == True: print 'hi'")
Run Code Online (Sandbox Code Playgroud)
输出:
这是重载 3
精灵:我是精灵
开始:0
方向:右这是重载 2
Sprite: I'm another Sprite
Script:
while x == True: print 'hi'
小智 6
您可以使用以下 Python 代码来实现此目的:
@overload
def test(message: str):
return message
@overload
def test(number: int):
return number + 1
Run Code Online (Sandbox Code Playgroud)
您可以轻松地在Python中实现函数重载。floats这是使用和 的示例integers:
class OverloadedFunction:
def __init__(self):
self.router = {int : self.f_int ,
float: self.f_float}
def __call__(self, x):
return self.router[type(x)](x)
def f_int(self, x):
print('Integer Function')
return x**2
def f_float(self, x):
print('Float Function (Overloaded)')
return x**3
# f is our overloaded function
f = OverloadedFunction()
print(f(3 ))
print(f(3.))
# Output:
# Integer Function
# 9
# Float Function (Overloaded)
# 27.0
Run Code Online (Sandbox Code Playgroud)
代码背后的主要思想是,一个类包含您想要实现的不同(重载)函数,而字典则充当router,根据输入将您的代码定向到正确的函数type(x)。
PS1。对于自定义类,例如Bullet1,您可以按照类似的模式初始化内部字典,例如self.D = {Bullet1: self.f_Bullet1, ...}。其余代码是相同的。
PS2。所提出的解决方案的时间/空间复杂度也很好,O(1)每个操作的平均成本为。