This recipe provides the class decorator
BoundInnerClass. The decorator makes inner classes symmetric with method calls. Functions declared inside classes become "methods", and when you call them through an object they automatically get a reference to "self". The
BoundInnerClass decorator makes this work for inner classes: an inner class decorated with
BoundInnerClass gets a reference to that same (now "outer") object passed in automatically to the inner class's
The recipe works unchanged in Python 2.6 and 3.1, and is licensed using the Zlib license.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
""" http://code.activestate.com/recipes/577070-bound-inner-classes/ Copyright (C) 2010-2011 by Alex Martelli and Larry Hastings Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ __all__ = ["BoundInnerClass", "UnboundInnerClass"] import weakref class _Worker(object): def __init__(self, cls): self.cls = cls def __get__(self, outer, outer_class): if not outer: return self.cls name = self.cls.__name__ # if we're already cached in outer, use that version # (but don't use getattr, that would call __get__ recursively) if name in outer.__dict__: return outer.__dict__[name] wrapper_bases = [self.cls] # iterate over cls's bases and look in outer to see # if any have bound inner classes in outer. # if so, multiply inherit from the bound inner version(s). multiply_inherit = False for base in self.cls.__bases__: # if we can't find a bound inner base, # add the original unbound base instead. # this is harmless but helps preserve the original MRO. inherit_from = base # if we inherit from a boundinnerclass from another outer class, # we might have the same name as a legitimate base class. # but if we look it up, we'll call __get__ recursively... forever. # if the name is the same as our own name, there's no way it's a # bound inner class we need to inherit from, so just skip it. if base.__name__ != name: bound_inner_base = getattr(outer, base.__name__, None) if bound_inner_base: bases = getattr(bound_inner_base, "__bases__", (None,)) # the unbound class is always the first base of # the bound inner class. if bases == base: inherit_from = bound_inner_base multiply_inherit = True wrapper_bases.append(inherit_from) Wrapper = self._wrap(outer, wrapper_bases) Wrapper.__name__ = name # Assigning to __bases__ is startling, but it's the only way to get # this code working simultaneously in both Python 2 and Python 3. if multiply_inherit: Wrapper.__bases__ = tuple(wrapper_bases) # cache in outer, but only if we're replacing the class # (if we're a bound inner class of A, and there's class B(A), # and in class B we're some other kind of attribute, # *don't* stomp on it!) if getattr(outer.__class__, name, None) == self.cls: setattr(outer, name, Wrapper) return Wrapper class BoundInnerClass(_Worker): def _wrap(self, outer, base): wrapper_self = self assert outer outer_weakref = weakref.ref(outer) class Wrapper(base): def __init__(self, *args, **kwargs): wrapper_self.cls.__init__(self, outer_weakref(), *args, **kwargs) # give the bound inner class a nice repr # (but only if it doesn't already have a custom repr) if wrapper_self.cls.__repr__ is object.__repr__: def __repr__(self): return "".join([ "<", self.__module__, ".", self.__class__.__name__, " object bound to ", repr(outer_weakref()), " at ", hex(id(self)), ">"]) return Wrapper class UnboundInnerClass(_Worker): def _wrap(self, outer, base): class Wrapper(base): pass return Wrapper # The code in this "if" statement will only execute if you run the module # directly; it won't run if you "import" this code into your own programs. if __name__ == "__main__": class Outer(object): @BoundInnerClass class Inner(object): def __init__(self, outer): self.outer = outer @BoundInnerClass class SubclassOfInner(Inner.cls): def __init__(self, outer): super(Outer.SubclassOfInner, self).__init__() assert self.outer == outer @BoundInnerClass class SubsubclassOfInner(SubclassOfInner.cls): def __init__(self, outer): super(Outer.SubsubclassOfInner, self).__init__() assert self.outer == outer @BoundInnerClass class Subclass2OfInner(Inner.cls): def __init__(self, outer): super(Outer.Subclass2OfInner, self).__init__() assert self.outer == outer class RandomUnboundInner(object): def __init__(self): super(Outer.RandomUnboundInner, self).__init__() pass @BoundInnerClass class MultipleInheritanceTest(SubclassOfInner.cls, RandomUnboundInner, Subclass2OfInner.cls): def __init__(self, outer): super(Outer.MultipleInheritanceTest, self).__init__() assert self.outer == outer @UnboundInnerClass class UnboundSubclassOfInner(Inner.cls): pass def tests(): assert outer.Inner == outer.Inner assert isinstance(inner, outer.Inner) assert isinstance(inner, Outer.Inner) assert isinstance(subclass, Outer.SubclassOfInner) assert isinstance(subclass, outer.SubclassOfInner) assert isinstance(subclass, Outer.Inner) assert isinstance(subclass, outer.Inner) assert isinstance(subsubclass, Outer.SubsubclassOfInner) assert isinstance(subsubclass, outer.SubsubclassOfInner) assert isinstance(subsubclass, Outer.SubclassOfInner) assert isinstance(subsubclass, outer.SubclassOfInner) assert isinstance(subsubclass, Outer.Inner) assert isinstance(subsubclass, outer.Inner) import itertools for order in itertools.permutations([1, 2, 3]): outer = Outer() # This strange "for" statement lets us test every possible order of # initialization for the "inner" / "subclass" / "subsubclass" objects. for which in order: if which == 1: inner = outer.Inner() elif which == 2: subclass = outer.SubclassOfInner() elif which == 3: subsubclass = outer.SubsubclassOfInner() tests() multiple_inheritance_test = outer.MultipleInheritanceTest() assert outer.MultipleInheritanceTest.mro() == [ # bound inner class, notice lowercase-o "outer" outer.MultipleInheritanceTest, # unbound inner class, notice uppercase-o "Outer" Outer.MultipleInheritanceTest, outer.SubclassOfInner, # bound Outer.SubclassOfInner, # unbound Outer.RandomUnboundInner, # etc. outer.Subclass2OfInner, Outer.Subclass2OfInner, outer.Inner, Outer.Inner, object ] unbound = outer.UnboundSubclassOfInner() assert outer.UnboundSubclassOfInner.mro() == [ outer.UnboundSubclassOfInner, Outer.UnboundSubclassOfInner, outer.Inner, Outer.Inner, object ] class InnerChild(outer.Inner): pass inner_child = InnerChild() isinstance(inner_child, Outer.Inner) isinstance(inner_child, InnerChild) isinstance(inner_child, outer.Inner)
One minor complaint about Python is that inner classes don't have access to the outer object. Consider this Python code:
class Outer(object): def method(self): pass class Inner(object): def __init__(self): pass o = Outer() o.method() i = o.Inner()
o.method is called, Python automatically passes in the
o object as the first parameter (generally called
"self"). But there's no built-in way for the
o.Inner object being constructed to automatically get a reference to the
Outer object. If you need one, you must pass it in yourself like so:
class Outer(object): def method(self): pass class Inner(object): def __init__(self, outer): self.outer = outer o = Outer() o.method() i = o.Inner(o)
It seems redundant to have to pass in
o as an argument to the
Inner constructor. You don't have to pass in
o explicitly to method calls; why should you have to pass it in explicitly to inner classes?
I posted a question online to see if anyone could propose a way to automatically pass the outer instance into the inner class's
__init__. Alex Martelli responded with a brilliant solution: a class decorator, itself implemented as a class providing a descriptor, which internally subclasses the passed-in class and passes in a reference to the object to the class's
__init__. The result has a pleasing symmetry with normal callables and "bound method objects". After trading some edits back and forth, Alex and I are pleased to present our solution below, which I've called "bound inner classes".
Using Bound Inner Classes
Let's modify the above example to use the recipe:
from boundinnerclass import BoundInnerClass class Outer(object): def method(self): pass @BoundInnerClass class Inner(object): def __init__(self, outer): self.outer = outer o = Outer() o.method() i = o.Inner()
All we did:
- Import the recipe.
- Decorate the inner class with
- Stop passing in the outer object
oto the constructor. It's now passed in automatically!
An Example Use Case
In case you're wondering "why would I ever care about this", consider the following only-slightly-contrived example.
Let's say you have a class that represents performing some task. The class supports the
__call__ method, making it directly callable to do its work. Something like:
class Worker(object): def __init__(self, ...): ... def __call__(self): # do work here ...
Now let's say you get more sophisticated one day, and want to wrap all that up in a thread. The most convenient way to create threads in Python is by subclassing the
threading.Thread class. However, the documentation for
threading.Thread tells you how to override the class's
run methods, then states:
No other methods (except for the constructor) should be overridden in a subclass. In other words, only override the
run()methods of this class.
Because of this restriction, it's generally best not to subclass
threading.Thread in your own general-purpose classes. Instead, you should write a special class, whose whole purpose in life is to subclass
threading.Thread and override just those two methods.
You could easily write it like this:
class Worker(object): ... class WorkerThread(threading.Thread): def __init__(self, worker): super(WorkerThread, self).__init__() self.worker = worker def run(self): self.worker() worker = Worker() thread = WorkerThread(worker)
Clearly this works. But I suggest it can be improved. Conceptually,
WorkerThread really has no utility apart from servicing the
Worker class. So I suggest it should be an inner class:
class Worker(object): ... class Thread(threading.Thread): def __init__(self, worker): super(WorkerThread, self).__init__() self.worker = worker def run(self): self.worker()
But now your code for initialization looks a bit silly:
worker = Worker() thread = worker.Thread(worker)
Here you are, calling an attribute of
worker, and yet you must also pass in
worker? Why doesn't it automatically know about
You can hide this redundancy by making a method of
Worker doing the redundant passing-in for you:
class Worker(object): ... class Thread(threading.Thread): ... def create_thread(self): return self.Thread(self) worker = Worker() thread = worker.create_thread()
But this really creates more problems than it solves. You've got two callable things when one should suffice. This clutters up the namespace of the class, not to mention cluttering your mind. If someone wants to create one of these thread objects, they must use one interface, but if they want to do an
isinstance() test they must use the other.
Decorating the inner
Thread class with
BoundInnerClass solves this problem:
class Worker(object): ... @BoundInnerClass class Thread(threading.Thread): def __init__(self, worker): ... worker = Worker() thread = worker.Thread()
Written this way, the
Thread constructor is now automatically called with the "outer" object, in this case the
worker object, without anyone having to explicitly--redundantly--pass it in. And everything works as you'd expect: you can tear off the
worker.Thread callable and call it as a standalone function, and you can run an
isinstance() test which will report success.
Bound inner classes get slightly complicated when mixed with inheritance. It's not all that difficult, you merely need to obey the following rules:
- A bound inner class can inherit normally from any unbound class.
- To subclass from a bound inner class while inside the same outer class, or when referencing the inner class from the outer class (as opposed to an instance of the outer class), you must actually subclass from
classname.cls. (This is because inside the outer class, the "class" you see is actually an instance of a
- All classes that inherit from a bound inner class must always call call the superclass's
__init__. You don't need to pass in the
outerparameter; it'll be automatically passed in to the superclass's
- An inner class that inherits from a bound inner class, and which also wants to be bound to the outer object, should be decorated with
- An inner class that inherits from a bound inner class, but isn't interested in being bound to the outer object, should be decorated with this recipe's other decorator,
Restating the last two rules: every class that descends from any
BoundInnerClass should be decorated with either
Here's a simple example of inheritance with bound inner classes:
from boundinnerclass import BoundInnerClass class Outer(object): @BoundInnerClass class Inner(object): def __init__(self, outer): self.outer = outer @UnboundInnerClass class ChildOfInner(Inner.cls): def __init__(self): super(Outer.ChildOfInner, self).__init__() o = Outer() i = o.ChildOfInner()
We followed the rules:
objectisn't a bound inner class, there are no special rules about inheritance
Innerneeds to obey.
ChildOfInnerinherits from a
BoundInnerClass, it must be decorated with either
UnboundInnerClass. It doesn't want the
outerobject passed in, so it's decorated with
Note that, because
ChildOfInner is decorated with
UnboundInnerClass, it doesn't take a
outer argument. Nor does it pass one in when it it calls
super().__init__. But when the constructor for
Inner is called, the correct
outer argument appears like magic. Thanks,
If you wanted
ChildOfInner to also get the
outer argument passed in to its
__init__, just decorate it with
BoundInnerClass instead of
UnboundInnerClass like so:
from boundinnerclass import BoundInnerClass class Outer(object): @BoundInnerClass class Inner(object): def __init__(self, outer): self.outer = outer @BoundInnerClass class ChildOfInner(Inner.cls): def __init__(self, outer): super(Outer.ChildOfInner, self).__init__() assert self.outer == outer o = Outer() i = o.ChildOfInner()
You can see more complex examples of using inheritance with
UnboundInnerClass) in the recipe's test code.
- If you refer to a bound inner class directly from the outer class, rather than using the outer instance, you get the original class. This means that references to
Outer.Innerare consistent, and it's a base class of all the bound inner classes. This also means that if you attempt to construct one without using an outer instance, you must pass in the
outerparameter by hand, just as you would have to pass in the
selfparameter by hand when calling an unbound method.
- If you refer to a bound inner class from an outer instance, you get a subclass of the original class.
- Bound classes are cached in the outer object, which both provides a small speedup and ensures that
isinstancerelationships are consistent.
- You must not rename inner classes decorated with this recipe! The implementation of
BoundInnerClasslooks up the bound inner class in the outer object by name in several places. Adding aliases to bound inner classes is harmless, but the original attribute name must always work.
- Bound inner classes from different objects are different classes. This is symmetric with bound methods; if you have two objects "a" and "b" that are instances of the same class,
a.BoundInnerClass != b.BoundInnerClass, just as
a.method != b.method.
- The binding only goes one level deep; if you had an inner class C inside another inner class B inside a class A, the constructor for C would be called with the B object, not the A object.
- Similarly, if you have a bound inner class B inside a class A, and another bound inner class D inside a class C, and D inherits from B, the constructor for D will be called with the B object but not the A object. When D calls
super().__init__it'll have to fill in the
outerparameter by hand.
- There is a race condition in the implementation: if you access a bound inner class through an outer instance from two separate threads, and the bound inner class was not previously cached, the two threads may get different (but equivalent) bound inner class objects, and only one of those instances will get cached on the outer object. This could lead to confusion and possibly cause bugs. For example, you could have two objects that would be considered equal if they were instances of the same bound inner class, but would not be considered equal if instantiated by different instances of that same bound inner class. There's an easy workaround for this problem: access the bound inner class from the
__init__of the outer class, which should allow the code to cache the bound inner class instance before a second thread could ever get a reference to the outer object.
An Alternate Approach
I've contributed a second implementation of bound inner classes as a new recipe, here:
While that implementation is interesting, I think this original approach remains preferable for production code.
Revision 1 was the original revision.
Revision 2 has been lost to the hoary mists of time.
Revision 3 removed the per-decorator cache mapping outer objects to the bound inner form of the decorated class. Instead, a reference to the bound inner class is stored in the object, occluding the original decorated class. This helps with object cleanup; storing this cache in the decorator meant the bound inner classes and the outer objects were effectively immortal. (They'd only get collected if you dropped all references to the outer object's class, and even then there was a long reference cycle.) Also, added warning about the surprising "bound inner subclasses don't appear to be children of the bound inner parent class" behavior.
Revision 4 cleaned up the interaction between bound inner classes and inheritance, by adding the "multiply inherit from bound inner version(s)" code and
UnboundInnerSubclass. Multiple inheritance now works cleanly, passing in the "outer" argument to the bound inner base classes is now automatic, and bound inner subclasses now inherit from their bound inner parent classes (which fixes some surprising
isinstance behavior from previous revisions).
Revision 5 changed
BoundInnerClass to keep only a weakref to the outer object. Without this change,
BoundInnerClasses created a reference cycle (o -> boundinnerclass, boundinnerclass -> o) which made object cleanup unnecessarily harder. Also,
UnboundInnerSubclass was renamed to
UnboundInnerClass; the former nomenclature was needlessly asymmetric. Third, all references to
parent were renamed to
outer. I think that's clearer, though it doesn't affect any of the interfaces. Finally, an explicit Zlib license was added to the source code. (The recipe is marked as being MIT license; ActiveState doesn't provide a choice for the Zlib license. Don't worry, Zlib is even less restrictive than MIT; it explicitly says that you don't need to preserve the copyright notice in compiled form.)
Revision 6 fixed a bug: if a bound inner class inherited from a class with the same name, the multiple-inheritance check would go into an infinite
__get__ recursion loop and blow the stack. To fix it, the multiple-inheritance code now skips classes with the same name as the currently-being-bound class, as there's no way said class could be an inner class inside the same outer object.
Revision 7 made no bugfixes and added no features. It was purely a code readability edit, based on comments Alex made regarding the recipe in-person at PyCon 2011.
Revision 8 fixed an obscure bug: if class A defines bound inner class BIC, and class B descends from A but defines BIC to be something else (a method, a scalar, etc) but still uses class A's BIC (by calling
super()), and b is an object of type B, then
BoundInnerClass would overwrite b.BIC in with the bound inner version of A.BIC.
BoundInnerClass definitely shouldn't overwrite b.BIC in this case. Unfortunately that means we have nowhere to store the bound version of A.BIC, so this will break some
isinstance() uses--there's really no good solution here.