0

So I have a text file for example:

RootObject: Sun

Object: Sun
Satellites: Mercury,Venus,Earth,Mars,Jupiter,Saturn,Uranus,Neptune,Ceres,Pluto,Haumea,Makemake,Eris
Radius: 20890260
Orbital Radius: 0

Object: Earth
Orbital Radius: 77098290
Period: 365.256363004
Radius: 6371000.0
Satellites: Moon

Object: Moon
Orbital Radius: 18128500
Radius: 1737000.10
Period: 27.321582

and I'm trying to input it into a dictionary. This is what I have so far but I keep getting an error...

#d = dictionary
#new_d = new dictionary

file = open("data.txt","r")
d = {}
def data(file):
    for line in file:
        if line != line.strip:
            continue
        line = line.strip()
        key = line.split(":")
        val = line.split(":")
        if key in d and key == "Object":
            print(d)
        d[key] = val
    print(d)

new_d = {}
with file as x:
    for d in data(x):
        new_d[d["Object"]] = d
print(nd)

I should be getting something like this:

{' Earth': {'Satellites': ' Moon', 'Orbital Radius': ' 77098290', 'Object': ' Earth', 'Radius': ' 6371000.0', 'Period': ' 365.256363004'}, ' Moon': {'Orbital Radius': ' 18128500', 'Object': ' Moon', 'Radius': ' 1737000.10', 'Period': ' 27.321582'}, ' Sun': {'Satellites': ' Mercury,Venus,Earth,Mars,Jupiter,Saturn,Uranus,Neptune,Ceres,Pluto,Haumea,Makemake,Eris', 'Orbital Radius': ' 0', 'Object': ' Sun', 'Radius': ' 20890260', 'RootObject': ' Sun'}}

I get this error:

Traceback (most recent call last):
  File "planet2.py", line 21, in <module>
    for d in data(x):
TypeError: 'NoneType' object is not iterable
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Romulus
  • 138
  • 2
  • 12
  • What is the error you are getting? It will help us help you to debug if you include the full traceback in your question. – Blckknght Nov 22 '12 at 20:12
  • oh right sorry. I added the error now. – Romulus Nov 22 '12 at 20:22
  • Is this a homework assignment, @Artorius? I'm writing up an answer, but I don't want to do too much work for you if this is something you're supposed to be learning from. – Blckknght Nov 22 '12 at 20:48
  • yes it is. I already have an answer but i would like to make it simpler and thats what I was trying above when i ran into the error... @Blckknght – Romulus Nov 22 '12 at 20:50
  • basically I'm looking for the simplest code possible to do the same thing. I'm not sure if thats possible or if the code I wrote up here already is sufficient. – Romulus Nov 22 '12 at 20:55

2 Answers2

2

This will do nicely:

file = open("data.txt","r")

def data(file):
    dic = {}
    for line in file:
        # If line not blank
        if line.strip() != '':
            key,value = line.split(":")
            if key == 'RootObject':
                dic[key] = value.strip()
            elif key == 'Object':
                # Get the Object value i.e Earth, Moon, Sun                
                obj = value.strip()
                # Create entry with obj key and blank dictionary value
                dic[obj]={}
            else:
                # Populate the blank dictionary with key, value pairs
                dic[obj][key] = value.strip()
    return dic

planets = data(file)

# Usage
print planets
print planets['Earth']
print planets['Earth']['Radius']

Output:

# The whole dictionary 
{'Sun': {'Satellites': 'Mercury,Venus,Earth,Mars,Jupiter,Saturn,Uranus,Neptune,Ceres,Pluto,Haumea,Makemake,Eris', 'Orbital Radius': '0', 'Radius': '20890260'}, 'Moon': {'Orbital Radius': '18128500', 'Radius': '1737000.10', 'Period': '27.321582'}, 'Earth': {'Satellites': 'Moon', 'Orbital Radius': '77098290', 'Radius': '6371000.0', 'Period': '365.256363004'}}

# The Earth dictionary
{'Satellites': 'Moon', 'Orbital Radius': '77098290', 'Radius': '6371000.0', 'Period': '365.256363004'}

# The Earth's radius
6371000.0
Chris Seymour
  • 83,387
  • 30
  • 160
  • 202
1

There are a few different errors in your code. The one that's causing your Exception is that your function data writes into a global variable and doesn't return anything, but your later code expects it to return something iterable, such as a sequence or generator.

You could fix this either by making the top-level code iterate directly on the global dictionary, or you could get rid of the global and create the dictionary within data and return it at the end. I suggest the latter, since global variables are hard to deal with as your code gets more complicated.

Here's a rough outline of how that should work (I'm leaving the middle part sketchy, since I'll discuss it later):

def data(file):
    objects = {}

    # add stuff to objects dict

    return objects

Your next error is with stripping your lines. Your code currently tests each line for inequality with its own strip method. This is an error in Python 3, since line and line.strip have incomparable types. But even if it worked, it would be meaningless. I suspect you were trying to eliminate empty lines, by stripping them first and rejecting any that are empty afterwards. Here's how you can do that:

if not line.strip():
    continue

This is an example of what some people in the Python community call "look before you leap" (LBYL) programming style, since you're checking for something that may be a problem before it becomes one. An alternative is the "easier to ask forgiveness than permission" (EAFP) style where you simply wrap the area where the trouble might emerge in a try block and catch the exceptions that are generated. EAFP-style is sometimes considered more "Pythonic", so I'll show that style a bit later.

The next error is a logical one, not something that will cause an error. You're splitting your line and you want to get the two parts of it into the variables key and value. However, you're doing two separate assignments to those variables, and in fact they're ending up with the same value in each. This is a place you get to use a cool feature of Python syntax, unpacking. Instead of assigning each variable separately, you can assign a two-valued sequence (such as a list or tuple) to both of them together. Python will take care of giving the first value to the first variable and the second value to the second variable. Here's what it looks like:

key, value = line.split(":")

This will fail if there's no colon in the line of course, so it's a good place for us to put a try block if we're using the EAFP-style of coding. Here's one way to do it:

try:
    key, value = line.split(":")
except ValueError:
    continue

You could instead put the try block around everything that remains in the loop, then let the except block contain only pass (which does nothing, but ignores the exception).

Finally, the last logical error you have has to do with how you build your nested dictionaries. Your current approach is to first build a single dictionary with all the keys and values from your file, then split them up into separate pieces, one per celestial object. However, this doesn't work if the keys for each object are the same. Since each object has an "Orbital Radius" key, for instance, they're all going to be writing over each other putting that key into the single dictionary.

@sudo_o's answer shows how to build the inner dictionary and fill it with values (it is almost identical to what I was going to write). I just wanted to post the rest of my explanations!

Blckknght
  • 100,903
  • 11
  • 120
  • 169
  • oh wow Thank you so much! That makes complete sense! question, could I add the Try statement to Sudo_o's code ? would that make it even more functionally correct? – Romulus Nov 22 '12 at 21:13
  • because I definitely didn't think of the potential colon error. – Romulus Nov 22 '12 at 21:15
  • 1
    Some sanity checking/input validation is always a good thing, I'm better a writing code than prose so +1 from me (in 2 hours.. used my daily votes haha) – Chris Seymour Nov 22 '12 at 21:28
  • 1
    @Artorius: To make @sudo_o's code use EAFP-style error checking rather than LBYL, replace is `if line.strip != ""` line with a `try`, and add `except ValueError: pass` at the same indentation level, in between the `else` block and the `return` statement. – Blckknght Nov 22 '12 at 21:40
  • ohhh I see ok. I could do that as well. Which do you think is better? or maybe works better? @Blckknght – Romulus Nov 22 '12 at 21:52
  • @Blckknght I guess it doesn't really matter eh? they both technically do the same thing. – Romulus Nov 22 '12 at 21:56
  • 1
    @Artorius Because the code is for one specific structured file the method I used is perfectly acceptable, however if you where writing more general code for multiple files with potential differences/formats then it would be a good idea to think about exceptional handling *(IMO)*. – Chris Seymour Nov 22 '12 at 22:07
  • @sudo_o Oh ok. Do you mean like if different values were used? or multiple data files? Because all I'm trying to get is to read off one file with potentially differing values each time. – Romulus Nov 22 '12 at 22:11
  • 1
    Both - say a value was `Object: Cygnus:OB2-12` then the second `:` would cause a `ValueError` or maybe another file is using a `-` as a delimiter such as `Object- Earth`. If the structure is consistence with your example you will have no problems. – Chris Seymour Nov 22 '12 at 22:22
  • oh ok. So if the data file has the same format you mean? Yea that makes sense. – Romulus Nov 23 '12 at 13:29