2009-02-23 65 views
5

在Python中,我已经看到建议使用保持或包装来扩展对象或类的功能,而不是继承。特别是,我认为Alex Martelli在他的Python Design Patterns谈话中谈到了这件事。我已经看到这个模式用于依赖注入库,如pycontainer。我碰到的包装对象扩展/添加功能同时解决isinstance

的一个问题是,当我有一个使用 isinstance anti-pattern代码交互,因为保持/包装对象失败的isinstance测试这种模式失败。如何设置持有/包装对象以避免不必要的类型检查?这可以做到一般吗?在某种意义上,我需要一些类似于保留签名的函数装饰器的类实例(例如,simple_decorator或Michele Simionato的decorator)。

资质:我并不是断言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! 

有5原因,你可能想要做的事情正是这样(用装饰代替,等等),但要记住:

  • 我们不不想触摸Foo类。假设我们正在编写可供我们尚不知道的客户使用的框架代码。
  • 重点是返回一个基本上是Foo的对象,但增加了一些功能。它应该看起来像是一个Foo ---尽可能---对任何期待Foo的客户代码。因此希望解决isinstance
  • 是的,我知道我不需要工厂班级(在这里抢先保护自己)。
+0

摇动我的拳头无助愤怒在isinstance。你不能用isinstance修复代码吗? http://stackoverflow.com/questions/423823/whats-your-favorite-programmer-ignorance-pet-peeve/423857#423857 – 2009-02-23 22:52:18

+0

无奈的愤怒:)在某些情况下,我有权访问isinstance。但另一个用例是希望编写与其他人很好的“框架”代码(例如pycontainer)。 – zweiterlinde 2009-02-23 23:02:54

回答

2

如果您依赖的库代码使用isinstance并依赖于继承,为什么不遵循此路线?如果你不能改变图书馆,那么最好保持一致。

我也认为isinstance有合法用途,在2.6中引入abstract base classes这已被官方承认。有些情况下isinstance确实是正确的解决方案,而不是使用hasattr进行鸭子打字或使用例外。

有些脏的选择,如果由于某种原因,你真的不想使用继承:

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

如果你能在运行时修改类有很多选择:

  • 使用运行时混入的,即只是一个类添加到您的类的__base__属性。但是这更多地用于添加特定的功能,而不是用于不知道需要包装什么的不加区分的包装。

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

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

0

我的第一个冲动是尝试修复使用isinstance的违规代码。否则,你只是把它的设计错误传播到你自己的设计中。任何原因你不能修改它?

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

我认为有几件事情不对的:

  • 你试图支持一个破碎的范例
  • 你是一个定义库和接口,这取决于用户使用它正确。
  • 有没有办法你都不可能预见所有的不好的编程库中的用户会做,所以这几乎是徒劳的努力,尽力支持好的编程问题

我想你最好写地道,设计良好的代码。好的代码(和错误的代码)有扩散的趋势,所以让你成为一个例子。希望它会导致代码质量的整体提高。换个方式只会继续质量下降。

1

有一点要记住的是,如果你走继承路线,你不一定要在基类中使用任何东西。你可以让一个存根类继承,不会添加任何具体的实现。我做了这样的事情几次:

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是尽可能避免代码的接口,但我我不确定我会把它称为反模式。有一些有效的理由使用isinstance。例如,有一些消息传递框架使用它来定义消息。例如,如果您得到一个从Shutdown继承的类,那么是时候关闭子系统了。

0

如果你正在编写一个需要接受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