파이썬 함수 오버로딩


213

파이썬이 메소드 오버로드를 지원하지 않는다는 것을 알고 있지만 훌륭한 파이썬 방식으로는 해결할 수없는 문제가 발생했습니다.

캐릭터가 다양한 총알을 쏠 필요가있는 게임을 만들고 있는데이 총알을 만들기 위해 다른 기능을 어떻게 작성합니까? 예를 들어 주어진 속도로 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_bullet1, add_bullet2또는 add_bullet_with_really_long_name.

일부 답변을 해결하려면 :

  1. 아니오 Bullet 클래스 계층 구조는 너무 느리므로 작성할 수 없습니다. 글 머리 기호를 관리하는 실제 코드는 C이고 내 함수는 C API를 래퍼합니다.

  2. 키워드 인수에 대해서는 알고 있지만 모든 종류의 매개 변수 조합을 확인하는 것은 성가신 일이지만 기본 인수는 다음과 같이 도움이됩니다. acceleration=0


5
하나의 매개 변수에 대해서만 작동하지만 여기 (검색 엔진에서 오는 사람들) : docs.python.org/3/library/…
leewz

1
이것은 기본값으로 좋은 장소처럼 보입니다. 일부를 없음으로 설정하고 확인하십시오. 여분의 부울 영향은 무시할 것
앤드류 스캇 에반스에게

사용해야 할 default value + if + elseC가 할 ++로 동일한 작업을 수행 할 수 있습니다. 이것은 C ++이 파이썬보다 더 나은 가독성을 갖는 아주 적은 것들 중 하나입니다.
Deqing

왜 kwargs가 올바른 대답이 아닌지 혼란 스럽습니다. 당신은 당신이 추악 해지기 때문에 많은 키워드 인수를 사용하고 싶지 않다고 말합니다. 글쎄 그것은 문제의 본질입니다. 당신이 많은 주장을 가지고 있고 당신이 기대했던 것보다 많은 주장을 가지고 있기 때문에 지저분합니까? 어디서나 지정하지 않고 많은 인수를 사용 하시겠습니까? 파이썬은 마인드 리더가 아닙니다.
Calculus

우리는 어떤 종류의 객체인지 script, curve, 공통 조상이 있는지, 어떤 방법을 지원하는지 알지 못합니다 . 오리 타이핑을 사용하면 클래스 디자인에서 지원해야 할 방법을 찾는 것이 전적으로 귀하의 몫입니다. 아마도 Script일종의 타임 스텝 기반 콜백을 지원합니다 (그러나 어떤 객체를 반환해야합니까? 그 타임 스텝에서의 위치? 그 타임 스텝에서의 궤적?). 아마도 start, direction, speedstart, headto, spead, acceleration모두 궤도의 유형에 대해 설명하지만, 다시 그들을 풀고을 처리하는 방법을 알 수있는 수신 클래스를 설계하는 당신에게 달려 있습니다.
smci

답변:


144

당신이 요구하는 것을 다중 디스패치 라고 합니다 . 다양한 유형의 디스패치를 ​​보여주는 Julia 언어 예제를 참조하십시오 .

그러나 그것을보기 전에 먼저 과부하 가 실제로 파이썬에서 원하는 것이 아닌 이유를 다루겠습니다 .

왜 과부하가되지 않습니까?

첫째, 과부하의 개념과 파이썬에 적용 할 수없는 이유를 이해해야합니다.

컴파일 타임에 데이터 유형을 식별 할 수있는 언어로 작업 할 때, 컴파일 타임에 대안 중에서 선택할 수 있습니다. 컴파일 타임 선택을위한 이러한 대체 함수를 작성하는 행위는 일반적으로 함수 오버로드라고합니다. ( 위키 백과 )

파이썬은 동적으로 입력되는 언어이므로 오버로딩의 개념은 단순히 적용되지 않습니다. 그러나 런타임에 이러한 대체 기능 을 만들 수 있기 때문에 모든 것이 손실되지는 않습니다 .

런타임까지 데이터 유형 식별을 지연시키는 프로그래밍 언어에서 동적으로 결정된 함수 인수 유형에 따라 런타임시 대체 기능 중에서 선택해야합니다. 이러한 방식으로 대체 구현이 선택된 함수를 가장 일반적으로 멀티 메소드 라고합니다 . ( 위키 백과 )

따라서 우리는 파이썬에서 멀티 메소드 를 수행 할 수 있어야합니다. 또는 다중 디스패치 라고도 합니다.

여러 파견

멀티 메소드는 다중 디스패치 라고도 합니다 .

다중 디스패치 또는 멀티 메소드는 일부 객체 지향 프로그래밍 언어의 기능으로, 함수 또는 메소드가 둘 이상의 인수의 런타임 (동적) 유형을 기반으로 동적으로 디스패치 될 수 있습니다. ( 위키 백과 )

파이썬은 상자의 밖으로 지원하지 않는 일을 그런 일이 같은 우수한 파이썬 패키지라는 존재,하지만 multipledispatch 정확히 않습니다.

해결책

다음은 multipledispatch 2 패키지를 사용하여 메소드를 구현하는 방법입니다.

>>> from multipledispatch import dispatch
>>> from collections import namedtuple  
>>> from types import *  # we can test for lambda type, e.g.:
>>> type(lambda a: 1) == LambdaType
True

>>> Sprite = namedtuple('Sprite', ['name'])
>>> Point = namedtuple('Point', ['x', 'y'])
>>> Curve = namedtuple('Curve', ['x', 'y', 'z'])
>>> Vector = namedtuple('Vector', ['x','y','z'])

>>> @dispatch(Sprite, Point, Vector, int)
... def add_bullet(sprite, start, direction, speed):
...     print("Called Version 1")
...
>>> @dispatch(Sprite, Point, Point, int, float)
... def add_bullet(sprite, start, headto, speed, acceleration):
...     print("Called version 2")
...
>>> @dispatch(Sprite, LambdaType)
... def add_bullet(sprite, script):
...     print("Called version 3")
...
>>> @dispatch(Sprite, Curve, int)
... def add_bullet(sprite, curve, speed):
...     print("Called version 4")
...

>>> sprite = Sprite('Turtle')
>>> start = Point(1,2)
>>> direction = Vector(1,1,1)
>>> speed = 100 #km/h
>>> acceleration = 5.0 #m/s
>>> script = lambda sprite: sprite.x * 2
>>> curve = Curve(3, 1, 4)
>>> headto = Point(100, 100) # somewhere far away

>>> add_bullet(sprite, start, direction, speed)
Called Version 1

>>> add_bullet(sprite, start, headto, speed, acceleration)
Called version 2

>>> add_bullet(sprite, script)
Called version 3

>>> add_bullet(sprite, curve, speed)
Called version 4

1. 파이썬 3는 현재 지원하는 단일 파견
사용하지 2. 테이크 치료를 multipledispatch을 멀티 스레드 환경에서 또는 당신이 이상한 행동을 얻을 것이다.


6
멀티 스레드 환경에서 'multipledispatch'의 문제점은 무엇입니까? 서버 측 코드는 일반적으로 멀티 스레드 환경에 있기 때문에! 그냥 파 내려고 했어요!
danzeer 2012 년

7
@danzeer 스레드 안전하지 않았습니다. 나는 두 개의 다른 스레드에 의해 인수가 수정되는 것을 보았습니다 (즉, speed다른 스레드가 자체 값을으로 설정할 때 함수 중간에 값 이 변경 될 수 있습니다 speed)! 그것이 범인이라는 것이 도서관이라는 것을 깨닫는 데 오랜 시간이 걸렸습니다.
Andriy Drozdyuk

108

파이썬은 "메소드 오버로딩"을 지원합니다. 사실, 방금 설명 한 것은 파이썬에서 구현하는 것이 매우 다양하지만 여러 가지 방법이 있습니다.

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 

107
이 모든 것은 오버로드가 아닌 변수 인수의 예입니다. 오버로드를 사용하면 인수와 다른 유형에 대해 동일한 기능을 가질 수 있습니다. 예 : sum (real_num1, real_num2) 및 sum (imaginary_num1, imaginary_num2) 둘 다 동일한 호출 구문을 갖지만 실제로 입력과는 다른 두 가지 유형을 기대하며 구현도 내부적으로 변경되어야합니다
Efren

17
당신이 함께 할 대답을 사용하여, 어떤 주장이 함께 이해되는지 발신자에게 어떻게 제시 하시겠습니까? 기본값으로 각각의 인수를 넣는 것만으로도 동일한 기능을 제공 할 수 있지만 API 측면에서는 훨씬 덜 우아합니다.
Greg Ennis

6
위의 내용 중 과부하가 아닌 경우, 구현시 다음과 같이 매개 변수 입력의 모든 조합을 확인하거나 매개 변수를 무시 if sprite and script and not start and not direction and not speed...해야합니다. 특정 작업에 있는지 알기 위해서입니다. 호출자가 사용 가능한 모든 매개 변수를 제공하는 함수를 호출 할 수 있기 때문입니다. 과부하가 걸리는 동안 정확한 관련 매개 변수 세트를 정의하십시오.
Roee Gavirel

5
사람들이 파이썬이 메소드 오버로드를 지원한다고 말하면 매우 화가납니다. 그렇지 않습니다. "메소드 오버로드"를 따옴표로 묶었다는 사실은이 사실을 알고 있음을 나타냅니다. 여기에 언급 된 것과 같은 몇 가지 기술로 유사한 기능을 얻을 수 있습니다. 그러나 메서드 오버로딩에는 매우 구체적인 정의가 있습니다.
Howard Swope

의도 된 요점은 메소드 오버로드가 파이썬의 기능이 아니지만 위의 메커니즘을 사용하여 동등한 효과를 얻을 수 있다고 생각합니다.
rawr 님이

93

기능 과부하에 "자신의 롤"솔루션을 사용할 수 있습니다. 이것은 다중 방법에 대한 Guido van Rossum의 기사 에서 복사 한 것입니다 (파이썬에서는 mm과 과부하의 차이가 거의 없기 때문에).

registry = {}

class MultiMethod(object):
    def __init__(self, name):
        self.name = name
        self.typemap = {}
    def __call__(self, *args):
        types = tuple(arg.__class__ for arg in args) # a generator expression!
        function = self.typemap.get(types)
        if function is None:
            raise TypeError("no match")
        return function(*args)
    def register(self, types, function):
        if types in self.typemap:
            raise TypeError("duplicate registration")
        self.typemap[types] = function


def multimethod(*types):
    def register(function):
        name = function.__name__
        mm = registry.get(name)
        if mm is None:
            mm = registry[name] = MultiMethod(name)
        mm.register(types, function)
        return mm
    return register

사용법은

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는 지원되지 않습니다.
  • 가져 오기시에 새로운 기능을 등록해야합니다. 스레드로부터 안전하지 않습니다.

6
이 사용 사례에서 언어를 확장하기위한 데코레이터의 경우 +1입니다.
Eloims

1
+1 이것은 좋은 아이디어이기 때문에 (그리고 아마도 OP와 함께해야 할 일) --- 파이썬에서 다중 메서드 구현을 본 적이 없었습니다.
Escualo

39

가능한 옵션은 다음과 같이 multidispatch 모듈을 사용하는 것입니다 : 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'

4
왜 더 많은 표를 얻지 못합니까? 예제가 없기 때문에 추측하고 있습니다 ... multipledispatch 패키지 의 OP 문제에 대한 솔루션을 구현하는 방법에 대한 예를 사용하여 답변을 만들었습니다 .
Andriy Drozdyuk

19

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)

11

이 유형의 동작은 일반적으로 다형성을 사용하여 OOP 언어로 해결됩니다. 각 유형의 총알은 어떻게 이동하는지 알 책임이 있습니다. 예를 들어 :

class Bullet(object):
    def __init__(self):
        self.curve = None
        self.speed = None
        self.acceleration = None
        self.sprite_image = None

class RegularBullet(Bullet):
    def __init__(self):
        super(RegularBullet, self).__init__()
        self.speed = 10

class Grenade(Bullet):
    def __init__(self):
        super(Grenade, self).__init__()
        self.speed = 4
        self.curve = 3.5

add_bullet(Grendade())

def add_bullet(bullet):
    c_function(bullet.speed, bullet.curve, bullet.acceleration, bullet.sprite, bullet.x, bullet.y) 


void c_function(double speed, double curve, double accel, char[] sprite, ...) {
    if (speed != null && ...) regular_bullet(...)
    else if (...) curved_bullet(...)
    //..etc..
}

존재하는 c_function에 많은 인수를 전달한 다음 초기 c 함수의 값을 기반으로 호출 할 c 함수를 결정하는 작업을 수행하십시오. 따라서 파이썬은 오직 하나의 c 함수 만 호출해야합니다. 한 c 함수는 인수를보고 다른 c 함수에 적절하게 위임 할 수 있습니다.

기본적으로 각 하위 클래스를 다른 데이터 컨테이너로 사용하고 있지만 기본 클래스에서 모든 잠재적 인수를 정의하면 하위 클래스는 아무것도하지 않는 것을 무시할 수 있습니다.

새로운 유형의 글 머리 기호가 나타나면 기본에 하나 이상의 속성을 정의하고 추가 속성을 전달하도록 하나의 파이썬 함수를 변경하고 인수를 검사하고 적절하게 위임하는 c_function 하나를 변경할 수 있습니다. 너무 나쁘지 않은 것 같아요.


1
이것이 나의 첫 접근 방식이지만, 성능상의 이유로 나는 그 코드를 C로 다시 작성해야했습니다.
Bullets

@Bullets, 아마도 많은 일을하지 않을 많은 c 함수를 작성하는 대신 성능을 향상시키는 데 사용할 수있는 여러 가지 옵션이있을 수 있다고 제안합니다. 예를 들어 인스턴스를 만드는 데 많은 비용이들 수 있으므로 개체 풀을 유지 관리하십시오. 나는 당신이 너무 느리다는 것을 알지 못하고 이것을 말하지만. 관심이 없다면,이 접근법에서 정확히 느린 것은 무엇입니까? 경계의 C 쪽에서 상당한 시간을 보내지 않으면 파이썬 (자체)이 진짜 문제라고 생각할 수 없습니다.
Josh Smeaton

성능을 향상시키는 다른 방법이있을 수 있지만 Python보다 C를 사용하는 것이 훨씬 좋습니다. 문제는 총알의 움직임을 계산하고 화면 범위를 벗어날 때를 감지하는 것이 었습니다. 총알의 위치를 ​​계산 pos+v*t한 다음 화면 경계 if x > 800와 비교하는 방법이 있습니다 . 프레임 당 수백 번 이러한 기능을 호출하면 허용 할 수 없을 정도로 느린 것으로 나타났습니다. C에서 할 때 순수 파이썬으로 100 % CPU에서 40fps, 5 % -10 %에서 60fps로
Bullets

@Bullets, 충분히 공정합니다. 데이터를 캡슐화하기 위해 사용한 접근 방식을 계속 사용합니다. bullet의 인스턴스를 add_bullet에 전달하고 필요한 모든 필드를 추출하십시오. 답변을 편집하겠습니다.
Josh Smeaton

@Bullets : Cython을 사용하여 Josh가 제안한 C 함수와 OOP 접근 방식을 결합 할 수 있습니다 . 초기 바인딩이 가능하므로 속도 패널티가 없어야합니다.
jfs


4

정의에서 여러 키워드 인수를 사용하거나 Bullet인스턴스가 함수에 전달 되는 계층을 작성하십시오 .


나는 두 번째 접근법을 제안하려고했다 : 총알 세부 사항을 지정하기 위해 일부 BulletParams ... 클래스를 만드십시오.
존 즈 빙크

이것에 대해 자세히 설명해 주시겠습니까? 글 머리 기호가 다른 클래스 계층 구조를 만들려고했지만 Python이 너무 느려서 작동하지 않습니다. 필요한 총알 수의 움직임을 충분히 빨리 계산할 수 없으므로 C로 해당 부분을 작성해야했습니다. 모든 add_bullet 변형은 해당 C 함수를 호출합니다.
글 머리 기호

4

기본 요구 사항은 가능한 한 두통이 적은 파이썬에서 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
Sprite : 저는 스프라이트입니다
시작 : 0
방향 : 맞습니다

이것은 과부하 2
Sprite : 다른 Sprite
Script입니다.
while x == True : print 'hi'


4

@overload장식은 타입 힌트 (484 PEP)을 첨가 하였다. 이것은 파이썬의 행동을 바꾸지는 않지만, 무슨 일이 일어나고 있는지 이해하고 mypy가 오류를 감지하는 것을 더 쉽게 만듭니다.
참고 : 유형 힌트PEP 484


몇 가지 예를 추가 할 수 있습니까?
gerrit

3

Bullet관련 다형성이 있는 클래스 계층 구조가 갈 길이 라고 생각합니다 . 메타 클래스를 사용하여 기본 클래스 생성자를 효과적으로 오버로드하여 기본 클래스를 호출하면 적절한 서브 클래스 객체가 생성됩니다. 아래는 내가 의미하는 바의 본질을 설명하기위한 샘플 코드입니다.

업데이트

코드는 파이썬 2와 3에서 모두 실행되도록 수정되었습니다. 이것은 파이썬의 명시 적 메타 클래스 구문을 사용하지 않는 방식으로 수행되었으며 두 버전에 따라 다릅니다.

이러한 목적을 달성하기 BulletMetaBase위해 BulletMeta클래스 클래스를 Bullet사용하면 __metaclass__=클래스 속성을 사용하거나 metaclassPython 버전에 따라 키워드 인수 를 사용하지 않고 기본 클래스를 작성할 때 메타 클래스를 명시 적으로 호출하여 클래스 인스턴스를 작성합니다 .

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

흠 이것은 여전히 ​​add_bullet1, add_bullet2 등으로 함수의 이름을 지정하는 멋진 방법입니다.
글 머리 기호

1
@Bullets : 아마도 팩토리 함수를 만드는 약간 정교한 방법 일 수도 있습니다. 이에 대한 좋은 점은 Bullet다른 하위 유형을 추가 할 때마다 기본 클래스 또는 팩토리 기능을 수정하지 않고도 하위 클래스 의 계층 구조를 지원한다는 것 입니다. (물론 C ++ 대신 C를 사용하는 경우 클래스가없는 것 같습니다.) 또한 유형 및 / 또는 숫자를 기반으로 만들 서브 클래스를 스스로 알아내는 더 똑똑한 메타 클래스를 만들 수도 있습니다. 전달 된 인수 (C ++이 오버로드를 지원하는 것처럼).
martineau 2016 년

1
이 상속 아이디어는 나의 첫 번째 옵션이 될 것입니다.
Daniel Möller

3

Python 3.8은 functools.singledispatchmethod를 추가했습니다.

메소드를 단일 디스패치 일반 함수로 변환하십시오.

일반적인 방법을 정의하려면 @singledispatchmethod 데코레이터로 장식하십시오. 디스패치는 첫 번째 non-self 또는 non-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가 가장 외부의 데코레이터 여야합니다. 다음은 neg 메소드가 클래스 바인딩 된 Negator 클래스입니다.

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

정적 메소드, 추상 메소드 등의 다른 유사한 데코레이터에도 동일한 패턴을 사용할 수 있습니다.


2

기본값으로 키워드 인수를 사용하십시오. 예 :

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

곡선 총알 대 직선 총알의 경우는 두 가지 기능을 추가 할 것 : add_bullet_straightadd_bullet_curved.


2

파이썬에서는 오버로드 방법이 까다 롭습니다. 그러나 dict, list 또는 primitive 변수를 전달하는 사용법이있을 수 있습니다.

나는 유스 케이스를 위해 무언가를 시도했는데, 이것은 사람들이 메소드를 오버로드하는 것을 이해하는 데 도움이 될 수 있습니다.

예를 들어 보자.

다른 클래스의 메소드를 호출하는 클래스 오버로드 메소드

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

따라서 메소드 오버로드에서 목록, 사전 또는 기본 변수에 대한 처리가 이루어지고 있습니다.

코드를 사용해보십시오.


2

간단한 데코레이터

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)

사용 사례에 맞게 수정하십시오.

개념의 설명

  • 함수 디스패치 : 같은 이름을 가진 여러 함수가 있습니다. 어느 것을 불러야합니까? 두 가지 전략
  • 정적 / 컴파일 타임 디스패치 ( 일명 "overloading" ). 인수 의 컴파일 타임 유형 에 따라 호출 할 함수를 결정 하십시오. 모든 동적 언어에는 컴파일 타임 유형이 없으므로 정의에 따라 오버로드가 불가능합니다.
  • 동적 / 런타임 디스패치 : 인수 의 런타임 유형 에 따라 호출 할 함수를 결정 합니다. 이것은 모든 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에서는 함수가 호출 된 시점의 실제 유형이므로 "정수 호출, 부동 호출" 이 표시됩니다.


이 예제 는 동적 디스패치 에 대해 어떤 메소드가 호출 되었는지x 또는 정적 디스패치에 대해 두 메소드가 호출 된 순서를 설명하지 않습니다 . 인쇄 설명 print('number called for Integer')등 을 편집하는 것이
좋습니다
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.