问题
As a simple example take a class Polynomial
class Polynomial(object):
def __init__(self, coefficients):
self.coefficients = coefficients
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.
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) Monkey-patching, i.e.
Polynomial.evaluate = horner.evaluate_polynomial
# or Polynomial.__call__ = horner.evaluate_polynomial
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 + 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
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?
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?
回答1:
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.
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.
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
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...
来源:https://stackoverflow.com/questions/18466214/should-a-plugin-adding-new-instance-methods-monkey-patch-or-subclass-mixin-and-r