从实例本身调用实例变量的方法

3
我有一个情况,我希望能够从实例本身调用实例变量的常用方法。这并不是程序正常运行所必需的,但是,输入bar.baz() 要比输入 bar.foo.baz() 更方便。
最Pythonic/易读/最常见的方法是什么?我想写一个函数修饰器;然而,我也接受其他的想法。解决方法还必须支持继承。
例子:
class Foo (object) :
    def baz (self) :
        return 324

class Bar (object) :
    def __init__ (self) :
        self.foo = Foo()

    # This is the current ugly solution.
    # It also results in an extra function call.
    def baz (self, *args, **kw) :
        return self.foo.baz(*args, **kw)

bar = Bar()
print bar.foo.baz()
print bar.baz() 

这是针对Python 2.7.3的,供您参考。

1
但是,如果baz只是一个实用方法,并且与Bar没有状态关联,那么您不需要一个类。您只需要函数。 - jdi
3个回答

4

您可以使用所谓的mixin。您创建一个没有个人状态但携带常见功能的类,您想要将其“混合”到另一个类中:

class FooMixin(object) :
    def baz (self) :
        return 324

class Bar(object, FooMixin):
    def __init__ (self):
        pass

bar = Bar()
bar.baz()

*或者像@Joachim在另一个答案中建议的那样使用组合。

但是可以预想到,“真实”的 Foo 版本将有一些依赖于某些内部状态的方法。 - Ben

2

这更简单;

class Foo (object) :
    def baz (self) :
        return 324

class Bar (object) :
    def __init__ (self) :
        self.foo = Foo()
        self.baz = self.foo.baz;

bar = Bar()
print bar.foo.baz()
print bar.baz()

这实际上是我解决问题的第一种方法。然而,这种解决方案在继承方面表现不佳,因此被排除在外。 - rectangletangle
@RectangleTangle:继承的哪个方面表现不佳? - jdi
如果我从Bar继承一个名为Buzz的类,并拥有自己的方法名baz,当调用Bar__init__方法时,Buzz.baz将被覆盖。 - rectangletangle
2
@RectangleTangle:那就把Foo作为所有类的基类,或者使用我的混入方法。 - jdi
1
@RectangleTangle 是的,我为你的例子选择了最简单的解决方案。当涉及到继承时,混合(@jdi的解决方案)确实是实现它的“Pythonic”方式。 - Joachim Isaksson

1

如果只有一个方法,那么你所拥有的并不算太糟糕。它非常明确地表明bazBoo接口的一部分,并通过委托给包含的Foo实例来实现这一点。

稍微简短一些的版本是使用属性:

class Foo (object) :
    def baz (self) :
        return 324

class Bar (object) :
    def __init__ (self) :
        self.foo = Foo()

    @property
    def baz(self):
        return self.foo.baz

    def __getattr__(self, attr):
        return getattr(self.foo, attr)

bar = Bar()
print bar.foo.baz()
print bar.baz()

虽然还有一个额外的函数调用,但也许稍微不太明显的是baz是一个方法。

如果baz是唯一需要委托给包含实例的方法,而不必重写继承树,那么我会选择你已经拥有的东西。如果您想委托多个方法,则有一些选项:

如果您希望从包含FooBar实例中调用所有方法,则可以尝试使用__getattr__

class Foo (object) :
    def baz (self) :
        return 324

class Bar (object) :
    def __init__ (self) :
        self.foo = Foo()

    def __getattr__(self, attr):
        return getattr(self.foo, attr)

bar = Bar()
print bar.foo.baz()
print bar.baz()

但这种方法有一些明显的缺点。

  1. 通过阅读源代码或内省,不清楚Bar实际提供了哪些方法。
  2. Bar接口将公开所有Foo的方法和属性(除了直接由Bar提供的方法和属性),这可能是不可取的。

当然,您可以在__getattr__方法中放置更多逻辑,以仅委托某些事物,而不是全部委托。

Bar只是一个简单的Foo包装器时,我倾向于采用这种方法,因为这样“Bar的行为大部分像Foo”是概念界面的一部分(而不是Foo是内部实现细节),并且它将Bar的定义减少到描述它与Foo不同的方式。

另一种方法是利用Python的动态特性,在Bar上定义属性,这些属性委托给Foo,而无需手动编写所有属性。像这样的东西就可以解决问题:

class Foo (object) :
    def baz (self) :
        return 324

class Bar (object) :
    def __init__ (self) :
        self.foo = Foo()

    for _method in ['baz', 'blob']:
        @property
        def _tmp_func(self, _name=_method):
            return getattr(self.foo, _name)
        locals()[_method] = _tmp_func
    # del to avoid name pollution
    del _method, _tmp_func

bar = Bar()
print bar.foo.baz()
print bar.baz()

这种方法相对于使用__getattr__的优点在于,当你阅读代码时,你有一个明确的方法列表被重定向,并且它们在运行时都会“看起来”像Bar的普通属性,因为它们是普通属性。然而,如果你只需要这个方法中的一个或两个方法,那么这显然比你原来的代码要多得多!而且这有点微妙(由于Python闭包的工作方式,需要通过默认参数将_method传递到_tmp_func中并不一定明显)。但是,将执行此操作的逻辑封装在类装饰器中相对容易,这将使您能够创建更多的类,将其某些方法的实现委托给它们的属性之一。
这样的事情有很多变化。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接