3

簡単な例として、クラスを取るPolynomial

class Polynomial(object):
     def __init__(self, coefficients):
         self.coefficients = coefficients

p(x) = a_0 + a_1*x + a_2*x^2 + ... + a_n*x^nリストcoefficients = (a_0, a_1, ..., a_n)がそれらの係数を格納する形式の多項式の場合。

1 つのプラグイン モジュールは、 valueでインスタンスを評価するhorner関数を提供できます。つまり、 の値を返します。しかし、そのように関数を呼び出す代わりに、への呼び出し(またはより直感的に を介した呼び出し) の方が適切です。しかし、それはどのように行われるべきですか?horner.evaluate_polynomial(p, x)Polynomialpxp(x)p.evaluate(x)p(x)__call__

a) モンキーパッチ、すなわち

Polynomial.evaluate = horner.evaluate_polynomial
# or Polynomial.__call__ = horner.evaluate_polynomial

b) クラスのサブクラス化と置換、つまり

orgPolynomial = Polynomial
class EvaluatablePolynomial(Polynomial):
    def evaluate(self, x):
        return horner.evaluate_polynomial(self, x)
Polynomial = EvaluatablePolynomial

c) ミックスイン + 置換、つまり

orgPolynomial = Polynomial
class Evaluatable(object):
    def evaluate(self, x):
        return horner.evaluate_polynomial(self, x)
class EvaluatablePolynomial(Polynomial, Evaluatable):
    pass
Polynomial = EvaluatablePolynomial

案の定、モンキー パッチは最短のものです (特に、 check à la を含めなかったのでhasattr(Polynomial, 'evaluate')、同様にサブクラスが呼び出す必要がありますsuper()...) が、それは最も Pythonic ですか? または、他のより良い代替手段はありますか?

特に、同じ機能を提供する複数のプラグインの可能性を考慮すると、たとえばzeros、使用するnumpyか自作の二分法など、もちろん実装プラグインを 1 つだけ使用する必要がありますが、どちらを選択するとエラーが発生しにくくなるでしょうか?

4

1 に答える 1

0

関数を置き換えるのではなく、元のクラスに直接モンキー パッチを適用することのおそらく最も重要なプロパティの 1 つは、プラグインが読み込まれる前の元のクラスの / インスタンスへの参照も新しい属性を持つようになることです。与えられた例では、これは望ましい動作である可能性が最も高いため、使用する必要があります。

ただし、モンキー パッチが元の実装と互換性のない方法で既存のメソッドの動作を変更し、変更されたクラスの以前のインスタンスが元の実装を使用する必要があるという他の状況があるかもしれません。確かに、これはまれであるだけでなく、デザインも良くありませんが、この可能性を念頭に置いておく必要があります。いくつかの複雑な理由から、コードはモンキー パッチが追加されたメソッドの不在に依存することさえあるかもしれませんが、ここで非人工的な例を思い付くのは難しいようです。

要約すると、いくつかの例外を除いて、メソッドを元のクラスにモンキー パッチすること (できれhasattr(...)ばパッチを適用する前にチェックを行う) が推奨される方法です。


edit My current go: サブクラスを作成し (コード補完とパッチ適用を簡素化するため)、次のpatch(patching_class, unpatched_class)メソッドを使用します。

import logging
from types import FunctionType, MethodType


logger = logging.getLogger(__name__)
applied_patches = []


class PatchingError(Exception):
    pass


def patch(subcls, cls):
    if not subcls in applied_patches:
        logger.info("Monkeypatching %s methods into %s", subcls, cls)
        for methodname in subcls.__dict__:
            if methodname.startswith('_'):
                logger.debug('Skipping methodname %s', methodname)
                continue
            # TODO treat modified init
            elif hasattr(cls, methodname):
                raise PatchingError(
                    "%s alrady has methodname %s, cannot overwrite!",
                    cls, methodname)
            else:
                method = getattr(subcls, methodname)
                logger.debug("Adding %s %s", type(method), methodname)
                method = get_raw_method(methodname, method)
                setattr(cls, methodname, method)
        applied_patches.append(subcls)


def get_raw_method(methodname, method):
    # The following wouldn't be necessary in Python3...
    # http://stackoverflow.com/q/18701102/321973
    if type(method) == FunctionType:
        logger.debug("Making %s static", methodname)
        method = staticmethod(method)
    else:
        assert type(method) == MethodType
        logger.debug("Un-bounding %s", methodname)
        method = method.__func__
    return method

未解決の問題は、それぞれのサブクラスのモジュールがpatchインポート時に直接呼び出す必要があるのか​​、それとも手動で行う必要があるのか​​ということです。また、そのようなパッチ適用サブクラスのデコレータまたはメタクラスを作成することも検討しています...

于 2013-08-27T13:32:57.623 に答える