Monday, January 11, 2010

Python 3.1 String Formatting - Positional Arguments

Last time I did a goofy little trick with the new Python 3 string formatting.  This time I'd like to demo something a little more practical.


I had heard a lot about positional arguments being more flexible and easier in Python 3's string formatting.  The basic gist is that the positions being referred to are on the right side of the string's format method as opposed to the left.  This usually results in fewer positions, and, hopefully, less confusion.


Quickie example:


>>> class Foo:
>>>    def __init__(self):
>>>        pass
>>>
>>> a = Foo()
>>> a.foo = 24
>>> a.bar = 33
>>> a.baz = 6
>>> # Python 2 string formatting
>>> # print '%d %d %d' % (a.foo, a.bar, a.baz)
>>> print('{0.foo} {0.bar} {0.baz}'.format(a))


24 33 6


It's true that the left side is a bit more involved now with the new string formatting.  There are more options for formatting, which I have omitted.  My main goal here is to understand the new formatting to the point where I can leverage it to my advantage in my work.  Let's see what, if anything, I've learned.


In the mining industry I did a lot data tracking on various production metrics - flows through pipes, broken rock moved to its final location, explosive power, etc.  One of the most challenging things to track was production in the open pit.  It all centered around big multimillion dollar electric shovels.


Anyway, to make a long story longer, by the time you worked your way up the object hierarchy to the shovel, you ended up with about 20 or more "fields" in the database, or members of your shovel object.  Smithing csv output from these objects could get pretty hairy, because there were so many fields to track, and they changed frequently.


Out of reality - it's just too complex - back to the programming world.  This Python 3 string formatting could have helped out some in this situation.  Let's go back to the Foo object:

UPDATE!
I had made an error in the print statment below by mixing quotes.  It is now corrected.
Yet another example of why code must be tested :-\


>>> # make two more Foo objects
>>> b = Foo()
>>> b.foo = 'hello'
>>> b.bar = 'cruel'
>>> b.baz = 'world'
>>> c = Foo()

>>> c.foo = '99.234'
>>> c.bar = '23.715'
>>> c.baz = '18.6'
>>> for objx in a, b, c:
>>>     for strx in 'foo', 'bar', 'baz':
>>>         print(('{0} = {1.' + strx + 
                            '}').format(
                           strx, objx))


foo = 24
bar = 33
baz = 6
foo = hello
bar = cruel
baz = world
foo = 99.234
bar = 23.715
baz = 18.6

Simple example, but it illustrates some of the utility of the new string formatting.  If I were trying to track a bunch of data, I would only have to redefine my "fields" once in a global constant ['foo', 'bar', 'baz'].  objx could be cycling (actually, iterating) through a list of objects.  Any writing or crunching could be done in a separate (small) function or method outside the loop (since ['foo', 'bar', 'baz'] is constant).

Turns out this new string formatting is something I could get used to . . .   ;-)

7 comments:

  1. Hi Carl,
    The example doesnt work for me (Py3.1 in windows).
    print(("{0.' + strx + '}").format(objx)) is calling an attribute of the objects a,b,c which does not exist.
    Maybe this new format is more powerful but seems to me more intrincate and in consequence error prone. Maybe I need more time
    Cheers

    ReplyDelete
  2. Joaquin,

    My bad - I mixed quotes while transposing the example. I apologize.

    The new format is fine; it is the author of the blog post who is error prone :-(

    Carl T.

    ReplyDelete
  3. Update: I have fixed the error Joaquin caught. I'll try testing the code when I get on a machine with 3.1 or 2.6 later today.

    ReplyDelete
  4. Hi Carl,
    Yes, now works on Py3.1. I checked it.
    Still, to produce the output in the blog, the print statement have to be modified to something like:
    print((strx + ' = {0.' + strx + '}').format(objx))

    Thanks, I think I got how it works: It first builds the string including the replacement field and then it format it.

    In the old style:
    print("%s = %s" %(strx, getattr(objx, strx)))
    Uf! I like better the old way...

    Joaquin

    ReplyDelete
  5. Joaquin,

    Thanks again. I'll never try to transcribe code to a blog again.

    I'm a lot more used to the old style, but I've got to leverage what's available. If I were a core language committer, I would probably have been against this change. But it's here, and we've got to play the hand we're dealt.

    Thanks again.

    ReplyDelete
  6. Carl,
    Well, the old style statement in my post runs perfect in 3.1. So for the moment...

    Do you know of some problem difficult to solve with %, that can be more easily done with .format?. I remember I read a blog about this time ago.
    Cheers

    ReplyDelete
  7. Joaquin,

    I had seen a good example of some marked up text (web page code) that seemed to make a good argument for it. Now I can't find it on Google.

    The best simple example I've seen is the centering one '{0:=^50}'.format(' HEADER ').

    Another one is the gettext related one where you're working with foreign languages and different word orders (you can use the same arguments to the format method in the same order).

    For a lot of common cases, it's a matter of taste.

    ReplyDelete