内部类:如何在构造时获取外部类对象?

11 投票
9 回答
10214 浏览
提问于 2025-04-15 19:22

考虑以下的Python代码(可以在2.x或3.x版本中运行):

class Outer(object):
  pass

  class Inner(object):
    def __init__(self):
      print("Inner.self", self)

o = Outer()
i = o.Inner()

我想在Inner.__init__()这个方法里获取到o。但是:

  • 我不想把o作为Inner的一个明确参数。
  • 我希望O.Innero.Inner是一个类对象,而不是一些奇怪的东西,比如闭包。

你能建议我怎么实现这个吗?

目前我最好的想法是使用线程本地存储。在我的使用场景中,每当我构造一个o.Inner()时,我已经在某个地方的o的方法里了,添加

threading.local()["my o object"] = o

到我的代码里也不是大问题。

这让你了解我愿意考虑的解决方案的复杂程度。

9 个回答

3

这件事做不了。不过,如果稍微重新设计一下,就可以实现:

class Outer(object):
  pass

  class _Inner(object):
    def __init__(self, outobj):
      self.outobj = outobj

  def Inner(self):
    return self._Inner(self)

o = Outer()
i = o.Inner()

print o, i.outobj
6

你可以使用元类来实现一个 __get__ 描述符,这样可以把内部类和外部类绑定在一起。而且因为你似乎只对绑定类感兴趣,所以可以考虑直接修改内部类,而不是像函数那样把它包装成一个方法。

>>> class Outer(object):
    class Inner(object):
        class __metaclass__(type):
            def __get__(self, instance, owner):
                self.owner = owner
                return self


>>> Outer.Inner is Outer().Inner
True
>>> Outer.Inner.owner is Outer
True

如果你更喜欢通过子类来包装内部类,那么可以把 __get__ 的内容替换成:

return type(self.__name__, (self,), {'owner': owner})
23

在Python 2.6中,有一种类装饰器,同时也是自定义描述符,能够符合你给出的要求:

class InnerClassDescriptor(object):
  def __init__(self, cls):
    self.cls = cls
  def __get__(self, instance, outerclass):
    class Wrapper(self.cls):
      outer = instance
    Wrapper.__name__ = self.cls.__name__
    return Wrapper

class Outer(object):
  @InnerClassDescriptor
  class Inner(object):
    def __init__(self):
      print self.outer

o = Outer()
i = o.Inner()
print 'Outer is a', type(Outer)
print 'Inner is a', type(o.Inner)

这段代码会输出:

<__main__.Outer object at 0x82f90>
Outer is a <type 'type'>
Inner is a <type 'type'>

只是为了确认一下,

o.Inner [[是]] 一个类对象,而不是像闭包那样奇怪的东西。

这符合你特别的要求。当然,每次都需要是一个不同的类,以便能够重新进入——即使在单线程的环境下,下面的代码:

o1 = Outer()
o2 = Outer()
i1 = o1.Inner
i2 = o2.Inner
print i1(), i2(), i1(), i2()

应该能正常工作,而把o1和o2存放在除了o1.Innero2.Inner返回的类之外的地方(比如在TLS中)会导致这个用法出现严重问题。

不过你并没有指定“o.Inner必须是完全相同的类对象,对于每一个可能的o,它都是Outer的一个实例”,所以这段代码完全符合你给出的要求;-)。

撰写回答