gpt4 book ai didi

python - 插件是否应该添加新的实例方法 monkey-patch 或子类/mixin 并替换父类?

转载 作者:行者123 更新时间:2023-11-28 19:23:40 25 4
gpt4 key购买 nike

作为一个简单的例子,拿一个类 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 的多项式,其中列表 系数 = ( a_0, a_1, ..., a_n) 存储这些系数。

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

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

果然,猴子修补是最短的(特别是因为我没有包含任何检查 à la hasattr(Polynomial, 'evaluate'),但类似地子类应该调用 super() then...),但它是最 Pythonic 的吗?或者有其他更好的选择吗?

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

最佳答案

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

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

总而言之,除了少数异常(exception),将方法猴子修补到原始类中(最好在修补之前使用 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

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

关于python - 插件是否应该添加新的实例方法 monkey-patch 或子类/mixin 并替换父类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18466214/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com