Python関数のオーバーロード


212

Pythonがメソッドのオーバーロードをサポートしていないことは知っていますが、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_bullet1add_bullet2add_bullet_with_really_long_name

いくつかの答えに対処するには:

  1. いいえ、遅すぎるため、Bulletクラス階層を作成できません。弾丸を管理するための実際のコードはCにあり、私の関数はC APIのラッパーです。

  2. キーワード引数については知っていますが、あらゆる種類のパラメータの組み合わせをチェックするのは面倒ですが、デフォルト引数は次のように役立ちます acceleration=0


5
(人々が検索エンジンからここに来るため)ここで一つだけのパラメータのために動作しますが、:docs.python.org/3/library/...
leewz

1
これはデフォルト値に適した場所のようです。いくつかをNoneに設定して、それらをチェックするだけです。余分なブール値の影響は無視できるようです
Andrew Scott Evans

default value + if + elseC ++と同じように使用する必要があります。これは、C ++がPythonよりも読みやすくなっている数少ないものの1つです...
Deqing

kwargsが有効な答えではない理由に困惑しています。キーワード引数はあまり醜く速くなるので、使いたくないと言いますが、それは問題の本質です。あなたが多くの議論をしていて、あなたが何を期待していたよりも多くの議論を持っているのでそれが厄介であるなら?どこにも指定せずに多くの引数を使用しますか??? Pythonはマインドリーダーではありません。
微積分

オブジェクトの種類がわからないscript, curve、共通の祖先があるのか、オブジェクトがサポートしているのかはわかりません。ダックタイピングでは、クラスの設計がサポートする必要のあるメソッドを理解するのはあなた次第です。おそらくScript、ある種のタイムステップベースのコールバックをサポートします(ただし、どのオブジェクトを返す必要がありますか?そのタイムステップでの位置?そのタイムステップでの軌跡?)。おそらく、start, direction, speedstart, headto, spead, accelerationの両方が軌道のタイプについて説明したが、再び、それはそれらを解凍し、それらを処理する方法を知っている受信クラスを設計するのはあなた次第です。
smci

回答:


143

あなたが求めているのはマルチディスパッチと呼ばれるものです。さまざまなタイプのディスパッチを示すJulia言語の例を参照してください。

ただし、その前に、なぜオーバーロードがpythonで本当に望んでいないのかをまず取り上げます。

なぜオーバーロードしないのですか?

まず、オーバーロードの概念と、なぜそれがPythonに適用できないのかを理解する必要があります。

コンパイル時にデータ型を区別できる言語を使用する場合、代替案の中から選択することがコンパイル時に行われます。コンパイル時の選択のためにこのような代替関数を作成する行為は、通常、関数のオーバーロードと呼ばれます。(ウィキペディア

Pythonは動的に型付けされた言語であるため、オーバーロードの概念は単純に適用されません。ただし、実行時にそのような代替関数を作成できるため、すべてが失われるわけではありません。

実行時までデータ型の識別を延期するプログラミング言語では、動的に決定される関数引数の型に基づいて、代替関数の選択が実行時に行われる必要があります。この方法で代替実装が選択される関数は、最も一般的にマルチメソッドと呼ばれます。(ウィキペディア

したがって、Pythonでマルチメソッドを実行できるはずです。または、別の方法として呼ばれているように、複数のディスパッチです。

複数発送

マルチメソッドはマルチディスパッチとも呼ばれます

複数のディスパッチまたはマルチメソッドは、一部のオブジェクト指向プログラミング言語の機能であり、複数の引数のランタイム(動的)タイプに基づいて関数またはメソッドを動的にディスパッチできます。(ウィキペディア

Pythonはそのままではこれをサポートしません1が、実際には、それを正確に行うmultipledispatchと呼ばれる優れたpythonパッケージがあります。

解決

次に、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. Python 3は現在、単一のディスパッチを サポートしています
。2。マルチスレッド環境でmultipledispatchを使用しないように注意してください 。


6
マルチスレッド環境での「multipledispatch」の問題は何ですか?サーバーサイドのコードは通常マルチスレッド環境にあるためです!それを掘り出そうとしているだけです!
danzeer 2017年

7
@danzeerスレッドセーフではありませんでした。引数が2つの異なるスレッドによって変更されるのを見ました(つまり、speed別のスレッドが独自の値を設定すると、関数の途中での値が変わる可能性がありますspeed)!!! 犯人が図書館であることに気づくのに長い時間がかかりました。
Andriy Drozdyuk 2017年

108

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 

107
これらはすべて、オーバーロードではなく、可変引数の例として見えます。オーバーロードにより、引数として異なる型に対して同じ関数を使用できます。例:合計(real_num1、real_num2)との和(imaginary_num1、imaginary_num2)は、両方同じ呼び出し構文を持っていますが、実際には入力として2つの異なる種類を期待している、と実装は、内部でも変更する必要があります
エフレン

17
あなたが行く答えを使用して、どの引数が一緒に意味をなすかを発信者にどのように提示しますか?ただ、同じ機能を提供しますが、それははるかに少ないエレガントなAPIの面でも、デフォルト値で、各引数の束を置く
グレッグ・エニス

6
上記のどれもオーバーロードではないため、実装は次のようにパラメーター入力のすべての組み合わせをチェックする(またはパラメーターを無視する)必要がありますif sprite and script and not start and not direction and not speed...。呼び出し側は、利用可能なすべてのパラメータを提供する関数を呼び出すことができるからです。オーバーロード中に、関連するパラメーターの正確なセットを定義します。
Roee Gavirel 2017

5
Pythonがメソッドのオーバーロードをサポートしていると人々が言うのは非常に動揺しています。ありません。「メソッドのオーバーロード」を引用符で囲んでいるという事実は、この事実を認識していることを示しています。ここで述べたようないくつかの手法で同様の機能を得ることができます。しかし、メソッドのオーバーロードには非常に具体的な定義があります。
Howard Swope

メソッドのオーバーロードはpythonの機能ではありませんが、上記のメカニズムを使用して同等の効果を達成できるのは、意図した点だと思います。
rawrが鳴った

93

関数のオーバーロードには「独自のロール」ソリューションを使用できます。これはマルチメソッドに関するGuido van Rossumの記事からコピーされたものです(Pythonでは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 2013年

1
+1これは素晴らしいアイデアです(そしておそらくOPはどうすべきか)--- Pythonでのマルチメソッド実装を見たことがありません。
エスクアロ2014

39

可能なオプションは、ここで詳しく説明されているように、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'

4
なぜこれはもっと投票しないのですか?例が少ないので推測しています... 複数のディスパッチパッケージを使用したOPの問題の解決策を実装する方法の例を含む回答を作成しました。
Andriy Drozdyuk 2015年

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関数を決定します。したがって、Pythonは1つのc関数のみを呼び出す必要があります。その1つのc関数は引数を調べ、他のc関数に適切に委任できます。

基本的には各サブクラスを異なるデータコンテナーとして使用していますが、基本クラスですべての潜在的な引数を定義することにより、サブクラスは何もしない引数を自由に無視できます。

新しいタイプの弾丸が登場したら、ベースにもう1つのプロパティを定義し、追加のプロパティを渡すように1つのpython関数を変更し、引数を調べて適切にデリゲートする1​​つのc_functionを変更できます。悪くないですね。


1
それは私の最初のアプローチでしたが、私はCでそのコードを書き換えなければならなかったパフォーマンス上の理由のために
箇条書き

@Bullets、私はおそらくパフォーマンスを向上させるために利用できるさまざまなオプションがいくつかあることをお勧めします。たとえば、インスタンスの作成にはコストがかかる可能性があるため、オブジェクトプールを維持します。私はあなたが遅すぎるとわかったものを知らずにこれを言いますが。興味深いことに、このアプローチの何が遅いのですか?境界のC側でかなりの時間が費やされない限り、Python(それ自体)が本当の問題であるとは思えません。
ジョシュ・スミートン

パフォーマンスを改善する方法は他にもあるかもしれませんが、PythonよりもCの方がはるかに優れています。問題は、弾丸の動きを計算し、画面の境界を超えたときを検出することでした。弾丸の位置を計算pos+v*tし、画面の境界if x > 800と比較する方法などがありました。これらの関数をフレームごとに数百回呼び出すと、許容できないほど遅いことがわかりました。C.で行われるとき、それは5%-10%と60 fpsに純粋なのpythonと100%のCPUで40 fpsのようなものだった
箇条書き

@弾丸、十分に公正です。データをカプセル化するために使用したアプローチを引き続き使用します。bulletのインスタンスをに渡し、add_bullet必要なすべてのフィールドを抽出します。回答を編集します。
ジョシュ・スミートン

@弾丸:C関数と、Cythonを使用し Joshが提案したOOPアプローチを組み合わせることができます。それは早期バインディングを可能にするので、速度のペナルティはありません。
jfs


4

定義で複数のキーワード引数を使用するかBullet、インスタンスが関数に渡される階層を作成してください。


2つ目のアプローチを提案します。BulletParams...クラスをいくつか作成して、弾丸の詳細を指定します。
ジョンズウィンク、2011年

これについて詳しく説明できますか?別の箇条書きでクラス階層を作成しようとしましたが、Pythonが遅すぎるため、これは機能しません。必要な数の弾丸の動きを十分に速く計算できないため、その部分をCで記述する必要がありました。すべてのadd_bulletバリアントは、対応するC関数を呼び出すだけです。
弾丸

4

あなたの基本的な要件は、PythonでC / C ++に似た構文をできるだけ頭痛のない状態にすることだと思います。私はアレクサンドル・ポルエクトフの答えが好きでしたが、それはクラスでは機能しません。

以下はクラスで機能するはずです。キーワード以外の引数の数で区別することで機能します(ただし、タイプによる区別はサポートしていません)。

class TestOverloading(object):
    def overloaded_function(self, *args, **kwargs):
        # Call the function that has the same number of non-keyword arguments.  
        getattr(self, "_overloaded_function_impl_" + str(len(args)))(*args, **kwargs)
    
    def _overloaded_function_impl_3(self, sprite, start, direction, **kwargs):
        print "This is overload 3"
        print "Sprite: %s" % str(sprite)
        print "Start: %s" % str(start)
        print "Direction: %s" % str(direction)
        
    def _overloaded_function_impl_2(self, sprite, script):
        print "This is overload 2"
        print "Sprite: %s" % str(sprite)
        print "Script: "
        print script

そしてそれはこのように簡単に使用できます:

test = TestOverloading()

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

出力:

これはオーバーロードです3
スプライト:私はスプライトです
開始:0
方向:右

これはオーバーロード2の
スプライトです:私は別のスプライト
スクリプトです:
while x == True:print 'hi'



3

Bullet関連するポリモーフィズムを備えたクラス階層が進むべき道だと思います。基本クラスを呼び出すと適切なサブクラスオブジェクトが作成されるように、メタクラスを使用して基本クラスコンストラクターを効果的にオーバーロードできます。以下は、私の意味の本質を説明するためのサンプルコードです。

更新しました

関連性を保つために、Python 2と3の両方で実行するようにコードが変更されました。これは、2つのバージョン間で異なるPythonの明示的なメタクラス構文の使用を回避する方法で行われました。

その目的を達成するために、ベースクラスの作成時にメタクラスを明示的に呼び出すことによりBulletMetaBaseBulletMetaクラスのインスタンスが作成されますBullet(Pythonのバージョンに応じて、__metaclass__=class属性を使用するか、metaclassキーワード引数を使用するのではありません)。

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
@弾丸:たぶんそうなのかもしれないし、ファクトリ関数を作成するための少し複雑な方法かもしれない。これの良い点は、Bullet別のサブタイプを追加するたびに基本クラスまたはファクトリー関数を変更する必要なく、サブクラスの階層をサポートすることです。(もちろん、C ++ではなくCを使用している場合は、クラスがないと思います。)タイプや番号に基づいて作成するサブクラスを独自に計算する、よりスマートなメタクラスを作成することもできます。渡される引数の数(C ++がオーバーロードをサポートするために行うように)。
martineau

1
この継承の考え方も私の最初の選択肢です。
ダニエル・メーラー

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が最も外側のデコレータである必要があります。以下は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など)に使用できます。


2

デフォルトでキーワード引数を使用します。例えば

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

直線弾丸と曲線弾丸の場合、2つの関数を追加add_bullet_straightadd_bullet_curvedます。


2

Pythonでは、メソッドのオーバーロードはトリッキーです。ただし、dict、list、またはプリミティブ変数を渡す使用法がある可能性があります。

私はユースケースで何かを試しましたが、これは、メソッドをオーバーロードする人々を理解するのに役立ちます。

あなたの例を見てみましょう:

異なるクラスからメソッドを呼び出すクラスオーバーロードメソッド。

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)

ユースケースに合わせて変更してください。

概念の明確化

  • 関数ディスパッチ:同じ名前の関数が複数あります。どちらを呼び出す必要がありますか?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として宣言されているため、「呼び出された番号」が2回表示されNumberます。動的ディスパッチを使用すると、「整数が呼び出され、浮動が呼び出されました」と表示されますx。これは、関数が呼び出されたときの実際のタイプであるためです。


この例では、決定的に示していないその上で呼ばれましたメソッドxの動的ディスパッチのために、またオーダーした両方の方法は、静的なディスパッチのために呼ばれました。あなたがprint文を編集オススメprint('number called for Integer')など
SMCI
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.