插件是否应该添加新的实例方法,monkey-patch或子类/mixin并替换其父级? [英] Should a plugin adding new instance-methods monkey-patch or subclass/mixin and replace the parent?

查看:73
本文介绍了插件是否应该添加新的实例方法,monkey-patch或子类/mixin并替换其父级?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

作为一个简单的例子,使用一个类Polynomial

As a simple example take a class 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)存储这些系数.

for polynomials of the form p(x) = a_0 + a_1*x + a_2*x^2 + ... + a_n*x^n where the list coefficients = (a_0, a_1, ..., a_n) stores those coefficients.

然后,一个插件模块horner可以提供函数horner.evaluate_polynomial(p, x)来评估值为xPolynomial实例p,即返回p(x)的值.但是,与其以这种方式调用函数,不如通过 __call__ )会更好.但是应该怎么做?

One plugin-module horner could then provide a function horner.evaluate_polynomial(p, x) to evaluate a Polynomial instance p at value x, i.e. return the value of p(x). But instead of calling the function that way, a call to p.evaluate(x) (or more intuitively p(x) via __call__) would be better. But how should it be done?

a)猴子补丁,即

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

b)子类化并替换该类,即

b) Subclassing and replacing the class, i.e.

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

c)Mixin +替换,即

c) Mixin + Replacing, i.e.

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

果然,猴子补丁是最短的补丁(特别是因为我没有包含任何检查àla hasattr(Polynomial, 'evaluate'),但是类似地,子类也应该调用super()然后...),但这是最Python的了. ?还是还有其他更好的选择?

Sure enough, monkey-patching is the shortest one (especially since I didn't include any check à la hasattr(Polynomial, 'evaluate'), but similarly a subclass should call super() then...), but is it the most Pythonic? Or are there other better alternatives?

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

Especially considering the possibility for multiple plugins providing the same function, e.g. zeros either using numpy or a self-made bisection, where of course only one implementing plugin should be used, which choice might be less error-prone?

推荐答案

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

The one and probably most important property of monkey-patching the function directly to the original class instead of replacing it is that references to / instances of the original class before the plugin was loaded will now also have the new attribute. In the given example, this is most likely desired behaviour and should therefore be used.

但是,在其他情况下,猴子补丁会以与原始实现不兼容的方式修改现有方法的行为,并且修改后的类的先前实例应使用原始实现.当然,这不仅罕见,而且设计不好,但您应牢记这种可能性.由于某些令人费解的原因,代码甚至可能依赖于添加了猴子补丁的方法的不存在,尽管在​​这里似乎很难提出一个非人工的例子.

There may however be other situations where a monkey-patch modifies the behaviour of an existing method in a way that is not compatible with its original implementation and previous instances of the modified class should use the original implementation. Granted, this is not only rare but also bad design, but you should keep this possibility in mind. For some convoluted reasons code might even rely on the absence of a monkey-patch-added method, though it seems hard to come up with a non-artificial example here.

总而言之,除了少数例外,猴子是将方法修补到原始类中的方法(最好在修补之前使用hasattr(...)检查).

In summary, with few exceptions monkey-patching the methods into the original class (preferably with a hasattr(...) check before patching) should be the preferred way.

编辑,我目前的工作:创建一个子类(用于更简单的代码完成和修补),然后使用以下patch(patching_class, unpatched_class)方法:

edit My current go: create a subclass (for simpler code completion and patching) and then use the following patch(patching_class, unpatched_class) method:

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还是应手动完成.我也在考虑为此类修补子类编写装饰器或元类...

The open question is whether the respective subclass' module should directly call patch on import or that should be done manually. I'm also considering writing a decorator or metaclass for such a patching subclass...

这篇关于插件是否应该添加新的实例方法,monkey-patch或子类/mixin并替换其父级?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆