450 Pound Library Program
bruno at modulix
onurb at xiludom.gro
Wed Feb 8 08:27:36 EST 2006
mwt wrote:
> So in a further attempt to learn some Python, I've taken the little
> Library program
> (http://groups.google.com/group/comp.lang.python/browse_thread/thread/f6a9ccf1bc136f84)
> I wrote and added several features to it. Readers now quit when they've
> read all the books in the Library. Books know how many times they've
> been read. Best of all, you can now create your own list of books to
> read!
>
> Again, the point of all this is to get used to programming in Python.
> So although the program is trivial, any feedback on style, structure,
> etc. would be much appreciated. I'm a convert from Java,
Welcome !-)
> so I've
> probably got some unconscious Javanese in there somewhere. Help me get
> rid of it!
Well, apart from namingConventions (vs naming_conventions), I did not
spot too much javaism in your code.
> Here's the new, improved program:
> [code]
> #!/usr/bin/python
> # Filename: Library.py
> # author: mwt
> # Feb, 2006
>
> import thread
> import time
> import threading
> import random
>
>
>
> class Library2:
Old-style classes are deprecated, use new-style classes instead:
class Library2(object):
> def __init__(self, listOfBooks, totalBooks):
> self.stacks = listOfBooks
If its a collection of books, why not call it 'books' ?
> self.cv = threading.Condition()
> self.totalBooks = totalBooks
What is 'totalBooks' ?
> def checkOutBook(self, readerName):
> "'Remove book from the front of the list, block if no books are
> available'"
Why not using triple-quoted strings ?
> self.cv.acquire()
> while len(self.stacks) == 0:
> self.cv.wait()
> print "%s waiting for a book..." %readerName
> book = self.stacks.pop(0)
This last line will crash (IndexError) on an empty list, and then the
resource may not be released... A first step would be to enclose this in
a try/finally block.
> self.cv.release()
> return book
>
> def returnBook(self, returnedBook):
> "'put book at the end of the list, notify that a book is
> available'"
> returnedBook.wasRead()
> self.cv.acquire()
> self.stacks.append(returnedBook)
> self.cv.notify()
> self.cv.release()
You have a recurring pattern in the last 2 methods: aquire/do
something/release. You could factor this out in a method decorator
(don't forget that functions and methods are objects too, so you can do
a *lot* of things with them).
> class Reader(threading.Thread):
>
> def __init__(self, library, name, readingSpeed, timeBetweenBooks):
> threading.Thread.__init__(self)
or :
super(Reader, self).__init__()
but this wouldn't make a big difference here
> self.library = library
> self.name = name
> self.readingSpeed = readingSpeed
> self.timeBetweenBooks = timeBetweenBooks
> self.book = ""
You later user Reader.book to hold a reference to a Book object, so it
would be wiser to initialize it with None.
> self.numberOfBooksRead = 0
>
> def run(self):
> "'Keep checking out and reading books until you've read all in
> the Library'"
> while self.numberOfBooksRead < self.library.totalBooks:
> self.book = self.library.checkOutBook(self.name)
> print "%s reading %s" %(self.name, self.book.title),
> time.sleep(self.readingSpeed)
> self.numberOfBooksRead += 1
> self.library.returnBook(self.book)
> print "%s done reading %s" %(self.name, self.book.title),
> print"Number of books %s has read: %d" %(self.name,
> self.numberOfBooksRead)
> self.bookName = ""
> time.sleep(self.timeBetweenBooks)
> print "%s done reading." %self.name
>
> class Book:
> def __init__(self, author, title):
> self.author = author
> self.title = title
> self.numberOfTimesRead = 0
> #print "%s,%s" % (self.author, self.title),#print as books are
> loaded in
>
> def wasRead(self):
> self.numberOfTimesRead += 1
> print "Number of times %s has been read: %d" %(self.title,
> self.numberOfTimesRead)
>
> if __name__=="__main__":
>
You should define a main() function and call it from here.
> print "\nWELCOME TO THE THURMOND STREET PUBLIC LIBRARY"
> print "Checking which books are avialable...\n"
s/avialable/available/ !-)
> try:
> theBookFile = open("books.txt", "r")#Create your own list of
> books!
Filenames should not be hardcoded.
(Ok, you probably know this already, but I thought it would be better to
point this out for newbie programmers)
> stacks = []#a place to put the books
> for line in theBookFile.readlines():
> L = line.split (",") # a comma-delimited list
Mmm... may not be the best format. What if there are commas in the book
title ? Hint : there's a CSV module in the standard lib.
Also, by convention, UPPERCASE identifiers are considered as CONSTANTS
> author = L[0]
> bookName = L[1]
> newBook = Book(author, bookName)
> stacks.append(newBook)
You can smash all this in a single line:
stacks = [Book(line.split(",", 1) for line in theBookFile]
> theBookFile.close()
> except IOError:
> print "File not found!"
An IOError can result from a lot of different reasons (like user not
having read access on the file). So don't assume the file was not found:
except IOError, e:
print "Could not open file %s : %s" % ('books.txt', e)
You may also want to exit here...
> #string = "How many books would you like in the Library?[1-" +
> str(len(stacks)) + "]"
> totalBooks = input("How many books would you like in the
> Library?[1-" + str(len(stacks)) + "]")
1/ use raw_input() and *validate* user data
2/ use string formatting:
prompt = "How many books would you like in the "
" Library? [1-%d]" % len(stacks)
> stacks[totalBooks: len(stacks)] = []
stacks = stacks[:totalBooks]
May be less efficient, but much more readable IMHO.
Also, the naming is somewhat misleading. Something along the line of
numberOfBooks or booksCount would probably be better.
> print "Number of books in the Library is:", len(stacks)
You've called len(stacks) 3 times in 3 lines.
> library = Library2(stacks, totalBooks)
What's the use of totalBooks here ? The Library2 class can figure it out
by itself, so it's useless - and it goes against DRY and SPOT.
hint: given the use of Library.totalBook, you'd better make it a
computed attribute
class LibraryN(object):
booksCount = property(fget=lambda self: len(self.books))
> readers = input("\nHow many readers would you like?")
idem. Also, something like readersCount would be less misleading (I
first thought it was a collection of Reader objects)
> print "Number of readers is:", readers, "\n"
> for i in range(0,readers):
> newReader = Reader(library, "Reader" + str (i),
> random.randint(1,7), random.randint(1,7))
performance hint: names are first looked up in the local namespace:
randint = random.randint
for i in range(0, readers):
newReader = Reader(library,
"Reader%d" % i,
randint(1,7),
randint(1,7))
> newReader.start()
My 2 cents...
(snip)
--
bruno desthuilliers
python -c "print '@'.join(['.'.join([w[::-1] for w in p.split('.')]) for
p in 'onurb at xiludom.gro'.split('@')])"
More information about the Python-list
mailing list