Learning Python

Over this past summer I learned Python by studying the book “A Byte of Python”.

Now I’m trying to apply what I know into some simple programs that will help me.

I successfully created a simple program that writes a dictionary to a file (I used cPickle for this) and then reads, edits, and rights to that file, thus creating a simple database in a way.

But, I recently found out that you can have a list with dictionaries as items in the list. So, I want to modify my program to this.

However, I can’t figure out how to have the program figure out if a certain dictionary is in the list.

Here is what I have:


import operator

n = "Name"
t = "Title"
s = "Species"
r = "Rank"

myList = [
{
n: "Tynach",
t: "First Speaker",
s: "Human",
r: 1
},
{
n: "Drak",
t: "Second Speaker",
s: "Locharnian",
r: 2
}
]

myList.sort(key=operator.itemgetter("Rank"))

def read():
	print
	for item in myList:
		print item[n],
		print "is the %s." % (item[t])
		print

def add():
	name = str(raw_input("Name to add: "))
	title = str(raw_input("Title: "))
	species = str(raw_input("Species: "))
	rank = int(raw_input("Numerical rank: "))
	if name not in myList[]				#Incomplete. Need to figure this out!

read()

The comment about it being incomplete is where I need help. I need it to figure out if a name is already in one of the dictionaries, so that it can either make a new dictionary or simply overwrite the old one with the same “Name” value.

For instance, if I type in for name “Tynach”, I want it to simply overwrite the dictionary with the name “Tynach”, and not make a new entry for “Tynach”.

However, I also want it to simply make a new dictionary if the name is not already in there. For instance, if I put “Blenderhead” for a name, I don’t want it to overwrite “Tynach”.

Hence the incompleted if statement.

Since this does not have to do with Blender, this may go in Off Topic. However, I figured I’d ask here since this is the Python area.

Note: This is not the program that writes the output to a file. That program I wrote is this:


#!/usr/bin/python

#imports
import cPickle as p

#define database file
database = "database.data"

#define user database from file
f = file(database)
stored_data = p.load(f)
users = stored_data

#read database
def read():
	global users
	print
	for name, title in users.items():
		print '%s is the %s.' % (name, title)
	print

print
print "Current users:"
read()

#write database file
def write(x):
	f = file(database, 'w')
	p.dump(x, f)
	f.close()

def writing():
	global users
	write(users)

#Interface and choices
def cont():
	print
	a = str(raw_input("Again (y/n)? 
"))
	if a == "y":
		read()
		choice()
	elif a == "n":
		print
		print "Done."
	else:
		print
		print "Invalid. Try again."
		cont()

def choice():
	a = str(raw_input("Add/change or delete name (a/d)? 
"))
	if a == "a":
		add()
	elif a == "d":
		delete()
	else:
		print "Invalid. Try again."
		choice()

def add():
	print
	a = str(raw_input("Are you sure you want to add or change a name (y/n)? 
"))
	if a == "y":
		print
		add_name()
	elif a == "n":
		print
		print "Canceled"
	else:
		print "Invalid. Try again."
		add()

def add_name():
	global users
	name = str(raw_input("Name to add or change: "))
	title = str(raw_input("Title: "))
	users[name] = title
	writing()
	cont()

def delete():
	print
	a = str(raw_input("Are you sure you want to delete a name (y/n)? 
"))
	if a == "y":
		print
		del_name()
	elif a == "n":
		print
		print "Canceled"
	else:
		print "Invalid. Try again."
		delete()

def del_name():
	global users
	name = str(raw_input("Name to delete: "))
	if name not in users:
		print "Invalid. Try again."
		del name
		delete()
	else:
		del users[name]
		writing()
	cont()

#initiate interface
choice()

#read again to verify changes
read()

I want my new program to have about the same functionality, but to be able to sort the list by the rank integer, and to have multiple things listed under each person. I chose the “list of dictionaries” way with the new version because of this.

You could do something like:


for entry in myList:
     if name in entry:
           # Its here do whatever
          break #maybe break so you don't keep looking
     else:
           # Its not here do whatever
          break

Another option you may consider is a dictionary of dictionaries i.e.


myDict = {"Tynach": {t:"First Speaker ...}, "Drak": {...}}

You could then do very easily:


if name in myDict:
     #stuff
else:
     #stuff

Just saw your sort line, though, so this may not be the best option here for that type of operation, but it might be something to consider.

Hope that helps somehow!

Ok, so how do I make it overwrite the one I want it to after it detects that the name is already there?

I thought about dictionaries of dictionaries, but I didn’t go that route because I wanted to sort them in various ways.

Just like you normally would.

If you have a dict:


myDict = {'name': 'john', 'phone':'876-5309'}

and then we want to change his phone number, we just assign:


myDict['phone'] = '123-876-5309'

In our case our loop would look like:


for entry in myList:
      if name in entry:
           entry['rank'] = 2
...

For example sets the entry’s rank to 2. That would mean if you looked for Tynach’s entry, (i.e. name == ‘Tynach’), it would make the rank in that dictionary = 2.

Ok, I’ve tried that, and it doesn’t seem to work.

Code:

import operator

n = "Name"
t = "Title"
s = "Species"
r = "Rank"

myList = [
{
n: "Tynach",
t: "First Speaker",
s: "Human",
r: 1
},
{
n: "Drak",
t: "Second Speaker",
s: "Locharnian",
r: 2
}
]

myList.sort(key=operator.itemgetter("Rank"))

def read():
    print
    for item in myList:
        print item[n],
        print "is %d." % (item[r])
    print

def add():
    name = str(raw_input("Name to add: "))
    rank = int(raw_input("Numerical rank: "))
    for entry in myList:
        if name in entry:
            r = rank
        else:
            print "error"

read()

add()

myList.sort(key=operator.itemgetter("Rank"))

read()

That doesn’t change the rank. Here’s my output when changing Tynach to rank 3:

$ python list_of_dictionaries.py

Tynach is 1.
Drak is 2.

Name to add: Tynach
Numerical rank: 3
error
error

Tynach is 1.
Drak is 2.

$

Am I doing something wrong? I can’t seem to wrap my head around this problem… And there are no errors except for the one I put in, so I can’t easily debug this…

EDIT 1: I changed the code to match yours, and I still get the same thing.

New code:

def add():
    name = str(raw_input("Name to add: "))
    rank = int(raw_input("Numerical rank: "))
    for entry in myList:
        if name in entry:
            entry["Rank"] = rank
        else:
            print "error"

EDIT 2: I think the problem is in the if statement. I don’t think its detecting the name in the entry properly. Otherwise it would go to the “entry[“Rank”] = rank” block instead of the “print “error”” block of code.

There should be a better way to do this…

Note: Tynach and Drak are characters of mine I write about in some stories that I plan to turn to movies or games in the future. I’m using them as examples for the purpose of getting this to work.

Oops… Shows me for thinking late at night ;).

It should be:


if name in entry.values():
 ...

To explain, the previous code checked for if it was a key, not a value…

Actually, on second thought, that’s not that great. You really need to test that its the value of the name key:


if n in entry and entry[n] == name: #tests to make sure we have both the key and the value
...

Which could probably be rewritten more succinctly as


if (n, name) in entry.items():
...

OH…

That explains it! Thanks!

New code:

import operator

n = "Name"
t = "Title"
s = "Species"
r = "Rank"

myList = [
{
n: "Tynach",
t: "First Speaker",
s: "Human",
r: 1
},
{
n: "Drak",
t: "Second Speaker",
s: "Locharnian",
r: 2
}
]

myList.sort(key=operator.itemgetter("Rank"))

def read():
	print
	for item in myList:
		print item[n],
		print "is %d." % (item[r])
	print

def add():
	name = str(raw_input("Name to add: "))
	rank = int(raw_input("Numerical rank: "))
	for entry in myList:
		if name in entry.values():
			entry[r] = rank
			break
		else:
			print "error"

read()

add()

myList.sort(key=operator.itemgetter("Rank"))

read()

and output:

$

Tynach is 1.
Drak is 2.

Name to add: Tynach
Numerical rank: 3

Drak is 2.
Tynach is 3.

$

Well, I’m having problems again.

I was starting to experience weird behavior with that, so I made a delete function that would try to delete a name, and this is the result of that:

def delete():
	global myList
	print "testing delete"
	name = str(raw_input("Insert name to delete: "))
	for entry in myList:
		if name in entry.values():
			del entry
			write(myList)
		else:
			print "Not detecting if!"
			del name
			delete()

And the result of just running that block:

testing delete
Insert name to delete: Tynach
Not detecting if!

So, this isn’t working.

I would post more, but I don’t have time right now. I’ll post more about the problem later when I’m actually at home.