10

Consider the following Python (runs in 2.x or 3.x):

class Outer(object):
  pass

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

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

I want to get my hands on o while inside Inner.__init__(). But:

  • I don't want o to be an explicit parameter to Inner.
  • I want O.Inner and o.Inner to be a class object, not something weird like a closure.

Can you suggest how I might achieve this?

Right now my best idea is to use thread local storage. In my use case, whenever I construct an o.Inner(), I'm already inside a method on o somewhere, and it wouldn't be a big deal to add

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

to my code.

This gives you an idea of the level of depravity I'm willing to consider.

Larry Hastings
  • 2,644
  • 20
  • 11
  • 3
    This is one of those cases where we have to ask: "What is it that you're trying to accomplish? Because there's probably a better way to do it." In Java, you can't put two (public) classes in the same file unless one of them is inside the other. In Python, there is no such requirement, so there is probably a way to do what you want without using inner classes at all. – Tyler Feb 17 '10 at 05:26
  • Linking in [Access outer class from inner class in python](https://stackoverflow.com/q/2024566/674039) – wim Apr 03 '19 at 18:07

7 Answers7

22

In Python 2.6, a class decorator that's also a custom descriptor matches the specs you give:

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)

This emits:

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

just to confirm that

o.Inner [[is]] a class object, not something weird like a closure

as per your peculiar specs. Of course it needs to be a different class each time for reentrancy -- even in a single-threaded world, the following:

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

should work cleanly, and stashing o1 vs o2 anywhere else than in the classes returned by o1.Inner vs o2.Inner (e.g., in TLS) would mean horrible results for this use.

But then you didn't specify "o.Inner has to be exactly the same class object for every possible o that's an instance of Outer", so this code fully meets the specs you did give;-).

Asclepius
  • 57,944
  • 17
  • 167
  • 143
Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • Marvelous! Now I must meditate whether I can live with o1.Inner != o2.Inner != Outer.Inner. Of course, bound methods != unbound methods, so it only makes sense that bound child classes != unbound child classes. – Larry Hastings Feb 17 '10 at 13:34
  • As written your approach returns a new class every time o.Inner is referenced, so "assert o.Inner == o.Inner" would fail. Caching the Wrapper class in the instance, like setattr(instance, "__cache__" + self.cls.__name__), then preferring the cached version where available, solves that at least. – Larry Hastings Feb 17 '10 at 13:45
  • 1
    Another tweak: if "instance" is None, then the __get__ should return self.cls. Without this isinstance(i, Outer.Inner) returns False. – Larry Hastings Feb 21 '10 at 06:24
  • 1
    Finally, @LarryHastings has posted a recipe http://code.activestate.com/recipes/577070-bound-inner-classes/ – xmedeko Mar 15 '18 at 12:08
  • @xmedeko fine solution, but `Inner` has wrong signature. – Superior May 10 '20 at 16:00
5

You can use a metaclass to implement a __get__ descriptor that binds the inner class to the outer one. And since you seem to be interested in only binding to a class, consider modifying the inner class in-place, unlike a function which is wrapped into a method.

>>> 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

If you'd rather wrap the inner class via a subclass then replace the __get__ body with:

return type(self.__name__, (self,), {'owner': owner})
A. Coady
  • 54,452
  • 8
  • 34
  • 40
  • Let me play with it a little more, but I think this may serve my purposes better than Alex Martelli's admittedly-mind-meltingly-cool answer. If so, I'll switch my checkmark. Thanks! (And to all of you who said "it can't be done"--phooey!) – Larry Hastings Feb 17 '10 at 22:25
  • I'm giving you a check, as you did solve the problem as stated, with a creative solution that showed me something new. But I can't use this approach. The unfixable problem is that it cannot be multithread-safe. However as that was not a stated requirement I can't fault you for not handling it. – Larry Hastings Feb 21 '10 at 05:46
  • Sorry, new to Stack Overflow, I thought you *could* give multiple checks. I'll definitely give you an upvote, sorry for the confusion. – Larry Hastings Feb 21 '10 at 05:47
3

Can't be done. But with a bit of redesign:

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
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • that’s cool. you simply specify what the inner class needs and you generate it via function from the outer object. – flying sheep May 16 '11 at 12:27
  • Actually, I think can be done (unless you are describing something else to what I think). See my answer for a demonstration. – Edward Feb 01 '16 at 17:17
0

You should redesign your code not to use inner classes and to explicitly pass the instance of Outer to Inner when you make it, or not to require it.

Mike Graham
  • 73,987
  • 14
  • 101
  • 130
0

You can't (in any portable, reliable or efficient way.) Nested classes have no relation to the nesting class. They have no added value, either. Instead of using nested classes, use containment of instances -- define Outer and Inner separately, and create an instance of Inner in Outer.__init__, possibly passing Outer or the Outer instance if you need it. You can make Inner a class attribute of Outer if you insist, but it will have no special access to Outer.

Thomas Wouters
  • 130,178
  • 23
  • 148
  • 122
  • 1
    @Thomas, so which one of the three characteristics you think my answer is missing -- seems pretty portable, reliable, _and_ efficient to me (also pretty elegant, if I say so myself;-). The "special access" comes through Inner being a _custom descriptor_ class (which wraps the original Inner class object via decorator syntax -- that's where the 2.6 requirement comes, as it needs to be a _class_ decorator of course) -- so that, whenever `o.Inner` is accessed, `o.Inner.__get__` comes into play (and gets `o` as an argument). – Alex Martelli Feb 17 '10 at 05:52
  • 1
    To be honest I didn't think of using a class decorator. It still seems like a nasty hack for no decent purpose. As for which of the three it lacks, obviously portability (it only works in Python 2.6 and later :) -- although I would point out that with your solution the inner class can't itself be a descriptor. And subsequent code in the class block can't use the inner class directly, which is presumably part of the reason it's an inner class in the first place. All in all I still stand by my answer that you shouldn't be creating interdependent classes like this; the complexity isn't worth it. – Thomas Wouters Apr 01 '10 at 10:55
0

See Access outer class from inner class in python (https://stackoverflow.com/a/35118717/3787376)
for a simple reliable answer I made that only uses variables, attributes, functions and classes - no special code.

Edward
  • 1,062
  • 1
  • 17
  • 39
0

This is my solution for python 3 (I'm using 3.7, don't know for earlier versions or 2.x)

from functools import wraps
from inspect import signature as _signature

class _Bound:
    '''type of bound methodsand classes'''
    def __init__(self, callable):
        self._callable = callable
    def __get__(self, instance, cls):
        'called when instance.callable or Class.callable'
        if instance is None: # called on class, not instance
            return self._callable

        @wraps(self._callable)
        def retf(*a, **k):
            try: 
                return self._callable(instance, *a, **k)
            except: # information if failed
                # user did `self.callable(*args, **kwargs)` and failed
                print(f'failed call of {self._callable}')
                print(f'self:   {instance}')
                print(f'args:   {a}')
                print(f'kwargs: {k}')
                raise 
        # additional wrap to expell first argument
        sig = _signature(retf)
        retf.__signature__ = sig.replace(parameters=tuple(sig.parameters.values())[1:])

        return retf
    def __call__(self, *a, **k):
        'Class.__dict__['BoundMethod'](instance, *a, **k) (c(), 55, 8, key=9)'
        return self._callable(*a, **k)
    pass

class UnBound:
    "similar to static, only doesn't change attribute at all (except for builtin-like method making)"
    def __init__(self, callable):
        self.callable = callable
    pass
class class_(classmethod):
    pass
class static(staticmethod):
    pass

class NestedType(type):
    '''meta to support nested classes (and other callables)
    meta to outer class'''
    @staticmethod
    def bind(dict):
        for name, attr in dict.items():
            if callable(attr) and not isinstance(attr, (class_, static)): #functions, classes, ...
                dict[name] = _Bound(attr)
            elif isinstance(attr, UnBound):
                dict[name] = attr.callable
    def __new__(meta, object_or_name, bases, dict):
        NestedType.bind(dict)
        return type.__new__(meta, object_or_name, bases, dict)

class NestedBase(metaclass=NestedType):
    pass

example

class Outer(<note0>):
    class Inner:
        def __init__(self, self_): # I prefer self_ above outer

note0: here can come NestedBase or metaclass=NestedType (and beware of meta mismatch error)

need to know
Outer.Inner return class module.Outer.Inner
Outer().Inner returns bound method
Outer.__dict__['Inner'] returns bound object _Bound...

note1
imports are not that important, they only change pop-up when using IDE (VScode python extension doesn't recognize it, so it displays it wrongly, but IDLE does)

classes explained
_Bound should not be used

used as a decorator:
UnBound will make old-style binding
class_ same as classmethod
static same as staticmethod

NestedType metaclass to the outer class
NestedBase base to the outer class
only one needed per outer class

note2
inner class that has no decorator UnBound needs to have init that receives <Outer object> as 2nd argument
otherwise will write (see _Bound.__get__.retf.except) and throw TypeError: Inner(?) takes ? arguments

meta mismatch
to see meta mismatch write class M(type): pass, class A(NestedBase): pass, class B(metaclass=M): pass, class C(A, B): pass
solution: create submeta of both metas and, if , call NestedType.bind(dict) (as in NestedType.__new__)
note3: overriding __new__ or NestedMeta not 1st in subclassing order (class NewMeta(NotNestedMeta, NestedMeta) ( here NestedMeta is non-strict submeta of NestedType)

Superior
  • 787
  • 3
  • 17