Python: String Formatting and Enumerate

by Chris Lesage on February 21, 2012

As a mostly self-taught Python scripter, I try to keep on top of best-practices and constantly learn, because I realize how easily bad habits can slip in. I’ve recently learned these bad habits are known as anti-patterns.

That said, I learned a couple new Python tricks this week.

1. Better String Formatting Using Format()

If you’ve used string formatting before, this is very similar, except the replacement fields are inside {} brackets, and you can use variables as keywords or an index.

examples:

print 'We are the {0} who say "{1}!"'.format('knights', 'Ni')
print 'We are the {1} who say "{0}!"'.format('Ni', 'knights')
print 'We are the {people} who say "{quote}!"'.format(people='knights', quote='Ni')

There are also lots of examples of how to format numbers or create precise column spacing. The “old string formatting” is eventually going to be removed from Python. There is likely no rush to switch, but it is less flexible and a bit harder to read:

print 'We are the %s who say %s!' % ('knights', 'Ni')

source: http://docs.python.org/tutorial/inputoutput.html

2. Looping With Enumerate()

One of the common things you want when looping through things is an index counter. You can do this a few various ways:
counter += 1
for i in range():
or you can use each.index() on array items. There is a better way:

Enumerate() is nice PEP-friendly way to get a “a compact, readable, reliable index notation” in loops. It works even when you don’t have an array to count, or a known length (for doing a range()). You use two variables in your for loop, and the first is your index. It is much nicer than having a separate counter or measuring the length of something which may change inside the loop.

(This is a PyMEL example in Maya and assumes you have a selection of objects.)

import pymel.core as pm

for i, each in enumerate(pm.selected()):
    print i, each

for i, each in enumerate(['some', 'array', 'here']):
    print i, each

3. Put Them Together

Let’s do a simple example where we also use .format() to rename a series of objects. I’ll use {1:02d} to automatically add frame-padding. {1:__} is the replacement field index. {__:02d} is the frame-padding. (You can also convert to percentages, hexadecimal, dates, change the column spacing, etc. etc. This is just one small example.)

for i, each in enumerate(pm.selected()):
    newName = '{0}_{1:02d}'.format('right_whisker', i)
    pm.rename(each, newName)

The nice thing about .format() is how easy it is to read and edit, since the replacement fields are clearly separated by brackets and you can pass any variables to them. Again this isn’t new, but it is easier to read than the old string formatting. So let’s play with this a bit and expand the example to automatically rename the object as “left” or “right”, depending on its translation in X.

for i, each in enumerate(pm.selected()):
    if each.tx.get() > 0:
        side = 'left'
    else:
        side = 'right'
    newName = '{0}_{1}_{2:02d}'.format(side, 'whisker', i)
    pm.rename(each, newName)

In the past, one of the most common ways I’ve created an index count is by measuring a length of a list or array or by using .index() to return the index of an array item. In comparison, this looks very ugly now. Plus, if you were to delete some objects inside the loop, the length of your range might change.

objects = pm.selected()
for i in range(0,len(objects)):
    print i, objects[i]

Two useful new tools for the arsenal. If you have any tips, suggestions or improvements, leave a comment. Next up, I’ll be getting back to the Mini Mammoth rigging after a bit of a hiatus.

{ 5 comments… read them below or add one }