Bound Inner Classes For Python

permalink         categories: programming         originally posted: 2013-12-24 09:39:02

(This blog entry is my contribution to the 2013 Python Advent Calendar. I'm the entry for December 25th,; however, due to the time zone difference between here and Japan, I'm posting it during what is to me the morning of the 24th. Merry Christmas!)

In Python, something magic happens when you put a function inside a class. If you access that function through an instance of the class, you don't simply get the function back. Instead you get a new object we call a "method". Conceptually, the function is "bound" to the instance; practically, when you call a method, it calls the function but automatically passes in the instance as the first positional argument, pushing the other positional arguments forward by one.

But this magic is only true for functions. Consider classes. In Python, if you define a class C inside another class D, we call C an "inner class" (or perhaps a "nested class"). If you access C through an instance of D, nothing magic happens, you just get C back, and if you call it the arguments to C.__init__ are unchanged.

Inner classes are rarely used in Python. Why? Because there are no practical advantages to doing so--but Python's scoping rules result in some minor inconveniences. (See below.)

I often use inner classes in Python anyway, because I have classes that conceptually should live inside some other class. And, most of the time, these inner classes conceptually want to be "bound" to the outer class instance. But I have to do it manually, like this:

class D:
    class C:
        def __init__(self, outer):
            self.outer = outer
d = D()
c = d.C(d)

Wouldn't it be nice if inner classes got "bound" the way functions did? Wouldn't it be nice if outer was passed in to the inner class's __init__ automatically?

But is this even possible in Python? Some years back I asked that question, on Stack Overflow:

http://stackoverflow.com/questions/2278426/inner-classes-how-can-i-get-the-outer-class-object-at-construction-time

I was amazed at the replies. People not only said that it was impossible, but that it was pointless. And then Alex Martelli showed it was possible--and his solution blew my mind! His solution uses a class decorator, and makes your class a "descriptor", which is the same mechanism functions use to become methods. Brilliant!

With Alex's permission, I posted this as a "recipe" at the Python Cookbook:

http://code.activestate.com/recipes/577070-bound-inner-classes/

I've since fine-tuned the approach many times. At this point it works really well! And, yes, the recipe works unchanged in both Python 2 and 3.

I love bound inner classes and I use them whenever I can. When I tell people about bound inner classes, the most common feedback I get is "What's your use case?" My reply: "What's your use case for method calls?" I think it's the same question. And I suggest to you that bound inner classes are just as useful as methods. If not more so!

Of course, classes are more complicated than functions. So it makes sense that bound inner classes are more complicated than methods. For example, inheritance and more than two levels of nesting can make things a little crazy. I worked hard to make bound inner classes sensible and predictable in these situations--but you have to understand a little how they work, and obey some simple rules. All of this is documented in the "recipe" above.

I hope you start using bound inner classes in your own code!


In case you're wondering why inner classes can be inconvenient, consider the following:

class D:
    value = 5
    class C:
        value2 = value * 5

This code doesn't work; it raises a NameError on value. Code in C can't see value--it can't see any members of D. All it can see are the members of C, globals, and builtins. (Unfortunately the nonlocal keyword doesn't help you here; that only helps with nested functions, and class bodies don't behave like nested functions.)

If you change it to this:

class D:
    value = 5
    class C:
        value2 = D.value * 5

Sadly that doesn't work either. D doesn't exist yet--technically speaking, it "hasn't been bound yet". This code raises a NameError on D.

The only thing that works is to do the lookup at runtime:

class D:
    value = 5
    class C:
        def __init__(self):
            self.value2 = D.value * 5

In other words, nested classes can't access any members of their outer classes at compile-time.

p.s. To my Japanese readers today on Christmas: I want to tell you that Americans don't actually eat KFC on Christmas day! This is a marketing gimmick created by KFC in Japan. Americans do usually have a big family dinner on Christmas, but there's no traditional main course.

Previous Essays


Getting 7.1 HDMI Audio Working Under Ubuntu
originally posted: 2011-11-19 23:40:01

I run Ubuntu and XBMC on my home theater PC (hereafter HTPC). I connect my HTPC to my receiver and TV via HDMI. The HTPC is an nVidia ION 2 machine, so it's using nVidia's HDMI implementation. I also have a full 7.1 speaker system. But out-of-the-box, Ubuntu refused to recognize my full 7.1 system. All it would let me choose were stereo and 5.1 configurations.

After beating my head against this for a day or two I finally stumbled on the solution. The trick is...

Continue reading Getting 7.1 HDMI Audio Working Under Ubuntu


Quick One-Positional-Argument Function Currying In Python
originally posted: 2011-11-01 14:48:12

Function "currying" in Python means pre-adding arguments to a function. If you have a function that takes two arguments, you can create a new function from it that only takes one, if you can somehow automatically set the second parameter. (My understanding is that the term "curry" isn't totally correct here; the correct mathematical term would be "partial application". Calling this "currying" is but one of the Python...

Continue reading Quick One-Positional-Argument Function Currying In Python


Rise From Your Grave
originally posted: 2011-11-01 14:44:23

It's been three years since I last posted. I have an excuse: in April of 2007 I got me a Internet Job! And sadly it's left me no time for essay-writing.

But I'm gonna try and revive my blog. To that end, I've finally added a reStructuredText renderer to my homegrown blogging tool PySa. Previously I had to write blog entries in HTML; precise but laborious. Now that I can write in ReST perhaps I can knock out essays a bit faster.

Also, my blog is now...

Continue reading Rise From Your Grave


Floating-Point Numbers Are Precise!
originally posted: 2010-05-22 09:47:31

(What they lack is accuracy!)

There's a great deal of misunderstanding about floating-point numbers. Better men than I have tried to dispell the haze of confusion that surrounds them. But let me make a start.

One source of confusion is when people think of floating-point numbers as approximations. That's muddy thinking. A floating-point number is not approximately anything; it's an exact value. The problem is that it may not be the value you wanted.

If I...

Continue reading Floating-Point Numbers Are Precise!


Older essays...

About Momentary Fascinations

RSS

Recent Fascinations

Getting 7.1 HDMI Audio Working Under Ubuntu

Quick One-Positional-Argument Function Currying In Python

Rise From Your Grave

Floating-Point Numbers Are Precise!

All Essays

Years

2013

2011

2010

2007

2006

2005

Tags

books

entertainment

game jones

general

meta

music

politics

programming

repeat-1 song of the day

tales of the self-indulgent

technology

trampled liberties

video games

Momentary Fascinations is Copyright 2005-2014 Larry Hastings.