Monday, March 29, 2010

Python class attribute annoyance

Python class attributes are fairly handy things, if somewhat visually misleading. Coming from other languages, you might expect this:

  class Foo(object):
    a = 10

To define a class whose instances will have one attribute called a. Not quite. a is actually what most languages call a "static attribute" or "static member" of the class itself, not the instances. Python calls these "class attributes." Once you know this, class attributes are a tool you'll reach for in a number of circumstances, but they have subtle behaviors that can feel like bugs.

For example, today I was trying to do something like this:

  class Foo(object):
    a = 10
    b = a + 5

which works just fine and does what you might expect (a is 10 and b is 15). But, this will yield an error:

  class Foo(object):
    a = 10
    b = [ a+i for i in range(1,11) ]

You might expect b to contain [ 11, 12, ..., 20 ] but instead, you get an error telling you that a isn't defined. [Note: tested in Python 2.6 and 3.1] This subtle flaw exists because that a+i is actually being executed in a nested lexical scope, but because it was created inside of a class body, it fails to inherit what appears to be the parent scope and thus has no access to its lexically scoped variables. There are many ways to accomplish what you might have intended, here, but none of them are very clean. For example:

  class Foo(object):
    a = 10
    b = [ z+i for z in (a,) for i in range(1,11) ]

Now you are passing a as a parameter to that nested scope, so it works perfectly. It's certainly a stilted way to do this, but it works just fine.
Coming, as I do, from Perl, this feels very odd. Perl's OO model is, at best, a framework upon which to build your own. Even still, this kind of scoping problem just never happens. Any lexical scope introduced anywhere in Perl will have a parent scope which is visually quite obvious. Running into such subtle shifts in Python's behavior seems counter to its stated goal of simplicity and elegance.