Wednesday, January 28, 2009

Canonical Python __str__ method

Here's a python class that shows some approaches for building up a meaningful __str__() to display the class's data attributes (like overriding toString() in java). The first way is to show a specific subset (or ordering) of attributes. The second way (currently commented out) just shows all the classes attributessorted by name. I also show how to make it work with a subclass--either leveraging the superclass __str__() method or doing it all from within the subclass. Of course, it should be easy to adjust the output to your own needs (padding, etc).

Nothing earth-shattering here, but it can save some work if it's a big class with lots of attributes. The example should work with modern CPython, including Python 3:

class X:
def __init__(self):
self.a = "apple"
self.b = "banana"
self.c = "cranberry"

def __str__(self):

# to show specific variables
showList = ["a", "b"]

# to show include all variables in sorted order
#showList = sorted(set(self.__dict__))

return ("X(%i):\n" % id(self)) + "\n".join([" %s: %s" % (key.rjust(8), self.__dict__[key]) for key in showList])

class Y(X):

def __init__(self):
X.__init__(self)
self.d = "date"
self.e = "elderberry"

def __str__(self):
showList = ["a","d","e"]

# if you want to use the superclass as-is and supplement from this subclass
# return X.__str__(self) + "\n Y:\n" + "\n".join([" %s: %s" % (key.rjust(8), self.__dict__[key]) for key in showList])

# if you want to do everything from this subclass
return ("Y(%i):\n" % id(self)) + "\n".join([" %s: %s" % (key.rjust(8), self.__dict__[key]) for key in showList])

print(Y())

This outputs:

Y(12172528):
a: apple
d: date
e: elderberry


Okay, but maybe this can just be a helper method of some kind. The class name info can be pulled of of the self.__class__ object, and then the function can be more generic:

def str_impl(anInstance, displayAttributeList):

classLine = "%s.%s(%i):\n" % (anInstance.__class__.__module__, anInstance.__class__.__name__, id(anInstance))
return (classLine + "\n".join([" %s: %s" % (key.rjust(8), anInstance.__dict__[key]) for key in displayAttributeList]))

class XX:

showList = ["a", "b"]

def __init__(self):
self.a = "apple"
self.b = "banana"
self.c = "cranberry"

def __str__(self):
return str_impl(self, self.__class__.showList)


class YY(XX):
showList = ["a","d","e"]

def __init__(self):
X.__init__(self)
self.d = "date"
self.e = "elderberry"

This outputs:

__main__.YY(12172784):
a: apple
d: date
e: elderberry



Note that __main__ is the module since the classes are defined in the "main" scope. The correct module/package would print if I had used one. Again, you wouldn't even need to bother with this for a small class--but for a class with a lot of attributes (which might be a sign it's time to break it up anyway ;-), it saves some tedium.

1 comment:

Unknown said...
This comment has been removed by a blog administrator.