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)存储这些系数的形式的多项式。

然后,一个插件模块horner可以提供一个函数horner.evaluate_polynomial(p, x)来评估 value 的实例Polynomial,即返回 的值。但是,与其以这种方式调用函数,不如调用(或更直观地通过)会更好。但是应该怎么做呢?pxp(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) Mixin + 替换,即

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

果然,monkey-patching 是最短的一个(特别是因为我没有包含任何 check à la hasattr(Polynomial, 'evaluate'),但类似的子类应该调用super()then...),但它是最 Pythonic 的吗?还是有其他更好的选择?

特别是考虑到多个插件提供相同功能的可能性,例如zeros使用numpy或自制二分法,当然应该只使用一个实现插件,哪种选择可能更不容易出错?

4

1 回答 1

0

将函数直接修补到原始类而不是替换它的一个可能也是最重要的属性是,在加载插件之前对原始类的 / 实例的引用现在也将具有新属性。在给定的示例中,这很可能是所需的行为,因此应该使用。

然而,可能存在其他情况,猴子补丁以与其原始实现不兼容的方式修改现有方法的行为,并且修改类的先前实例应使用原始实现。当然,这不仅是罕见的而且也是糟糕的设计,但你应该记住这种可能性。由于一些令人费解的原因,代码甚至可能依赖于没有添加猴子补丁的方法,尽管在这里似乎很难想出一个非人为的例子。

总之,除了少数例外,将方法猴子修补到原始类中(最好hasattr(...)在修补之前进行检查)应该是首选方式。


编辑我目前的做法:创建一个子类(用于更简单的代码完成和修补),然后使用以下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

悬而未决的问题是各个子类的模块是否应该直接调用patchimport 或者应该手动完成。我也在考虑为这样的修补子类编写一个装饰器或元类......

于 2013-08-27T13:32:57.623 回答