views:

161

answers:

5

I made a python function to convert dictionaries to formatted strings. My goal was to have a function take a dictionary for input and turn it into a string that looked good. For example, something like "{'text':'Hello', 'blah':{'hi':'hello','hello':'hi'}}" would be turned into this:

text:
    Hello
blah:
    hi:
        hello
    hello:
        hi

This is the code I wrote:

indent = 0

def format_dict(d):
    global indent
    res = ""
    for key in d:
        res += ("   " * indent) + key + ":\n"
        if not type(d[key]) == type({}):
            res += ("   " * (indent + 1)) + d[key] + "\n"
        else:
            indent += 1
            res += format_dict(d[key])
            indent -= 1
    return res
#test
print format_dict({'key with text content':'some text', 
                  'key with dict content':
                  {'cheese': 'text', 'item':{'Blah': 'Hello'}}})

It works like a charm. It checks if the dictionary item is another dictionary, in which it process that, or something else, in which it would use that as the value. The problem is: I can't have a dictionary and a string together in a dictionary item. For example: if I wanted

blah:
    hi
    hello:
        hello again

there'd be no way to do it. Is there some way I could have something like a list item in a dictionary. Something like this "{'blah':{'hi', 'hello':'hello again'}}"? And if you provide a solution could you tell me how I would need to change my code (if it did require changes).
Note: I am using python 2.5

+2  A: 

You can express dictionaries as having lists of children:

{'blah': [
    'hi',
    {'hello':[
        'hello again'
    ]},
    {'goodbye':[
        'hasta la vista, baby'
    ]}
]}

A consequence of this is that each dictionary will have just a single key-value pair. On the plus side, it means you can have repeating keys and deterministic ordering, just like XML.

EDIT: On second thought, you could simply fold 'hello' and 'goodbye' into a single dictionary, though I would personally find that to be quite confusing, since you could now have a mish-mash of ordered and unordered stuff. So I guess the one-key-per-dictionary rule is more of a recommendation than a requirement.

Marcelo Cantos
A: 

A dictionary is a mapping, so you can't have a key without a value. However, the closest to that would be for a key to have the value of None. Then add a check for None before the if not type(d[key]) == type({}): line and continue to avoid printing the value. BTW, that line would be better as if not isinstance(d[key], dict):.

Max Shawabkeh
+2  A: 

You can simply store a list in the dictionary. Also, it's better not to use a global to store the indentation. Something along the lines of:

def format_value(v, indent):
    if isinstance(v, list):
         return ''.join([format_value(item, indent) for item in v])
    elif isinstance(v, dict):
         return format_dict(v, indent)
    elif isinstance(v, str):
         return ("   " * indent) + v + "\n"

def format_dict(d, indent=0):
    res = ""
    for key in d:
        res += ("   " * indent) + key + ":\n"
        res += format_value(d[key], indent + 1)
    return res
Ray
How would I use this function?
None
Using your example, you would define the dictionary as d = {'blah':['hi', {'hello':'hello again'}]} In other words, the values can be dictionaries, lists, or strings. Then call format_dict(d).
Ray
A: 

Why not just use yaml?

import yaml
import StringIO

d = {'key with text content':'some text', 
     'key with dict content':
     {'cheese': 'text', 'item': {'Blah': 'Hello'}}}
s = StringIO.StringIO()
yaml.dump(d, s)
print s.getvalue()

this prints out:

key with dict content:
  cheese: text
  item: {Blah: Hello}
key with text content: some text

and you can load it back in to a dict

s.seek(0)
d = yaml.load(s)
Toni Ruža
First of all, I don't have yaml installed (I don't know if I could install it). Second, this what I want, because if your example is correct, second level dictionaries would not be processed as well, but instead just displayed as "item: {Blah: Hello}".
None
Ok, thought I'd ask just in case.
Toni Ruža
A: 

As mentioned, you'll need to use lists as values whenever you want to have a text and dictionary at the same level. here's some code that prints what you need.

# -*- coding: utf-8 -*-
#!/usr/bin/env python2.5
# http://stackoverflow.com/questions/2748378/python-dictionary-formating

def pretty_dict(d, indent=0, spacer='.'):
    """
    takes a dict {'text':'Hello', 'blah':{'hi':'hello','hello':'hi'}}
    And prints:

    text:
        Hello
    blah:
        hi:
            hello
        hello:
            hi

    """
    kindent = spacer * indent

    if isinstance(d, basestring):
        return kindent + d

    if isinstance(d, list):
        return '\n'.join([(pretty_dict(v, indent, spacer)) for v in d])

    return '\n'.join(['%s%s:\n%s' % (kindent, k, pretty_dict(v, indent + 1, spacer)) 
        for k, v in d.items()])


test_a = {'text':'Hello', 'blah':{'hi':'hello','hello':'hi'}}
test_b = {'key with text content':'some text', 'key with dict content':
    {'cheese': 'text', 'item':{'Blah': 'Hello'}}}
test_c = {'blah':['hi', {'hello':'hello again'}]}
test_d = {'blah': [
    'hi',
    {'hello':[
        'hello again'
    ]},
    {'goodbye':[
        'hasta la vista, baby'
]}
]}


if __name__ == '__main__':
    print pretty_dict(test_a)
    print pretty_dict(test_b)
    print pretty_dict(test_c)
    print pretty_dict(test_d)
Jj