插件应该添加新的instancemethods monkeypatch或子类/mixin并替换父类吗?

2024-06-28 19:25:09 发布

您现在位置:Python中文网/ 问答频道 /正文

作为一个简单的例子,以一个类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)来计算Polynomial实例{},即返回{}的值。但是,与其那样调用函数,不如通过^{}调用p.evaluate(x)(或者更直观地说,p(x)通过^{})更好。但是应该怎么做呢?在

a)猴子修补,即

^{pr2}$

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

果然,monkey-patching是最短的一个(尤其是我没有包括check-alahasattr(Polynomial, 'evaluate'),但是类似地,一个子类应该调用super()),但是它是最具python特性的吗?或者还有其他更好的选择吗?在

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


Tags: self插件returnobjectdefclass例子evaluate
1条回答
网友
1楼 · 发布于 2024-06-28 19:25:09

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

但是,也可能存在其他情况,即monkey patch以与原始实现不兼容的方式修改现有方法的行为,并且修改后的类的以前实例应该使用原始实现。当然,这不仅是罕见的,而且是糟糕的设计,但你应该记住这种可能性。由于一些复杂的原因,代码甚至可能依赖于monkey-patch-added方法的缺失,尽管这里似乎很难想出一个非人工的例子。在

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


编辑我当前的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

悬而未决的问题是,相应的子类的module是应该在导入时直接调用patch,还是应该手动调用。我也在考虑为这样一个修补子类编写一个装饰器或元类。。。在

相关问题 更多 >