By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
435,531 Members | 2,219 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 435,531 IT Pros & Developers. It's quick & easy.

450 Pound Library Program

P: n/a
mwt
So in a further attempt to learn some Python, I've taken the little
Library program
(http://groups.google.com/group/comp....a9ccf1bc136f84)
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, so I've
probably got some unconscious Javanese in there somewhere. Help me get
rid of it!

Here's the new, improved program:
Expand|Select|Wrap|Line Numbers
  1. #!/usr/bin/python
  2. # Filename: Library.py
  3. # author: mwt
  4. # Feb, 2006
  5.  
  6. import thread
  7. import time
  8. import threading
  9. import random
  10.  
  11.  
  12.  
  13. class Library2:
  14. def __init__(self, listOfBooks, totalBooks):
  15. self.stacks = listOfBooks
  16. self.cv = threading.Condition()
  17. self.totalBooks = totalBooks
  18.  
  19. def checkOutBook(self, readerName):
  20. "'Remove book from the front of the list, block if no books are
  21. available'"
  22. self.cv.acquire()
  23. while len(self.stacks) == 0:
  24. self.cv.wait()
  25. print "%s waiting for a book..." %readerName
  26. book = self.stacks.pop(0)
  27. self.cv.release()
  28. return book
  29.  
  30. def returnBook(self, returnedBook):
  31. "'put book at the end of the list, notify that a book is
  32. available'"
  33. returnedBook.wasRead()
  34. self.cv.acquire()
  35. self.stacks.append(returnedBook)
  36. self.cv.notify()
  37. self.cv.release()
  38.  
  39. class Reader(threading.Thread):
  40.  
  41. def __init__(self, library, name, readingSpeed, timeBetweenBooks):
  42. threading.Thread.__init__(self)
  43. self.library = library
  44. self.name = name
  45. self.readingSpeed = readingSpeed
  46. self.timeBetweenBooks = timeBetweenBooks
  47. self.book = ""
  48. self.numberOfBooksRead = 0
  49.  
  50. def run(self):
  51. "'Keep checking out and reading books until you've read all in
  52. the Library'"
  53. while  self.numberOfBooksRead < self.library.totalBooks:
  54. self.book = self.library.checkOutBook(self.name)
  55. print "%s reading %s" %(self.name, self.book.title),
  56. time.sleep(self.readingSpeed)
  57. self.numberOfBooksRead += 1
  58. self.library.returnBook(self.book)
  59. print "%s done reading %s" %(self.name, self.book.title),
  60. print"Number of books %s has read: %d" %(self.name,
  61. self.numberOfBooksRead)
  62. self.bookName = ""
  63. time.sleep(self.timeBetweenBooks)
  64. print "%s done reading." %self.name
  65.  
  66. class Book:
  67. def __init__(self, author, title):
  68. self.author = author
  69. self.title = title
  70. self.numberOfTimesRead = 0
  71. #print "%s,%s" % (self.author, self.title),#print as books are
  72. loaded in
  73.  
  74. def wasRead(self):
  75. self.numberOfTimesRead += 1
  76. print "Number of times %s has been read: %d" %(self.title,
  77. self.numberOfTimesRead)
  78.  
  79. if __name__=="__main__":
  80.  
  81. print "\nWELCOME TO THE THURMOND STREET PUBLIC LIBRARY"
  82. print "Checking which books are avialable...\n"
  83. try:
  84. theBookFile = open("books.txt", "r")#Create your own list of
  85. books!
  86. stacks = []#a place to put the books
  87. for line in theBookFile.readlines():
  88. L = line.split (",")  # a comma-delimited list
  89. author = L[0]
  90. bookName =  L[1]
  91. newBook = Book(author, bookName)
  92. stacks.append(newBook)
  93. theBookFile.close()
  94. except IOError:
  95. print "File not found!"
  96. #string = "How many books would you like in the Library?[1-" +
  97. str(len(stacks)) + "]"
  98. totalBooks = input("How many books would you like in the
  99. Library?[1-" + str(len(stacks)) + "]")
  100. stacks[totalBooks: len(stacks)] = []
  101. print "Number of books in the Library is:", len(stacks)
  102. library = Library2(stacks, totalBooks)
  103. readers = input("\nHow many readers would you like?")
  104. print "Number of readers is:", readers, "\n"
  105. for i in range(0,readers):
  106. newReader = Reader(library, "Reader" + str (i),
  107. random.randint(1,7), random.randint(1,7))
  108. newReader.start()
  109.  
And here's a handy text file of books for you, so you don't have to
make your own:
Expand|Select|Wrap|Line Numbers
  1. Conrad, Heart of Darkness
  2. Kafka, Die Verwandlung
  3. Hemingway, For Whom the Bell Tolls
  4. James Joyce, Dubliners
  5. Moliere, Cyrano de Bergerac
  6. William Golding, Lord of the Flies
  7. Dostoevski, Crime and Punishment
  8. Cervantes, Don Quixote
  9. Camus, L'Etranger
  10. Tolstoy, War and Peace
  11. Poe, Tales
  12. Faulkner, The Sound and the Fury
  13. Orwell, 1984
  14. Fitzgerald, The Great Gatsby
  15. Steinbeck, The Grapes of Wrath
  16. Huxley, Brave New World
  17. Twain, The Adventures of Huckleberry Finn
  18. Mann, Der Tod in Venedig
  19. Kesey, Sometimes a Great Notion
  20. Pynchon, Gravity's Rainbow
  21. McEwan, The Cement Garden
  22. Mįrquez, Cien Ańos de Soledad
  23. Salinger, The Catcher in the Rye
  24. Miltion, Paradise Lost
  25. Chapman et al , The Pythons
  26.  
Feb 7 '06 #1
Share this Question
Share on Google+
10 Replies


P: n/a
just a few style notes...
def checkOutBook(self, readerName):
"'Remove book from the front of the list, block if no books are
available'"
I don't understand what "' is supposed to imply. If you
meant to use triple quoting, you need to use ''' or """.
Then the string can contain line breaks. (Perhaps you have
a really stupid email client/news client/editor/whatever that
replaces ''' with "'? Please loose that or use """.)

Also, please use lines that are short enough not to wrap
around. Line wrapping makes the code very ugly. Do like this:

def checkOutBook(self, readerName):
"""Remove book from the front of the list, block if no
books are available."""

[...] for line in theBookFile.readlines():
In modern Python you simply write:
for line in theBookFile:
L = line.split (",") # a comma-delimited list
author = L[0]
bookName = L[1]
Why bother with L? The follwing is as clear I think, and solves
the problem of commas in the title. Also, don't put a space between
the callable and the parenthesis please. See the Python style guide,
PEP 008.

author, bookName = line.split(",", 2)

[...] totalBooks = input("How many books would you like in the
Library?[1-" + str(len(stacks)) + "]")


Again, don't make the lines so long. You don't have to do that.
You can break lines freely inside (), {} and [], and adjacent
string literals are automatically concatenated.

totalBooks = input("How many books would you like in "
"the Library?[1-%d]" % len(stacks))
Feb 8 '06 #2

P: n/a
mwt wrote:
So in a further attempt to learn some Python, I've taken the little
Library program
(http://groups.google.com/group/comp....a9ccf1bc136f84)
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 'o****@xiludom.gro'.split('@')])"
Feb 8 '06 #3

P: n/a
Magnus Lycka wrote:
just a few style notes...
(snip)
Why bother with L? The follwing is as clear I think, and solves
the problem of commas in the title. Also, don't put a space between
the callable and the parenthesis please. See the Python style guide,
PEP 008.

author, bookName = line.split(",", 2)
"toto, tata, tutu".split(",", 2) ['toto', ' tata', ' tutu']


You want line.split(',', 1) here.

--
bruno desthuilliers
python -c "print '@'.join(['.'.join([w[::-1] for w in p.split('.')]) for
p in 'o****@xiludom.gro'.split('@')])"
Feb 8 '06 #4

P: n/a
mwt <mi*********@gmail.com> wrote:
while len(self.stacks) == 0:


To (kind of) repeat myself, the idiomatic Python would be:

while not self.stacks:

An empty list is considered to be false, hence testing the list
itself is the same as testing len(l) > 0 .

As someone else has noticed, you're using len() an awful lot
when you don't need to.

--
\S -- si***@chiark.greenend.org.uk -- http://www.chaos.org.uk/~sion/
___ | "Frankly I have no feelings towards penguins one way or the other"
\X/ | -- Arthur C. Clarke
her nu becomež se bera eadward ofdun hlęddre heafdes bęce bump bump bump
Feb 8 '06 #5

P: n/a
Ok,

I give up. DRY = Don't Repeat Yourself (google Pragmatic Programmers)
but SPOT? Google is little help here, SPOT is too common a word.

Thanks!

Feb 8 '06 #6

P: n/a
pl****@alumni.caltech.edu wrote:
Ok,

I give up. DRY = Don't Repeat Yourself (google Pragmatic Programmers)
but SPOT? Google is little help here, SPOT is too common a word.


The only SPOT I worked with (as I know of) was SPOT4
(Le Systeme Pour 'l Observation de la Terre) but that's
probably not it...

Single Point Of Truth? It seems some claim that DRY and
SPOT are the same things.
http://www.artima.com/cppsource/reducepnp3.html
Feb 8 '06 #7

P: n/a
pl****@alumni.caltech.edu wrote:
Ok,

I give up. DRY = Don't Repeat Yourself (google Pragmatic Programmers)
but SPOT? Google is little help here, SPOT is too common a word.


Single Point Of Truth (or Single Point Of Transformation)

And before you mention it, yes, it's *almost* the same thing as DRY !-)
--
bruno desthuilliers
python -c "print '@'.join(['.'.join([w[::-1] for w in p.split('.')]) for
p in 'o****@xiludom.gro'.split('@')])"
Feb 8 '06 #8

P: n/a
Magnus Lycka wrote:
pl****@alumni.caltech.edu wrote:
Ok,

I give up. DRY = Don't Repeat Yourself (google Pragmatic Programmers)
but SPOT? Google is little help here, SPOT is too common a word.

The only SPOT I worked with (as I know of) was SPOT4
(Le Systeme Pour 'l Observation de la Terre) but that's
probably not it...


Probably not !-)
Single Point Of Truth?
And the winner is... <your-name-here />
It seems some claim that DRY and
SPOT are the same things.
http://www.artima.com/cppsource/reducepnp3.html


Well, they are at least very closely related !-)

And I of course forgot OnceAndOnlyOnce...

--
bruno desthuilliers
python -c "print '@'.join(['.'.join([w[::-1] for w in p.split('.')]) for
p in 'o****@xiludom.gro'.split('@')])"
Feb 8 '06 #9

P: n/a
mwt
A million thanks for in-depth critique. I look forward to figuring out
half of what you're talking about ;)

Feb 9 '06 #10

P: n/a
mwt
BTW - I can't find any code examples of how to interrupt a beast like
this. A regular ctrl-c doesn't work, since the separate threads just
keep executing. Is this a case where you need to iterate through all
threads and stop each one individually? How would that look?

Feb 9 '06 #11

This discussion thread is closed

Replies have been disabled for this discussion.