파이썬이 메소드 오버로드를 지원하지 않는다는 것을 알고 있지만 훌륭한 파이썬 방식으로는 해결할 수없는 문제가 발생했습니다.
캐릭터가 다양한 총알을 쏠 필요가있는 게임을 만들고 있는데이 총알을 만들기 위해 다른 기능을 어떻게 작성합니까? 예를 들어 주어진 속도로 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
.
일부 답변을 해결하려면 :
-
아니오 Bullet 클래스 계층 구조는 너무 느리므로 작성할 수 없습니다. 글 머리 기호를 관리하는 실제 코드는 C이고 내 함수는 C API를 래퍼합니다.
-
키워드 인수에 대해서는 알고 있지만 모든 종류의 매개 변수 조합을 확인하는 것은 성가신 일이지만 기본 인수는 다음과 같이 도움이됩니다.
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 하나를 변경할 수 있습니다. 너무 나쁘지 않은 것 같아요.