[python] 파이썬 함수 오버로딩

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

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



답변

당신이 요구하는 것을 다중 디스패치 라고 합니다 . 다양한 유형의 디스패치를 ​​보여주는 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을 멀티 스레드 환경에서 또는 당신이 이상한 행동을 얻을 것이다.


답변

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

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 


답변

기능 과부하에 “자신의 롤”솔루션을 사용할 수 있습니다. 이것은 다중 방법에 대한 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는 지원되지 않습니다.
  • 가져 오기시에 새로운 기능을 등록해야합니다. 스레드로부터 안전하지 않습니다.

답변

가능한 옵션은 다음과 같이 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'


답변

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)


답변

이 유형의 동작은 일반적으로 다형성을 사용하여 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 하나를 변경할 수 있습니다. 너무 나쁘지 않은 것 같아요.


답변

키워드 args전달합니다 .

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