6

関数の元のコードを変更せずに関数が操作できる型をユーザーが拡張できるようにする Python での適切な方法は何ですか?

my_module.foo()もともと型で動作するように書かれた関数を持つモジュールがあるとしfloatます。mpmathここで、元のモジュールのコードを変更せずに、同じ関数が任意精度の浮動小数点数でも動作できるようにしたいと考えています。

C++ では、余分なオーバーロードを追加します (または、ヘルパー構造体を使用したテンプレートの特殊化の策略)。my_module.foo()ユーザーが独自のカスタムフックを内部に追加できるようにするには、元のコードをどのように構成すればよいですか?

これを達成する方法はいくつか考えられますが、初心者の Python プログラマーとして、それらのほとんどは恐ろしいものになると確信しています :)

編集:これまでのすべての回答に感謝します。

おそらく、重要な要件の 1 つは、自分で定義していない型に対処できることであることを明確にする必要があります。たとえば、モジュールでジェネリック関数をコーディングしようとしている場合、組み込み型、型、sympy シンボリック型などcosを呼び出したいと思います。もちろん、ディスパッチ ロジックがモジュールの実装に含まれないようにしたいと考えています。math.cosmpmath.cosmpfsympy.coscos

4

3 に答える 3

7

これを行うには、次の 2 つの方法があります。

  • 委任。すでに Python で使用されています。最も Pythonic です。組み込み型では機能しません。まさにあなたが探しているものではありません。
  • シングルディスパッチ。まだ PEP です。組み込み型で機能します。まさにあなたが探しているもの。

委任

通常、操作対象のオブジェクトに責任を委任し、関数にロジックを実装しません。

次に例を示しますlen。の実装lenは非常に簡単です。

def len(obj):
    return obj.__len__()

異なるタイプ ( strlisttuple...) には異なる実装がありますが、それらはすべて同じ関数で動作します。

で動作する独自の型を定義したい場合は、次のようにしlenます。

class MyTypeOfLength3(object):
    def __len__(self):
        return 3

o = MyTypeOfLength3()
print len(o) 
# 3

あなたの場合、次のようなものを実装しますlen

(注: これは の実際のコードではありませんが、lenほぼ同等です。)

シングルディスパッチ

もちろん、場合によっては、それが実用的でないこともあります。それがあなたのケースなら、"Single Dispatch" PEP 443がおそらくあなたが探しているものです。

探しているものを実現する新しいデコレータを提案します:

>>> from functools import singledispatch
>>> @singledispatch
... def fun(arg, verbose=False):
...     if verbose:
...         print("Let me just say,", end=" ")
...     print(arg)
...
>>> @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)

関数をそのように定義したら、 を呼び出すことができ、Python はデフォルトの実装へのフォールバックの正しい実装 (またはここ)fun(something)を見つけます。intlistdef fun(...): ...

したがって、元の関数を装飾するだけで済み、ユーザーは独自の型を追加できます。

注: コメントで指摘されているように、singledispatch既に Python で実装されています。pkgutil.simplegeneric

于 2013-09-23T10:58:29.970 に答える
1

PEP 443 - Python 2.6 で追加された抽象基底クラスを代わりに使用することで、単一ディスパッチのジェネリック関数が実装されるのを待たずに、やりたいことを実行できます。これらを使用すると、「仮想」メタ クラスを作成し、既存のコードを変更したりモンキー パッチを適用したりすることなく、その場で任意のサブクラスをそれらに追加できます。モジュールは、このメタクラスに登録された型を使用して、何をすべきかを判断できます。他のタイプのあなた (または作成者) は、必要に応じてそれらを登録できます。

概念を示すコード例を次に示します。

import abc

class Trigonometric(object):
    __metaclass__ = abc.ABCMeta
    _registry = {}

    @classmethod
    def register(cls, subclass, cos_func, sin_func):
        cls.__metaclass__.register(cls, subclass)
        if subclass not in cls._registry:  # may or may not want this check...
            cls._registry[subclass] = {'cos': cos_func, 'sin': sin_func}

    @classmethod
    def call_func(cls, func_name, n):
        try:
            return cls._registry[n.__class__][func_name](n)
        except KeyError:
            raise RuntimeError(
                "Either type {} isn't registered or function {}() "
                "isn't known.".format(n.__class__.__name__, func_name))

# module-level functions
def cos(n):
    return Trigonometric.call_func('cos', n)

def sin(n):
    return Trigonometric.call_func('sin', n)

if __name__ == '__main__':
    # avoid hardcoding this module's filename into the source
    import sys
    my_module = sys.modules[__name__]  # replaces import my_module

    # register the built-in float type
    import math
    print 'calling Trigonometric.register(float)'
    Trigonometric.register(float, math.cos, math.sin)

    # register mpmath's arbitrary-precision mpf float type
    from mpmath import mp
    print 'calling Trigonometric.register(mp.mpf)'
    Trigonometric.register(mp.mpf, mp.cos, mp.sin)

    f = 1.0
    print 'isinstance(f, Trigonometric):', isinstance(f, Trigonometric)
    print 'my_module.cos(f):', my_module.cos(f), my_module.sin(f)

    v = mp.mpf(1)
    print 'isinstance(v, Trigonometric):', isinstance(v, Trigonometric)
    print 'my_module.cos(v):', my_module.cos(v), my_module.sin(v)
于 2013-09-23T17:19:59.923 に答える