代码之家  ›  专栏  ›  技术社区  ›  zweiterlinde

在处理IsInstance时包装对象以扩展/添加功能

  •  5
  • zweiterlinde  · 技术社区  · 15 年前

    在Python中,我看到了使用holding或wrapping扩展对象或类的功能的建议,而不是继承。特别是,我认为亚历克斯·马泰利在他的 Python Design Patterns 说话。我在依赖注入的库中看到过这种模式,比如 pycontainer .

    我遇到的一个问题是,当我必须与使用 isinstance anti-pattern ,此模式失败,因为保留/包装对象 isinstance 测试。如何设置保持/包装对象以绕过不必要的类型检查?这可以用一般的方法吗?在某种意义上,我需要类实例类似于保留签名的函数修饰符(例如, simple_decorator 或米歇尔西莫纳托的 decorator )

    一个条件:我不是在断言 实例 使用不当;有几个答案对这一点很有帮助。也就是说,应该认识到 实例 使用对对象交互(IT强制)有很大的限制 遗传 是多态性的来源,而不是 行为 .

    对于这究竟是如何/为什么是一个问题似乎有些困惑,所以让我提供一个简单的例子(从 Py-容器 )假设我们有一个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!
    

    有可能是你想这样做的原因(使用装饰器等),但请记住:

    • 我们不想碰足球课。假设我们正在编写框架代码,这些代码可以被我们还不知道的客户机使用。
    • 重点是返回一个基本上是foo的对象,但具有附加的功能。对于任何其他期望有foo的客户机代码来说,它应该是foo(尽可能多)。因此,想要四处工作的愿望 实例 .
    • 是的,我知道我不需要工厂课程(在这里先发制人地保护自己)。
    5 回复  |  直到 15 年前
        1
  •  2
  •   nikow    15 年前

    如果您依赖的库代码使用 isinstance 为什么不走这条路呢?如果你不能改变图书馆,那么最好还是考虑一下。

    我也认为有合法的用途 实例 以及 abstract base classes 在2.6中,这已得到正式承认。有些情况下 实例 确实是正确的解决方案,而不是用duck输入 hasattr 或者使用异常。

    如果出于某种原因您确实不想使用继承,则使用一些脏选项:

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

    如果可以在运行时修改类,则有许多选项:

    • 使用运行时混音,即只需将类添加到 __base__ 类的属性。但这更适用于添加特定的功能,而不是在您不知道需要包装什么的地方随意包装。

    • Dave答案中的选项(python中的类修饰符=2.6或元类)。

    编辑:对于您的具体示例,我想只有第一个选项有效。但我还是会考虑创建一个 LogFoo 或者选择一个完全不同的解决方案来处理诸如日志之类的特定问题。

        2
  •  1
  •   Jason Baker    15 年前

    要记住的一件事是你不必 使用 如果您使用继承路由,则基类中的任何内容。您可以使存根类从中继承,而不添加任何具体的实现。我做过几次这样的事情:

    class Message(object):
         pass
    
    class ClassToBeWrapped(object):
        #...
    
    class MessageWithConcreteImplementation(Message):
        def __init__(self):
            self.x = ClassToBeWrapped()
        #... add concrete implementation here
    
    x = MessageWithConcreteImplementation()
    isinstance(x, Message)
    

    如果您需要从其他事物继承,我想您可能会遇到多继承的一些问题,但是如果您不提供任何具体的实现,这应该是相当小的。

    我遇到的一个问题是,当我必须与使用isInstance反模式的代码接口时

    我同意如果可能的话应该避免出现这种情况,但我不确定我会称之为反模式。使用ISInstance有一些合理的理由。例如,有一些消息传递框架使用它来定义消息。例如,如果您得到一个从shut down继承的类,那么子系统应该关闭了。

        3
  •  0
  •   Dave Stenglein    15 年前
        4
  •  0
  •   Kamil Kisiel    15 年前

    我的第一个冲动是尝试修复使用 isinstance . 否则,你只是把它的设计错误传播到你自己的设计中。你为什么不能修改它?

    编辑: 所以,您的理由是您正在编写框架/库代码,您希望人们能够在所有情况下使用这些代码,即使他们想使用isInstance?

    我认为这有几个问题:

    • 你试图支持一个破碎的范例
    • 您是定义库及其接口的人,正确使用库取决于用户。
    • 你不可能预料到你的库用户会做的所有糟糕的编程,所以试图支持糟糕的编程实践几乎是徒劳的。

    我认为您最好是编写惯用的、设计良好的代码。好代码(和坏代码)有传播的趋势,所以请举个例子。希望它将导致代码质量的整体提高。反之,质量只会继续下降。

        5
  •  0
  •   SingleNegationElimination    15 年前

    如果您编写的框架需要接受来自API用户的某种输入,那么我就没有理由使用IsInstance。虽然可能很难看,但我总是检查它是否提供了我要使用的接口:

    def foo(bar):
        if hasattr(bar, "baz") and hasattr(bar, "quux"):
            twiddle(bar.baz, bar.quux())
        elif hasattr(bar, "quuux"):
            etc...
    

    如果API用户想要使用它,我还经常提供一个继承默认功能的好类:

    class Bar:
        def baz(self):
            return self.quux("glu")
    
        def quux(self):
            raise NotImplemented