在解决问题时包装对象以扩展/添加功能

zwe*_*nde 5 python design-patterns

在Python中,我已经看到了使用holding或wrap来扩展对象或类的功能而不是继承的建议.特别是,我认为Alex Martelli在他的Python Design Patterns谈话中谈到了这一点.我已经在库中看到了这种用于依赖注入的模式,比如pycontainer.

我遇到的一个问题是,当我必须与使用isinstance反模式的代码进行交互时 ,此模式会失败,因为保持/包装对象未通过isinstance测试.如何设置保持/包装对象以绕过不必要的类型检查?一般可以这样做吗?在某种意义上,我需要类似于签名保留函数装饰器的类实例(例如,simple_decorator或Michele Simionato的装饰器).

资格:我并不断言所有isinstance用法都不合适; 几个答案对此有所了解.也就是说,应该认识到isinstance使用对对象交互构成了重大限制 - 它迫使继承成为多态性的来源,而不是行为.

关于这究竟是什么/为什么这是一个问题似乎有些混乱,所以让我提供一个简单的例子(广泛地从pycontainer中解除).假设我们有一个Foo类,还有一个FooFactory.为了举例,我们假设我们希望能够实例化记录每个函数调用的Foo对象,或者不想 - 想想AOP.此外,我们希望在不以任何方式修改Foo类/源的情况下这样做(例如,我们实际上可能正在实现一个可以动态地向任何类实例添加日志记录功能的通用工厂).对此的第一次尝试可能是:

class Foo(object):
    def bar():
        print 'We\'re out of Red Leicester.'

class LogWrapped(object):
    def __init__(self, wrapped):
        self.wrapped = wrapped
    def __getattr__(self, name):
        attr = getattr(self.wrapped, name)
        if not callable(attr):
            return attr
        else:
            def fun(*args, **kwargs):
                print 'Calling ', name
                attr(*args, **kwargs)
                print 'Called ', name
            return fun

class FooFactory(object):
    def get_foo(with_logging = False):
        if not with_logging:
            return Foo()
        else:
            return LogWrapped(Foo())

foo_fact = FooFactory()
my_foo = foo_fact.get_foo(True)
isinstance(my_foo, Foo) # False!
Run Code Online (Sandbox Code Playgroud)

有可能你可能想要以这种方式做事情(使用装饰器等),但请记住:

  • 我们不想触及Foo类.假设我们正在编写可供我们尚不了解的客户使用的框架代码.
  • 重点是返回一个基本上是Foo的对象,但具有附加功能.对于任何其他期望Foo的客户端代码,它应该尽可能地看起来是Foo.因此渴望解决问题isinstance.
  • 是的,我知道我不需要工厂阶级(先发制人地在这里为自己辩护).

nik*_*kow 3

如果您依赖的库代码使用isinstance并依赖于继承,为什么不遵循此路线呢?如果您无法更改库,那么最好与其保持一致。

我还认为 存在合法用途,并且随着 2.6 中抽象基类isinstance的引入,这一点已得到官方承认。在某些情况下,这确实是正确的解决方案,而不是带有异常或使用异常的鸭子类型。isinstancehasattr

如果由于某种原因你真的不想使用继承,那么一些肮脏的选项:

  • 您可以使用实例方法仅修改类实例。您为new.instancemethod实例创建包装方法,然后调用原始类中定义的原始方法。这似乎是唯一既不修改原始类也不定义新类的选项。

如果您可以在运行时修改类,则有很多选择:

  • 使用运行时混合,即只需将一个类添加到__base__类的属性中。但这更多的是用于添加特定的功能,而不是用于你不知道需要包装什么的情况下乱包装。

  • Dave 答案中的选项(Python >= 2.6 或元类中的类装饰器)。

编辑:对于您的具体示例,我想只有第一个选项有效。LogFoo但我仍然会考虑为特定的事情(例如日志记录)创建或选择完全不同的解决方案的替代方案。