【问题标题】:How to wrap child methods multiple times in an inheritance tree?如何在继承树中多次包装子方法?
【发布时间】:2016-08-22 06:03:16
【问题描述】:

在一个框架中,我经常想提供一个基类,该基类是框架用户的子类。基类提供对基类的受控访问。实现此目的的一种方法是提供具有不同名称的未实现方法,例如通过添加下划线作为前缀:

class Base:

    def method(self, arg):
        # ...
        result = self._method(arg)
        # ...
        return result

    def _method(self, arg):
        raise NotImplementedError

但是,此方案仅适用于一级继承。对于更多级别,不同的方法名称使得很难对正在发生的事情进行概览。此外,框架用户必须根据他选择的基类覆盖不同的方法:

class Base:

    def method(self, arg):
        # ...
        result = self._method_sub(arg)
        # ...
        return result

    def _method_sub(self, arg):
        raise NotImplementedError

class Intermediate(Base):

    def _method_sub(self, arg):
        # ...
        result = self._method_sub_sub(arg)
        # ...
        return result

    def _method_sub_sub(self, arg):
        raise NotImplementedError

当基方法需要访问子方法的返回值时,调用超方法无济于事。我觉得面向对象有点缺陷,缺少一个允许将调用转发到子类的child 关键字。有什么解决方案可以解决这个问题?

【问题讨论】:

  • @FujiApple 假设 Python 将具有最终方法。我将如何允许子类实现其中的一部分?我的示例中的方法并不是最终的。子类提供行为,基类包装它。基本方法可以向参数和返回值添加行为或范围检查。
  • 我很难理解这个问题 - 我 认为 我可能知道你想要什么,但我真的不确定:有 Base.method() 换行 Base.methodSub() 然后有Intermediate 提供 methodSub() 实现。然后,父 Base.method() 可以对 methodSub() 的子实现进行任何前/后验证。如果这听起来不错,我可以举一个例子,如果不是,我会退出并让其他人弄清楚:)
  • @FujiApple 没错。但是BaseIntermediate 都是框架提供的抽象类。对于框架用户扩展Intermediate,该类必须提供Intermediate.methodSubSub() 等等。这不好,因为框架用户需要覆盖不同的方法,这取决于他选择的基类。
  • 我根据我对您的问题的理解(可能仍有缺陷)在下面发布了一个答案,因为它太长了无法放入 cmets。如果我仍然错过了这一点,请告诉我,我会删除它。

标签: python python-3.x inheritance design-patterns frameworks


【解决方案1】:

这能满足你的需求吗?

import abc

class Base(object):
    __metaclass__ = abc.ABCMeta

    def calculate(self):
        result = self.doCalculate()
        if 3 < result < 7: # do whatever validation you want
            return result
        else:
            raise ValueError()

    @abc.abstractmethod
    def doCalculate(self):
        pass

class Intermediate(Base):
    __metaclass__ = abc.ABCMeta

class Leaf(Intermediate):
    def doCalculate(self):
        return 5

leaf = Leaf()
print leaf.calculate()

【讨论】:

  • 是建造者还是抽象工厂设计模式?
  • 我会说它最接近 template method GoF 模式
  • 是的,这就是解决方案。由于父方法无法获取子方法的返回值,只需将其委托给另一个方法即可。
  • 感谢您的回答。但仍然没有抓住重点。 BaseIntermediate 都需要各自子方法的返回值。
【解决方案2】:

我认为这个问题的重点是中间类中可能发生行为扩展的不同点。中间类显然应该在这里细化“control”部分。

第一种解决方案

大多数情况下,这可以通过重写“安全”方法以经典方式完成 - 特别是当“Base 和 Intermediate 都是框架提供的抽象类”时,可以这样组织事物。 完成铲子工作的最终“愚蠢”实现类覆盖了不安全的方法。

想想这个例子:

class DoublePositive:
    def double(self, x):
        assert x > 0
        return self._double(x)
    def _double(self, x):
        raise NotImplementedError

class DoubleIntPositive(DoublePositive):
    def double(self, x):
        assert isinstance(x, int)
        return DoublePositive.double(self, x)

class DoubleImplementation(DoubleIntPositive):
    def _double(self, x):
        return 2 * x

第二个解决方案

调用虚拟子类方法,从而以非经典方式在“内部”执行点进行行为扩展,可以通过 Python 中的自省来完成 - 通过降低类 __bases__ 或方法带有辅助函数的解析顺序__mro__

例子:

def child_method(cls, meth, _scls=None):
    scls = _scls or meth.__self__.__class__
    for base in scls.__bases__:
        if base is cls:
            cmeth = getattr(scls, meth.__name__, None)
            if cmeth.__func__ is getattr(cls, meth.__name__, None).__func__:
                return child_method(scls, meth)  # next child
            if cmeth:
                return cmeth.__get__(meth.__self__)
    for base in scls.__bases__:
        r = child_method(cls, meth, base)  # next base
        if r is not None:
            return r
    if _scls is None:
        raise AttributeError("child method %r missing" % meth.__name__)    
    return None

class Base(object):
    def double(self, x):
        assert x > 0
        return Base._double(self, x)
    def _double(self, x):
        return child_method(Base, self._double)(x)        
class Inter(Base):
    def _double(self, x):
        assert isinstance(x, float)
        return child_method(Inter, self._double)(x)
class Impl(Inter):
    def _double(self, x):
        return 2.0 * x

这里的辅助函数 child_method() 因此与 Python 的 super() 相反。


第三种解决方案

如果调用应该是灵活的链式调用,则可以将事物显式地组织为一种处理程序链。想想__init__() 链中的self.addHandler(self.__privmeth)——或者甚至通过一个棘手的元类。研究例如urllib2 处理程序链。

【讨论】:

  • 感谢您的回答。虽然您的第一个解决方案非常干净,但这意味着 Intermediate 不能扩展 Base 的行为,它必须完全替换它。调用super() 将不起作用,因为基类调用了未实现的用户方法。你能想办法解决这个问题吗?您的第二个解决方案实际上会起作用。我希望不要解决继承问题,但如果这是唯一的方法,我会接受。
  • 在第一个解决方案中,中间类(也是抽象的)也“扩展”基本行为。只是在其他点:通过在调用基类之前和之后做事来“外部”。 (&lt;BASECLASS&gt;.double() 在这里只是显式的super() 调用)而通过自省辅助函数的子方法调用解决方案在其他点扩展:“内部”。两种解决方案加上在中间类中提供默认实现的选项,也可以在中间类中混合使用:当它同时覆盖安全和不安全方法时。所以它可以将行为扩展到 4 个点。
  • 感谢您对答案的澄清和更新!
猜你喜欢
  • 2014-09-06
  • 2014-01-13
  • 2019-07-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多