In addition to the below, I recommend reading Maarten Fabré's answer.
Edge case
Consider the following scenario:
book_tracking = StudentDatabase()
book_tracking.return_book("Student A", "Moby Dick")
# KeyError: 'Student A'
However, we don't need to do a try-except block to catch the student who never handled books before trying to return a book; Python provides defaultdict in the collections module to help us with this very problem:
from collections import defaultdict
class StudentDatabase:
def __init__(self):
self._books = defaultdict(list)
# ...
Now, if anything tries to access an unassigned key of _books
, it will simply return an empty list. This can subsequently simplify borrow_book()
:
def borrow_book(self, name, book):
self._books[name].append(book)
Interclass operability
Library
and StudentDatabase
don't seem to interact at all. If they're related, the student database listing book borrows should inform the library when a book is borrowed or returned. The most versatile way to do this at this stage is to add a library
parameter to relevant methods.
class StudentDatabase:
# ...
def borrow_book(self, name, book, library):
if library.lend_book(book):
self._books[name].append(book)
def return_book(self, name, book, library):
if book not in self._books[name]:
print("You don't seem to have borrowed \"%s\"" % book)
else:
library.add_book(book)
self._books[name].remove(book)
This will require returning False when the book is not found:
class Library:
# ...
def lend_book(self, requested_book):
if requested_book in self._books:
print("You have now borrowed \"%s\" " % requested_book)
self._books.remove(requested_book)
return True
else:
print("Sorry, \"%s\" is not there in our library at the moment" % requested_book)
return False
The implementation you currently have is very simple, so there's not much else to discuss in terms of design: it becomes more relevant when you have more moving parts. I do find it somewhat odd that it's only tracking by student, and not by book (i.e. Library
could know how many copies of each book it had (int), to whom it's checked (a list perhaps with a custom CheckedOut
class to store the person and the due date), and any other information pertaining to that book. A BookStatus
class could store everything related to each book; the library would then contain a list of BookStatus objects.
Checking if a sequence is empty.
(Note I previously advocated using len(self._books) > 0
until Maarten Fabré (along with three people upvoting his comment) pointed out this suggestion contradicted PEP-8) The relevant passage is excerpted below.
For sequences, (strings, lists, tuples), use the fact that empty sequences are false.
Format string literals
You seem to be using printf-style string formatting. In Python 3, format strings are more flexible and would probably be considered more Pythonic. Python 3.6 adds format string literals, which are very compact. Here are some example uses:
- Original:
"You have now borrowed \"%s\" " % requested_book
- With f-string:
f"You have now borrowed \"{requested_book}\"."
- Original:
"Sorry, \"%s\" is not there in our library at the moment" % requested_book
- With f-string:
f"Sorry, \"{requested_book}\" is not there in our library at the moment"
- Original:
"You don't seem to have borrowed \"%s\"" % book
- With f-string:
f"You don't seem to have borrowed \"{book}\""
- Original:
"%s: %s" % (name, books)
- With f-string:
f"{name}: {books}"
Result
from collections import defaultdict
class Library:
def __init__(self):
self._books = []
def add_book(self, new_book):
self._books.append(new_book)
def display_books(self):
if len(self._books) > 0:
print("The books we have made available in our library are:\n")
for book in self._books:
print(book)
else:
print("Sorry, we have no books available in the library at the moment")
def lend_book(self, requested_book):
if requested_book in self._books:
print(f"You have now borrowed \"{requested_book}\".")
self._books.remove(requested_book)
return True
else:
print(f"Sorry, \"{requested_book}\" is not there in our library at the moment")
return False
class StudentDatabase:
def __init__(self):
self._books = defaultdict(list)
def get_student(self, name):
if name not in self._books:
return "Not Found"
return self._books[name]
def borrow_book(self, name, book, library):
if library.lend_book(book):
self._books[name].append(book)
def return_book(self, name, book, library):
if book not in self._books[name]:
print(f"You don't seem to have borrowed \"{book}\"")
else:
library.add_book(book)
self._books[name].remove(book)
def display_students_with_books(self):
for name, books in self._books.items():
if books:
print(f"{name}: {books}")