Python函数重载

发布于 2024-11-16 08:24:10 字数 941 浏览 11 评论 0原文

我知道 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_bullet1add_bullet2add_bullet_with_really_long_name

要解决一些问题:

  1. 不,我无法创建 Bullet 类层次结构,因为那太慢了。管理项目符号的实际代码是用 C 编写的,我的函数是 C API 的包装器。

  2. 我了解关键字参数,但检查各种参数组合很烦人,但默认参数有助于分配,例如 acceleration=0

I know that Python does not support method overloading, but I've run into a problem that I can't seem to solve in a nice Pythonic way.

I am making a game where a character needs to shoot a variety of bullets, but how do I write different functions for creating these bullets? For example suppose I have a function that creates a bullet travelling from point A to B with a given speed. I would write a function like this:

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

But I want to write other functions for creating bullets like:

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

And so on with many variations. Is there a better way to do it without using so many keyword arguments cause its getting kinda ugly fast. Renaming each function is pretty bad too because you get either add_bullet1, add_bullet2, or add_bullet_with_really_long_name.

To address some answers:

  1. No I can't create a Bullet class hierarchy because thats too slow. The actual code for managing bullets is in C and my functions are wrappers around C API.

  2. I know about the keyword arguments but checking for all sorts of combinations of parameters is getting annoying, but default arguments help allot like acceleration=0

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(21

森林迷了鹿 2024-11-23 08:24:11

根据定义,在 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)

修改它使其适应您的用例。

概念的澄清

  • 函数调度:有多个同名函数。应该叫哪一个呢?两种策略
  • 静态/编译时调度又名“重载”)。根据参数的编译时类型决定调用哪个函数。在所有动态语言中,没有编译时类型,因此根据定义
  • 动态/运行时调度是不可能重载的:根据运行时类型决定调用哪个函数的论点。所有 OOP 语言都是这样做的:多个类具有相同的方法,语言根据 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)

通过静态分派(重载),您将看到“number called”两次,因为 x 已被声明为 Number< /code>,这就是重载所关心的。使用动态调度,您将看到“调用整数,调用浮点”,因为这些是调用函数时 x 的实际类型。

It is impossible by definition to overload a function in python (read on for details), but you can achieve something similar with a simple decorator

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)

You can use it like this

@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)

Modify it to adapt it to your use case.

A clarification of concepts

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

To clear up, assume that we define, in a hypothetical static language, the functions

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)

With static dispatch (overloading) you will see "number called" twice, because x has been declared as Number, and that's all overloading cares about. With dynamic dispatch you will see "integer called, float called", because those are the actual types of x at the time the function is called.

神经暖 2024-11-23 08:24:11

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=}")

输出

v=42, neg=-42
v=True, neg=False
NotImplementedError: Cannot negate a

@singledispatchmethod 支持与其他装饰器嵌套,例如
@类方法。请注意,为了允许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=}")

输出:

v=42, neg=-42
v=True, neg=False
NotImplementedError: Cannot negate a

相同的模式可以用于其他类似的装饰器:
静态方法、抽象方法等。

Python 3.8 added functools.singledispatchmethod

Transform a method into a single-dispatch generic function.

To define a generic method, decorate it with the @singledispatchmethod
decorator. Note that the dispatch happens on the type of the first
non-self or non-cls argument, create your function accordingly:

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=}")

Output

v=42, neg=-42
v=True, neg=False
NotImplementedError: Cannot negate a

@singledispatchmethod supports nesting with other decorators such as
@classmethod. Note that to allow for dispatcher.register,
singledispatchmethod must be the outer most decorator. Here is the
Negator class with the neg methods being class bound:

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=}")

Output:

v=42, neg=-42
v=True, neg=False
NotImplementedError: Cannot negate a

The same pattern can be used for other similar decorators:
staticmethod, abstractmethod, and others.

一个人的夜不怕黑 2024-11-23 08:24:11

通过传递关键字参数。

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

By passing keyword args.

def add_bullet(**kwargs):
    #check for the arguments listed above and do the proper things
囚我心虐我身 2024-11-23 08:24:11

您可以轻松地在Python中实现函数重载。下面是一个使用 floatsintegers 的示例:

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

代码背后的主要思想是一个类包含您想要实现的不同(重载)函数,以及一个字典作为路由器工作,根据输入type(x)将您的代码引导至正确的函数。

PS1。对于自定义类,例如 Bullet1,您可以按照类似的模式初始化内部字典,例如 self.D = {Bullet1: self.f_Bullet1, ...}。其余代码是相同的。

PS2。所提出的解决方案的时间/空间复杂度也很好,每个操作的平均成本为O(1)

You can easily implement function overloading in Python. Here is an example using floats and 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

The main idea behind the code is that a class holds the different (overloaded) functions that you would like to implement, and a Dictionary works as a router, directing your code towards the right function depending on the input type(x).

PS1. In case of custom classes, like Bullet1, you can initialize the internal dictionary following a similar pattern, such as self.D = {Bullet1: self.f_Bullet1, ...}. The rest of the code is the same.

PS2. The time/space complexity of the proposed solution is good as well, with an average cost of O(1) per operation.

时光礼记 2024-11-23 08:24:11

我认为你的基本要求是在 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

并且可以像这样简单地使用:

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
雪碧:我是另一个雪碧
脚本:
while x == True: 打印 'hi'

I think your basic requirement is to have a C/C++-like syntax in Python with the least headache possible. Although I liked Alexander Poluektov's answer it doesn't work for classes.

The following should work for classes. It works by distinguishing by the number of non-keyword arguments (but it doesn't support distinguishing by type):

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

And it can be used simply like this:

test = TestOverloading()

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

Output:

This is overload 3
Sprite: I'm a Sprite
Start: 0
Direction: Right

This is overload 2
Sprite: I'm another Sprite
Script:
while x == True: print 'hi'

昔日梦未散 2024-11-23 08:24:11

您可以使用以下 Python 代码来实现此目的:

@overload
def test(message: str):
    return message

@overload
def test(number: int):
    return number + 1

You can achieve this with the following Python code:

@overload
def test(message: str):
    return message

@overload
def test(number: int):
    return number + 1
断爱 2024-11-23 08:24:11

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

Either use multiple keyword arguments in the definition, or create a Bullet hierarchy whose instances are passed to the function.

脸赞 2024-11-23 08:24:11

如何在Python中重载?

我知道这是一个老问题,但这个主题仍然非常相关,而且我还没有读过一个清晰简洁的答案,所以我想我自己提供一个。


首先,安装包:

pip3 install overloading
pip3 install typing

然后,使用包中的 overload 装饰器来定义函数的多个实现:

from typing import Set, List
from overloading import overload

@overload
def process_data(data: List[str], join_string: str) -> str:
    return join_string.join(data)

@overload
def process_data(data: Set[str], join_string: str) -> str:
    return join_string.join(data)

@overload
def process_data(data: List[float], round_to: int) -> List[float]:
    return [round(x, round_to) for x in data]

# And so on ...

参数的数量和类型决定调用哪个版本的函数。

  • 不幸的是,不支持基于参数名称的调度

在这里您可以找到完整的文档。

How to overload in python?

I know this is an old question, but the topic is still very relevant, and I haven't read a single clear and concise answer, so I thought I'd provide one myself.


First, install the package:

pip3 install overloading
pip3 install typing

Then, use the overload decorator from the package to define multiple implementations of the function:

from typing import Set, List
from overloading import overload

@overload
def process_data(data: List[str], join_string: str) -> str:
    return join_string.join(data)

@overload
def process_data(data: Set[str], join_string: str) -> str:
    return join_string.join(data)

@overload
def process_data(data: List[float], round_to: int) -> List[float]:
    return [round(x, round_to) for x in data]

# And so on ...

The number and the types of the arguments determine which version of the function is called.

  • Unfortunately, dispatch based on argument names is not supported.

Here you can find the complete documentation.

心凉 2024-11-23 08:24:11

我认为具有关联多态性的 Bullet 类层次结构是正确的选择。您可以使用元类有效地重载基类构造函数,以便调用基类导致创建适当的子类对象。下面是一些示例代码来说明我的意思的本质。

已更新

代码已修改为在 Python 2 和 3 下运行,以保持相关性。这样做的方式避免了使用 Python 的显式元类语法,该语法在两个版本之间有所不同。

为了实现该目标,在创建 Bullet 基类时显式调用元类(而不是使用__metaclass__= 类属性或通过 metaclass 关键字参数,具体取决于 Python 版本)。

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')

输出:

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

I think a Bullet class hierarchy with the associated polymorphism is the way to go. You can effectively overload the base class constructor by using a metaclass so that calling the base class results in the creation of the appropriate subclass object. Below is some sample code to illustrate the essence of what I mean.

Updated

The code has been modified to run under both Python 2 and 3 to keep it relevant. This was done in a way that avoids the use Python's explicit metaclass syntax, which varies between the two versions.

To accomplish that objective, a BulletMetaBase instance of the BulletMeta class is created by explicitly calling the metaclass when creating the Bullet baseclass (rather than using the __metaclass__= class attribute or via a metaclass keyword argument depending on the Python version).

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')

Output:

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
时光无声 2024-11-23 08:24:11

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

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

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

Use keyword arguments with defaults. E.g.

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

In the case of a straight bullet versus a curved bullet, I'd add two functions: add_bullet_straight and add_bullet_curved.

把回忆走一遍 2024-11-23 08:24:11

在 Python 中重载方法很棘手。然而,可以使用传递字典、列表或原始变量。

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

让我们举个例子:

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

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']}

因此,通过方法重载实现了对列表、字典或基元变量的处理。

尝试一下您的代码。

Overloading methods is tricky in Python. However, there could be usage of passing the dict, list or primitive variables.

I have tried something for my use cases, and this could help here to understand people to overload the methods.

Let's take your example:

A class overload method with call the methods from different class.

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

Pass the arguments from the remote class:

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

Or

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

So, handling is being achieved for list, Dictionary or primitive variables from method overloading.

Try it out for your code.

茶花眉 2024-11-23 08:24:11

Plum 以简单的 Python 方式支持它。从下面的自述文件中复制示例。

from plum import dispatch

@dispatch
def f(x: str):
    return "This is a string!"
    

@dispatch
def f(x: int):
    return "This is an integer!"

>>> f("1")
'This is a string!'

>>> f(1)
'This is an integer!'

Plum supports it in a straightforward pythonic way. Copying an example from the README below.

from plum import dispatch

@dispatch
def f(x: str):
    return "This is a string!"
    

@dispatch
def f(x: int):
    return "This is an integer!"

>>> f("1")
'This is a string!'

>>> f(1)
'This is an integer!'
入画浅相思 2024-11-23 08:24:11

我的解决方案

def _either(**kwargs):
    return len([True for _, v in kwargs.items() if v is not None]) == 1

def overload(func, _overloaded_args=None):
    """enable overloading toward all params
    Usage:
        @overload
        def overloaded_func(either1=None, either2=None, either3=None):
            pass
        @overload
        def overloaded_func(must1, must2, either1=None, either2=None):
            pass
    """
    def inner(*func_args, **func_kwargs):
        nonlocal _overloaded_args
        __overloaded_args = _overloaded_args
        if __overloaded_args is None:
            __overloaded_args = list(func_kwargs.keys())
        if __overloaded_args:
            __overloaded_kwargs = {k: v for k, v in func_kwargs.items()
                                  if k in __overloaded_args}
            assert _either(**__overloaded_kwargs), (
                'function overloading should contain a single overloaded param.'
                f' overloaded params: {__overloaded_args}. inputs: {func_kwargs}')
        return func(*func_args, **func_kwargs)
    return inner

def overload_args(*_overloaded_args):
    """enable overloading toward specified params
    Usage:
        @overload_args('either1', 'either2')
        def overloaded_func(either1=None, either2=None):
            pass
        @overload_args('either1', 'either2')
        def overloaded_func(either1=None, either2=None, optional1=None, optional2=123):
            pass
    """
    def inner(func):
        return overload(func, _overloaded_args=_overloaded_args)
    return inner

My solution

def _either(**kwargs):
    return len([True for _, v in kwargs.items() if v is not None]) == 1

def overload(func, _overloaded_args=None):
    """enable overloading toward all params
    Usage:
        @overload
        def overloaded_func(either1=None, either2=None, either3=None):
            pass
        @overload
        def overloaded_func(must1, must2, either1=None, either2=None):
            pass
    """
    def inner(*func_args, **func_kwargs):
        nonlocal _overloaded_args
        __overloaded_args = _overloaded_args
        if __overloaded_args is None:
            __overloaded_args = list(func_kwargs.keys())
        if __overloaded_args:
            __overloaded_kwargs = {k: v for k, v in func_kwargs.items()
                                  if k in __overloaded_args}
            assert _either(**__overloaded_kwargs), (
                'function overloading should contain a single overloaded param.'
                f' overloaded params: {__overloaded_args}. inputs: {func_kwargs}')
        return func(*func_args, **func_kwargs)
    return inner

def overload_args(*_overloaded_args):
    """enable overloading toward specified params
    Usage:
        @overload_args('either1', 'either2')
        def overloaded_func(either1=None, either2=None):
            pass
        @overload_args('either1', 'either2')
        def overloaded_func(either1=None, either2=None, optional1=None, optional2=123):
            pass
    """
    def inner(func):
        return overload(func, _overloaded_args=_overloaded_args)
    return inner
謌踐踏愛綪 2024-11-23 08:24:11

从 Python 3.10 开始,您可以使用 match-case 语句来执行此操作。
就像这样:

def add_bullet(arguments):
    match arguments:
        case (sprite, start, direction, speed):
            # your code here
        case (sprite, start, headto, spead, acceleration):
            # your code here
        case (sprite, script):
            # your code here
        case (sprite, curve, speed):
            # your code here

只需确保在元组或列表中输入参数即可。像这样:

new_bullet = add_bullet((mysprite, myscript))

From Python 3.10, you can use the match-case statement to do this.
Like so:

def add_bullet(arguments):
    match arguments:
        case (sprite, start, direction, speed):
            # your code here
        case (sprite, start, headto, spead, acceleration):
            # your code here
        case (sprite, script):
            # your code here
        case (sprite, curve, speed):
            # your code here

Just make sure that you input your arguments in a tuple or list. Like this:

new_bullet = add_bullet((mysprite, myscript))
素手挽清风 2024-11-23 08:24:10

您所要求的称为多重调度。请参阅 Julia 语言示例,其中演示了不同类型的调度。

然而,在了解这一点之前,我们首先要解决为什么重载在 Python 中并不是您真正想要的。

为什么不超载?

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

当使用可以区分数据类型的语言时
编译时,可以在替代方案中进行选择
编译时。创建此类替代功能的行为
编译时选择通常称为重载
功能。 (维基百科

Python 是一个 动态类型语言,所以重载的概念根本不存在适用于它。然而,一切并没有丢失,因为我们可以在运行时创建这样的替代函数

在将数据类型识别推迟到的编程语言中
运行时在替代方案中进行选择
函数必须在运行时发生,基于动态确定的
函数参数的类型。其替代函数
以这种方式选择的实现被大多数人提及
一般为多方法。 (维基百科

所以我们应该能够在 Python 中执行多种方法——或者,也称为:多重调度

多重分派

多重方法也称为多重分派:

多重调度或多方法是某些
面向对象的编程语言,其中函数或方法
可以根据运行时(动态)类型动态调度
它的论点不止一个。 (维基百科

Python 不支持开箱即用1,但是,碰巧有一个名为 multipledispatch 的优秀 Python 包可以做到这一点。

解决方案

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

>>> 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**2
>>> 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 目前支持 single dispatch
  2. 注意不要在多线程环境中使用 multipledispatch 否则你会出现奇怪的行为。

What you are asking for is called multiple dispatch. See Julia language examples which demonstrates different types of dispatches.

However, before looking at that, we'll first tackle why overloading is not really what you want in Python.

Why Not Overloading?

First, one needs to understand the concept of overloading and why it's not applicable to 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 is a dynamically typed language, so the concept of overloading simply does not apply to it. However, all is not lost, since we can create such alternative functions at run-time:

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)

So we should be able to do multimethods in Python—or, as it is alternatively called: multiple dispatch.

Multiple dispatch

The multimethods are also called multiple dispatch:

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 does not support this out of the box1, but, as it happens, there is an excellent Python package called multipledispatch that does exactly that.

Solution

Here is how we might use multipledispatch2 package to implement your methods:

>>> 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**2
>>> 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 currently supports single dispatch
  2. Take care not to use multipledispatch in a multi-threaded environment or you will get weird behavior.
小嗲 2024-11-23 08:24:10

正如您所介绍的,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 是这些参数的合理默认值,或者 <代码>无。然后,您可以仅使用您感兴趣的参数调用该方法,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 

Python does support "method overloading" as you present it. In fact, what you just describe is trivial to implement in Python, in so many different ways, but I would go with:

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

In the above code, default is a plausible default value for those arguments, or None. You can then call the method with only the arguments you are interested in, and Python will use the default values.

You could also do something like this:

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

Another alternative is to directly hook the desired function directly to the class or instance:

def some_implementation(self, arg1, arg2, arg3):
  # implementation
my_class.add_bullet = some_implementation_of_add_bullet

Yet another way is to use an abstract factory pattern:

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 
机场等船 2024-11-23 08:24:10

您可以使用“自己动手”的解决方案来实现函数重载。这篇文章是从 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;
  • 注册新函数应该在导入时完成,这不是线程安全的

You can use "roll-your-own" solution for function overloading. This one is copied from Guido van Rossum's article about multimethods (because there is little difference between multimethods and overloading in 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

The usage would be

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):
    # ...

Most restrictive limitations at the moment are:

  • methods are not supported, only functions that are not class members;
  • inheritance is not handled;
  • kwargs are not supported;
  • registering new functions should be done at import time thing is not thread-safe
我早已燃尽 2024-11-23 08:24:10

一个可能的选择是使用 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'

A possible option is to use the multipledispatch module as detailed here:
http://matthewrocklin.com/blog/work/2014/02/25/Multiple-Dispatch

Instead of doing this:

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

You can do this:

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)

With the resulting usage:

>>> add(1, 2)
3

>>> add(1, 'hello')
'1 + hello'
玻璃人 2024-11-23 08:24:10

在 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)

要向函数添加重载实现,请使用通用函数的 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)

In Python 3.4 PEP-0443. Single-dispatch generic functions was added.

Here is a short API description from PEP.

To define a generic function, decorate it with the @singledispatch decorator. Note that the dispatch happens on the type of the first argument. Create your function accordingly:

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

To add overloaded implementations to the function, use the register() attribute of the generic function. This is a decorator, taking a type parameter and decorating a function implementing the operation for that type:

@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)
说谎友 2024-11-23 08:24:10

添加了 @overload 装饰器带有类型提示(PEP 484)。

虽然这不会改变 Python 的行为,但它确实使理解正在发生的事情以及 mypy 检测错误变得更容易。

from typing import overload

@overload
def foo() -> int: ...
@overload
def foo(arg: int) -> int: ...
def foo(arg=None):
    if arg is None:
        return 0
    return arg + 1

请参阅:类型提示PEP 484

The @overload decorator was added with type hints (PEP 484).

While this doesn't change the behaviour of Python, it does make it easier to understand what is going on, and for mypy to detect errors.

from typing import overload

@overload
def foo() -> int: ...
@overload
def foo(arg: int) -> int: ...
def foo(arg=None):
    if arg is None:
        return 0
    return arg + 1

See: Type hints and PEP 484

墨小墨 2024-11-23 08:24:10

这种类型的行为通常可以使用多态性来解决(在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 即可。我想听起来还不错。

This type of behaviour is typically solved (in OOP languages) using polymorphism. Each type of bullet would be responsible for knowing how it travels. For instance:

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

Pass as many arguments to the c_function that exist, and then do the job of determining which c function to call based on the values in the initial c function. So, Python should only ever be calling the one c function. That one c function looks at the arguments, and then can delegate to other c functions appropriately.

You're essentially just using each subclass as a different data container, but by defining all the potential arguments on the base class, the subclasses are free to ignore the ones they do nothing with.

When a new type of bullet comes along, you can simply define one more property on the base, change the one python function so that it passes the extra property, and the one c_function that examines the arguments and delegates appropriately. It doesn't sound too bad I guess.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文