标签归档:overloading

如何在Python中使用方法重载?

问题:如何在Python中使用方法重载?

我正在尝试在Python中实现方法重载:

class A:
    def stackoverflow(self):    
        print 'first method'
    def stackoverflow(self, i):
        print 'second method', i

ob=A()
ob.stackoverflow(2)

但是输出是second method 2; 类似地:

class A:
    def stackoverflow(self):    
        print 'first method'
    def stackoverflow(self, i):
        print 'second method', i

ob=A()
ob.stackoverflow()

Traceback (most recent call last):
  File "my.py", line 9, in <module>
    ob.stackoverflow()
TypeError: stackoverflow() takes exactly 2 arguments (1 given)

我该如何工作?

I am trying to implement method overloading in Python:

class A:
    def stackoverflow(self):    
        print 'first method'
    def stackoverflow(self, i):
        print 'second method', i

ob=A()
ob.stackoverflow(2)

but the output is second method 2; similarly:

class A:
    def stackoverflow(self):    
        print 'first method'
    def stackoverflow(self, i):
        print 'second method', i

ob=A()
ob.stackoverflow()

gives

Traceback (most recent call last):
  File "my.py", line 9, in <module>
    ob.stackoverflow()
TypeError: stackoverflow() takes exactly 2 arguments (1 given)

How do I make this work?


回答 0

它是方法重载而不是方法重写。在Python中,您可以通过一个函数来完成全部操作:

class A:

    def stackoverflow(self, i='some_default_value'):    
        print 'only method'

ob=A()
ob.stackoverflow(2)
ob.stackoverflow()

在Python中,不能有两个具有相同名称的方法,而且也不需要。

请参阅Python教程的默认参数值部分。有关避免的常见错误,请参见“最小惊讶”和“可变默认参数 ”。

编辑:有关Python 3.4中新的单调度通用功能的信息,请参阅PEP 443

It’s method overloading not method overriding. And in Python, you do it all in one function:

class A:

    def stackoverflow(self, i='some_default_value'):    
        print 'only method'

ob=A()
ob.stackoverflow(2)
ob.stackoverflow()

You can’t have two methods with the same name in Python — and you don’t need to.

See the Default Argument Values section of the Python tutorial. See “Least Astonishment” and the Mutable Default Argument for a common mistake to avoid.

Edit: See PEP 443 for information about the new single dispatch generic functions in Python 3.4.


回答 1

您还可以使用pythonlangutil

from pythonlangutil.overload import Overload, signature

class A:
    @Overload
    @signature()
    def stackoverflow(self):    
        print 'first method'

    @stackoverflow.overload
    @signature("int")
    def stackoverflow(self, i):
        print 'second method', i

You can also use pythonlangutil:

from pythonlangutil.overload import Overload, signature

class A:
    @Overload
    @signature()
    def stackoverflow(self):    
        print 'first method'

    @stackoverflow.overload
    @signature("int")
    def stackoverflow(self, i):
        print 'second method', i

回答 2

在Python中,您不会那样做。当人们使用Java之类的语言来执行此操作时,他们通常希望使用默认值(如果不需要,则通常需要使用其他名称的方法)。因此,在Python中,您可以具有默认值

class A(object):  # Remember the ``object`` bit when working in Python 2.x

    def stackoverflow(self, i=None):
        if i is None:
            print 'first form'
        else:
            print 'second form'

如您所见,您可以使用它来触发单独的行为,而不仅仅是具有默认值。

>>> ob = A()
>>> ob.stackoverflow()
first form
>>> ob.stackoverflow(2)
second form

In Python, you don’t do things that way. When people do that in languages like Java, they generally want a default value (if they don’t, they generally want a method with a different name). So, in Python, you can have default values.

class A(object):  # Remember the ``object`` bit when working in Python 2.x

    def stackoverflow(self, i=None):
        if i is None:
            print 'first form'
        else:
            print 'second form'

As you can see, you can use this to trigger separate behaviour rather than merely having a default value.

>>> ob = A()
>>> ob.stackoverflow()
first form
>>> ob.stackoverflow(2)
second form

回答 3

您可以,永远也不需要,也不需要。

在Python中,一切都是对象。类是事物,所以它们是对象。方法也是如此。

有一个称为A类的对象。它具有称为的属性stackoverflow。它只能具有一个这样的属性。

在编写时def stackoverflow(...): ...,发生的事情是创建一个方法对象,并将其分配给的stackoverflow属性A。如果您编写两个定义,则第二个定义将替换第一个定义,即分配始终起作用。

此外,您也不想编写执行有时用于重载的各种事情的代码。这不是这种语言的工作方式。

不要试图为每种类型的事物定义一个单独的函数(这毫无意义,因为您始终不为函数参数指定类型),而不必担心事物什么而开始思考它们可以做什么

您不仅不能编写一个单独的元组来处理一个元组还是一个列表,而且不想要也不需要

您要做的就是利用它们都是可迭代的事实(例如,您可以编写for element in container:)。(它们与继承没有直接关系这一事实无关紧要。)

You can’t, never need to and don’t really want to.

In Python, everything is an object. Classes are things, so they are objects. So are methods.

There is an object called A which is a class. It has an attribute called stackoverflow. It can only have one such attribute.

When you write def stackoverflow(...): ..., what happens is that you create an object which is the method, and assign it to the stackoverflow attribute of A. If you write two definitions, the second one replaces the first, the same way that assignment always behaves.

You furthermore do not want to write code that does the wilder of the sorts of things that overloading is sometimes used for. That’s not how the language works.

Instead of trying to define a separate function for each type of thing you could be given (which makes little sense since you don’t specify types for function parameters anyway), stop worrying about what things are and start thinking about what they can do.

You not only can’t write a separate one to handle a tuple vs. a list, but also don’t want or need to.

All you do is take advantage of the fact that they are both, for example, iterable (i.e. you can write for element in container:). (The fact that they aren’t directly related by inheritance is irrelevant.)


回答 4

虽然@agf过去使用PEP-3124正确地回答了问题,但我们得到了语法糖。有关装饰器的详细信息 ,请参见键入文档。@overload但是请注意,这实际上只是语法糖,恕我直言,这是所有人们从那时以来一直在参数的话题。我个人认为,具有不同特征的多个功能,使它更具可读性则具有20+的参数都设置为默认值(单个功能None的大部分时间),然后不得不反复折腾使用不已ifelifelse链找出什么调用者实际上希望我们的函数处理提供的参数集。在Python Zen之后,早就应该这样做了

美丽胜于丑陋。

并且可以说也是

简单胜于复杂。

直接来自上面链接的官方Python文档:

from typing import overload
@overload
def process(response: None) -> None:
    ...
@overload
def process(response: int) -> Tuple[int, str]:
    ...
@overload
def process(response: bytes) -> str:
    ...
def process(response):
    <actual implementation>

While @agf was right with the answer in the past now with PEP-3124 we got our syntax sugar. See typing documentation for details on the @overload decorator but note that this is really just syntax sugar and IMHO this is all people have been arguing about ever since. Personally I agree that having multiple functions with different signatures makes it more readable then having a single function with 20+ arguments all set to a default value (None most of the time) and then having to fiddle around using endless if, elif, else chains to find out what the caller actually wants our function to do with the provided set of arguments. This was long overdue following the Python Zen

Beautiful is better than ugly.

and arguably also

Simple is better than complex.

Straight from the official Python documentation linked above:

from typing import overload
@overload
def process(response: None) -> None:
    ...
@overload
def process(response: int) -> Tuple[int, str]:
    ...
@overload
def process(response: bytes) -> str:
    ...
def process(response):
    <actual implementation>

回答 5

我用Python 3.2.1写出答案。

def overload(*functions):
    return lambda *args, **kwargs: functions[len(args)](*args, **kwargs)

这个怎么运作:

  1. overload接受任意数量的可调用对象并将其存储在tuple中functions,然后返回lambda。
  2. lambda接受任意数量的参数,然后返回存储在functions[number_of_unnamed_args_passed]被调用中的调用函数的结果,并带有传递给lambda的参数。

用法:

class A:
    stackoverflow=overload(                    \
        None, \ 
        #there is always a self argument, so this should never get called
        lambda self: print('First method'),      \
        lambda self, i: print('Second method', i) \
    )

I write my answer in Python 3.2.1.

def overload(*functions):
    return lambda *args, **kwargs: functions[len(args)](*args, **kwargs)

How it works:

  1. overload takes any amount of callables and stores them in tuple functions, then returns lambda.
  2. The lambda takes any amount of arguments, then returns result of calling function stored in functions[number_of_unnamed_args_passed] called with arguments passed to the lambda.

Usage:

class A:
    stackoverflow=overload(                    \
        None, \ 
        #there is always a self argument, so this should never get called
        lambda self: print('First method'),      \
        lambda self, i: print('Second method', i) \
    )

回答 6

我认为您要查找的单词是“超载”。python中没有方法重载。但是,您可以使用默认参数,如下所示。

def stackoverflow(self, i=None):
    if i != None:     
        print 'second method', i
    else:
        print 'first method'

当您传递参数时,它将遵循第一个条件的逻辑并执行第一个print语句。当您不传递任何参数时,它将进入else条件并执行第二个print语句。

I think the word you’re looking for is “overloading”. There is no method overloading in python. You can however use default arguments, as follows.

def stackoverflow(self, i=None):
    if i != None:     
        print 'second method', i
    else:
        print 'first method'

When you pass it an argument it will follow the logic of the first condition and execute the first print statement. When you pass it no arguments, it will go into the else condition and execute the second print statement.


回答 7

我用Python 2.7写下答案:

在Python中,方法重载是不可能的。如果您真的想访问具有不同功能的相同功能,建议您进行方法重写。

class Base(): # Base class
    '''def add(self,a,b):
        s=a+b
        print s'''

    def add(self,a,b,c):
        self.a=a
        self.b=b
        self.c=c

        sum =a+b+c
        print sum

class Derived(Base): # Derived class
    def add(self,a,b): # overriding method
        sum=a+b
        print sum



add_fun_1=Base() #instance creation for Base class
add_fun_2=Derived()#instance creation for Derived class

add_fun_1.add(4,2,5) # function with 3 arguments
add_fun_2.add(4,2)   # function with 2 arguments

I write my answer in Python 2.7:

In Python, method overloading is not possible; if you really want access the same function with different features, I suggest you to go for method overriding.

class Base(): # Base class
    '''def add(self,a,b):
        s=a+b
        print s'''

    def add(self,a,b,c):
        self.a=a
        self.b=b
        self.c=c

        sum =a+b+c
        print sum

class Derived(Base): # Derived class
    def add(self,a,b): # overriding method
        sum=a+b
        print sum



add_fun_1=Base() #instance creation for Base class
add_fun_2=Derived()#instance creation for Derived class

add_fun_1.add(4,2,5) # function with 3 arguments
add_fun_2.add(4,2)   # function with 2 arguments

回答 8

在Python中,重载不是一个可应用的概念。但是,如果您试图创建一种情况,例如,如果要传递一个类型foo为实参的参数,而又要为类型为实参的参数,则希望执行一个初始化程序bar,因为Python中的所有内容都作为对象处理,因此您可以检查传递的对象的类类型的名称,并根据该条件编写条件处理。

class A:
   def __init__(self, arg)
      # Get the Argument's class type as a String
      argClass = arg.__class__.__name__

      if argClass == 'foo':
         print 'Arg is of type "foo"'
         ...
      elif argClass == 'bar':
         print 'Arg is of type "bar"'
         ...
      else
         print 'Arg is of a different type'
         ...

根据需要,可以通过不同的方法将此概念应用于多个不同的方案。

In Python, overloading is not an applied concept. However, if you are trying to create a case where, for instance, you want one initializer to be performed if passed an argument of type foo and another initializer for an argument of type bar then, since everything in Python is handled as object, you can check the name of the passed object’s class type and write conditional handling based on that.

class A:
   def __init__(self, arg)
      # Get the Argument's class type as a String
      argClass = arg.__class__.__name__

      if argClass == 'foo':
         print 'Arg is of type "foo"'
         ...
      elif argClass == 'bar':
         print 'Arg is of type "bar"'
         ...
      else
         print 'Arg is of a different type'
         ...

This concept can be applied to multiple different scenarios through different methods as needed.


回答 9

在Python中,您可以使用默认参数来执行此操作。

class A:

    def stackoverflow(self, i=None):    
        if i == None:
            print 'first method'
        else:
            print 'second method',i

In Python, you’d do this with a default argument.

class A:

    def stackoverflow(self, i=None):    
        if i == None:
            print 'first method'
        else:
            print 'second method',i

回答 10

刚刚遇到这个https://github.com/bintoro/overloading.py的人可能感兴趣。

从链接的存储库的自述文件中:

重载是一个基于运行时参数的类型和数量提供功能分派的模块。

调用重载函数时,调度程序将提供的参数与可用函数签名进行比较,并调用提供最准确匹配的实现。

特征

注册时的功能验证和详细的解决规则可确保在运行时具有唯一的,定义明确的结果。实现功能解析缓存以获得出色的性能。在函数签名中支持可选参数(默认值)。解决最佳匹配时,同时评估位置参数和关键字参数。支持后备功能和共享代码的执行。支持参数多态性。支持类和继承,包括类方法和静态方法。

Just came across this https://github.com/bintoro/overloading.py for anybody who may be interested.

From the linked repository’s readme:

overloading is a module that provides function dispatching based on the types and number of runtime arguments.

When an overloaded function is invoked, the dispatcher compares the supplied arguments to available function signatures and calls the implementation that provides the most accurate match.

Features

Function validation upon registration and detailed resolution rules guarantee a unique, well-defined outcome at runtime. Implements function resolution caching for great performance. Supports optional parameters (default values) in function signatures. Evaluates both positional and keyword arguments when resolving the best match. Supports fallback functions and execution of shared code. Supports argument polymorphism. Supports classes and inheritance, including classmethods and staticmethods.


回答 11

Python不支持Java或C ++之类的方法重载。我们可能会重载这些方法,但只能使用最新定义的方法。

# First sum method.
# Takes two argument and print their sum
def sum(a, b):
    s = a + b
    print(s)

# Second sum method
# Takes three argument and print their sum
def sum(a, b, c):
    s = a + b + c
    print(s)

# Uncommenting the below line shows an error    
# sum(4, 5)

# This line will call the second sum method
sum(4, 5, 5)

我们需要提供可选参数或* args,以便在调用时提供不同数量的args。

来自https://www.geeksforgeeks.org/python-method-overloading/

Python does not support method overloading like Java or C++. We may overload the methods but can only use the latest defined method.

# First sum method.
# Takes two argument and print their sum
def sum(a, b):
    s = a + b
    print(s)

# Second sum method
# Takes three argument and print their sum
def sum(a, b, c):
    s = a + b + c
    print(s)

# Uncommenting the below line shows an error    
# sum(4, 5)

# This line will call the second sum method
sum(4, 5, 5)

We need to provide optional arguments or *args in order to provide different number of args on calling.

Courtesy from https://www.geeksforgeeks.org/python-method-overloading/


回答 12

Python 3.x包含标准的类型库,该库允许使用@overload装饰器来重载方法。不幸的是,这是为了使代码更具可读性,因为@overload装饰的方法将需要后面跟一个处理不同参数的非装饰方法。在这里可以找到更多信息但以您的示例为例:

from typing import overload
from typing import Any, Optional
class A(object):
    @overload
    def stackoverflow(self) -> None:    
        print('first method')
    @overload
    def stackoverflow(self, i: Any) -> None:
        print('second method', i)
    def stackoverflow(self, i: Optional[Any] = None) -> None:
        if not i:
            print('first method')
        else:
            print('second method', i)

ob=A()
ob.stackoverflow(2)

Python 3.x includes standard typing library which allows for method overloading with the use of @overload decorator. Unfortunately, this is to make the code more readable, as the @overload decorated methods will need to be followed by a non-decorated method that handles different arguments. More can be found here here but for your example:

from typing import overload
from typing import Any, Optional
class A(object):
    @overload
    def stackoverflow(self) -> None:    
        print('first method')
    @overload
    def stackoverflow(self, i: Any) -> None:
        print('second method', i)
    def stackoverflow(self, i: Optional[Any] = None) -> None:
        if not i:
            print('first method')
        else:
            print('second method', i)

ob=A()
ob.stackoverflow(2)

回答 13

在MathMethod.py文件中

from multipledispatch import dispatch
@dispatch(int,int)
def Add(a,b):
   return a+b 
@dispatch(int,int,int)  
def Add(a,b,c):
   return a+b+c 
@dispatch(int,int,int,int)    
def Add(a,b,c,d):
   return a+b+c+d

在Main.py文件中

import MathMethod as MM 
print(MM.Add(200,1000,1000,200))

我们可以通过使用multidispatch重载方法

In MathMethod.py file

from multipledispatch import dispatch
@dispatch(int,int)
def Add(a,b):
   return a+b 
@dispatch(int,int,int)  
def Add(a,b,c):
   return a+b+c 
@dispatch(int,int,int,int)    
def Add(a,b,c,d):
   return a+b+c+d

In Main.py file

import MathMethod as MM 
print(MM.Add(200,1000,1000,200))

We can overload method by using multipledispatch


回答 14

Python在PEP-3124中添加了@overload装饰器,以通过类型检查为重载提供语法糖-而不是仅仅进行覆盖。

PEP-3124中通过@overload重载的代码示例

from overloading import overload
from collections import Iterable

def flatten(ob):
    """Flatten an object to its component iterables"""
    yield ob

@overload
def flatten(ob: Iterable):
    for o in ob:
        for ob in flatten(o):
            yield ob

@overload
def flatten(ob: basestring):
    yield ob

由@ overload-decorator转换为:

def flatten(ob):
    if isinstance(ob, basestring) or not isinstance(ob, Iterable):
        yield ob
    else:
        for o in ob:
            for ob in flatten(o):
                yield ob

Python added the @overload decorator with PEP-3124 to provide syntactic sugar for overloading via type inspection – instead of just working with overwriting.

Code example on overloading via @overload from PEP-3124

from overloading import overload
from collections import Iterable

def flatten(ob):
    """Flatten an object to its component iterables"""
    yield ob

@overload
def flatten(ob: Iterable):
    for o in ob:
        for ob in flatten(o):
            yield ob

@overload
def flatten(ob: basestring):
    yield ob

is transformed by the @overload-decorator to:

def flatten(ob):
    if isinstance(ob, basestring) or not isinstance(ob, Iterable):
        yield ob
    else:
        for o in ob:
            for ob in flatten(o):
                yield ob

Python函数重载

问题:Python函数重载

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


回答 0

您要的就是所谓的多重调度。请参阅Julia语言示例,该示例演示了不同类型的调度。

但是,在查看该内容之前,我们将首先解决为什么重载并不是python中真正想要的。

为什么不超载?

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

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

Python是一种动态类型的语言,因此重载的概念根本不适用于它。但是,一切都不会丢失,因为我们可以在运行时创建这样的替代函数

在将数据类型标识推迟到运行时的编程语言中,必须根据功能参数的动态确定类型,在运行时进行其他功能的选择。以这种方式选择其替代实现的功能通常被称为多方法。(维基百科

因此,我们应该能够做到多方法在python-或者,它也可称为:多分派

多次派遣

多重方法也称为多重调度

多种调度或多种方法是某些面向对象的编程语言的功能,其中可以基于多个参数的运行时(动态)类型来动态调度函数或方法。(维基百科

Python不支持这个开箱1,但是,因为它发生,有一个优秀的Python包称为multipledispatch这正是这么做的。

这是我们可能如何使用multidispatch 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

1. Python 3当前支持单调度 。2.注意不要在多线程环境中使用 调度,否则会出现奇怪的行为。

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


回答 1

演示时,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 

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 

回答 2

您可以使用“自己动手”解决方案进行函数重载。这是从Guido van Rossum关于多方法的文章中复制(因为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):
    # ...

最严格的限制,目前主要有:

  • 不支持方法,仅支持不是类成员的函数;
  • 不处理继承;
  • 不支持kwarg;
  • 注册新功能应该在导入时完成,这不是线程安全的

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

回答 3

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

回答 4

在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 was added PEP-0443. Single-dispatch generic functions.

Here is 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)

回答 5

通常使用多态来解决这种类型的行为(在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, 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. Doesn’t sound too bad I guess.


回答 6

通过传递关键字args

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

回答 7

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

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


回答 8

我认为您的基本要求是在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
Sprite:我是另一个Sprite
脚本:
而x == True:print’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 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’


回答 9

@overload用类型的提示(PEP 484)添加装饰器。尽管这不会改变python的行为,但确实可以更轻松地了解正在发生的事情,并让mypy检测错误。
请参阅:键入提示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.
See: Type hints and PEP 484


回答 10

我认为Bullet具有相关多态性的类层次结构是必经之路。您可以通过使用元类有效地重载基类构造函数,以便调用基类可导致创建适当的子类对象。下面是一些示例代码,以说明我的意思。

更新

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

为了实现这一目标,一个BulletMetaBase的实例BulletMeta类是由创建时显式调用元类来创建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

回答 11

Python 3.8添加了functools.singledispatchmethod

将方法转换为单调度通用函数。

要定义通用方法,请使用@singledispatchmethod装饰器对其进行装饰。请注意,调度是根据第一个非自身或非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支持与其他装饰器(例如,@ 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=}")

输出:

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

相同的模式可用于其他类似的修饰符:staticmethod,abstractmethod等。

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.


回答 12

将关键字参数与默认值一起使用。例如

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.


回答 13

重载方法在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, 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 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 codes.


回答 14

只是一个简单的装饰

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)

在静态分派(超载)中,您将看到两次“被调用”,因为x已被声明为Number,这就是所有超载所关心的。使用动态分派,您将看到“整数调用,浮点调用”,因为这些x是调用函数时的实际类型。

Just 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 a static language, and define 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.