Object Oriented Programming in Python

!!! UPDATE !!!

Sorry for the formatting issue on most screen captures. Not sure what happened. I went back and pasted the same text as before. It seems to be fine at this time. Not sure what is causing this to happen. Perhaps it is time to look for a better plugin :o(

Unless something changes in the next few days, MN should enter Phase One in the process of “Opening up America Again” next week. Hopefully all will go as planned.

I am not sure if there is something on my Windows computers, but one of the first things I do every day is to power each computer up and log on into Gmail. Most of the times I just click on my user name and my password and I am in Gmail. Now and then, I get prompted to enter a code which is sent as a message to my phone. I have a checkbox selected which should tell Google not to use 2FA on this computer, but it ignores my wishes and requests me to enter the code. Not sure why this is happening. It cannot be so difficult to Google to write such code. If you have comments on this subject please let me know.

Yesterday I was checking the amount of deaths that have so far occurred in different countries due to COVID-19. I was checking among others Peru. It seems that there have been a total of 782 deaths in a country with a population of about 32 million. In the USA there has been reported about 56,096 deaths and our population is about 331 million. It seems that in Peru the percentage of dead people is 0.002 while in the USA is 0.017 which seems strange to me. Over the weekend my wife received a message that in Lima (the capital of Peru which hosts about 30% of the population of the entire country) at least 50 markets were going to be closed last Saturday due to the fact that they needed to be sanitized because they were compromised by the COVID-19 virus. In other words, the food supply for about 10 million people is compromised and so far only 782 people have died due to the pandemic. Not sure if countries are reporting properly or if they are altering the data in orders to not cause panic among their people. Hopefully the number of deaths has been properly reported and for some set of specific reasons the pandemic is not affecting people as much as other countries.

My wife and I do not watch too much TV. Last year we canceled our monthly subscription to AT&T TV Now. The reason is that we had not watched TV for several months. Due to the COVID-19 pandemic, we decided to reinstate our subscription. About three months went by and we watched TV about 5 times. Today I cancelled our subscription once again. Hopefully there will be no reason to renew it in the foreseeable future.

OK, let’s get to the subject of this post. I am taking a course on Python and wanted to refresh some material. Did a Google search and found on YouTube the video “OBJECT-ORIENTED PROGRAMMING IN PYTHON – CS50 on Twitch, EP. 33” hosted by Brian Yu and Colton Ogden. It just happens that the course I am taking on edX has Brian as instructor. More on the course on future posts.

I tried experimenting on the different topics and code covered during the video. For this reason things do not seem to follow a path, but the idea is to experiment with classes and methods in Python.

C:\Users\johnc>python
Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> 50
50
>>> 20 + 8
28
>>> type(28)
<class 'int'>
>>> x = 28
>>> x
28
>>> type(x)
<class 'int'>
>>> x.numerator
28
>>> x.denominator
1
>>> 'hello'.capitalize()
'Hello'
>>> s = 'hello'
>>> s
'hello'
>>> type(s)
<class 'str'>
>>> s.capitalize()
'Hello'
>>>

If you wish to tag along, I suggest you download and install a version 3.x of Python. I have installed on my Windows 10 machine Python 3.7.7, but any 3.7 version will do.

We enter some numbers and are able to perform some operations on them. What is of interest is that a number in Python is represented by a class. In other programming languages e.g., C, an integer is a binary value. In Java an int is a binary value but an Integer represents a class whose object as an integer representation. An int in Python is like an Integer in Java.

Strings are different than integers and they have a different set of methods.

# **** Book class ****
class Book:

# **** constructor method ****
    def __init__(self, title):
        self.title = title

The keyword self in Python refers to the current object.

Properties in a class that begin with ‘_’ are considered as private. This is just a convention.

In this code snippet we are declaring the constructor for a book in the class Book. Our book at this time only has a title.

In order to use the Book class that we have implemented in the book.py file we need to import the class.

We experiment with an object and give it different titles for the book. As expected only the last name is returned by the book.

We then specify an author (which was added in a later version of the code).

# **** Book class ****
class Book:

# **** constructor method ****
    def __init__(self, title, author):
        self.title = title
        self.author = author

As we can see, we do not need to define the fields in the class and associate access. We just need to include them in the constructor.

C:\Users\johnc\workspace0\ClassesInPython>python
Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.

# **** this comments were enter for clarity; you so not need to enter them ****
>>> from book import Book

# **** ****
>>> Book
<class 'book.Book'>
>>> b = Book('Harry Potter')
>>> b = Book("Harry Potter and the Sorcerer's Stone")
>>> b
<book.Book object at 0x000001BA28E9D448>
>>> type(b)
<class 'book.Book'>
>>> b.title
"Harry Potter and the Sorcerer's Stone"

# **** ****
>>> b.author = "J.K. Rowling"
>>> b
<book.Book object at 0x000001BA28E9D448>
>>> b.author
'J.K. Rowling'

After importing the Book class we attempt to instantiate a book. The operation fails because as we saw in the last code snippet, the constructor now takes two arguments, the name of the book and the author. When we specify both arguments, we are able to instantiate a new book.

# **** Book class ****
class Book:

# **** constructor method ****
    def __init__(self, title, author=None):
        self.title = title
        self.author = author

The constructor has been updated in order not to require the author.

C:\Users\johnc\workspace0\ClassesInPython>python
Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.

# **** ****
>>> from book import Book

# **** ****
>>> b = Book("Harry Potter")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __init__() missing 1 required positional argument: 'author'

# **** ****
>>> b = Book("Harry Potter", "J.K. Rowling")

In this example we import the Book class and we instantiate a book by specifying the name of the book and the author. We then specify a second book which we name with the same name as the previous one, but in this case we did not specify the author. All seems to be working as expected.

# **** Book class ****
class Book:

    # **** constructor method ****
    def __init__(self, title, author=None):
        self.title = title
        self.author = author

    # **** print info about the book ****
    def print_info(self):
        print(self.title + " is written by " + self.author)

We noticed in a previous console screen capture that the information about the book is not nicely displayed. To address that situation we could define a new method which will display the title of the boon and its author. The print_info method accomplishes such task.

C:\Users\johnc\workspace0\ClassesInPython>python
Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.

# **** ****
>>> from book import Book

# **** ****
>>> b = Book("Harry Potter", "J.K. Rowling")
>>> b.title
'Harry Potter'
>>> b.author
'J.K. Rowling'

# **** ****
>>> b.print_info
<bound method Book.print_info of <book.Book object at 0x000001673A73C5C8>>
>>> b.print_info()
Harry Potter is written by J.K. Rowling

# **** ****
>>> b2 = Book("The Last Lecture", "Randy Pausch")
>>> b2.print_info()
The Last Lecture is written by Randy Pausch

After we imported the updated Book class, we create a new book by name and author. We then attempt to call the method we just added to the class. We run into a problem. We need to specify the () because we are invoking a method in the class.

We then create a second book and properly invoke the new method. All seems to work well.

# **** Book class ****
class Book:

    # **** constructor method ****
    def __init__(self, title, author=None):
        self.title = title
        self.author = author

    # **** print info about the book ****
    def print_info(self):
        print(self.title + " is written by " + self.author)
        print(f"{self.title} is written by {self.author}")

This code snippet illustrates that there are at least two ways we can display the information using the print function. The second approach is becoming more popular because it is easier to follow.

C:\Users\johnc\workspace0\ClassesInPython>python
Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.

# **** ****
>>> from book import Book

# **** ****
>>> b = Book("James Bond", "Ian Fleming")
>>> b.print_info()
James Bond is written by Ian Fleming
James Bond is written by Ian Fleming

In this screen capture we import the Book class. We instantiate a book and call the print_info method to display the name of the book and the author. We display it twice to show that the two statements are equivalent.

A nice site that was mentioned during the video is Project Euler.

Project Euler is a series of challenging mathematical/computer programming problems that will require more than just mathematical insights to solve.  Although mathematics will help you arrive at elegant and efficient methods, the use of a computer and programming skills will be required to solve most problems.

The motivation for starting Project Euler, and its continuation, is to provide a platform for the inquiring mind to delve into unfamiliar areas and learn new concepts in a fun and recreational context.

In the near future, I will be visiting the site and experiment with different problems and challenges.

C:\Users\johnc\workspace0\ClassesInPython>python
Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> def f(**kwargs):
...     print(kwargs)
...
>>> f(a=3, b=4)
{'a': 3, 'b': 4}

>>> x = {"a": 3, "b": 4}
>>> x
{'a': 3, 'b': 4}
>>> f(**x)
{'a': 3, 'b': 4}

As we previously saw, most methods require a set of argument names and values. This example illustrates how a dictionary may be used as an argument to a method.

It was mentioned that object programming is just one approach to develop software. In practice there are many approaches such as:

object oriented programming
imperative programming
functional programming
lazy programming
lazy evaluation
declarative programming
logic base programming

The approach depends on the problem at hand. As a software developer it is good to have as many tools as possible in your tool chest to be able to apply the proper one to the proper task.

# **** ****
class Library:

    # **** constructor method ****
    def __init__ (self):
        self.books = []

    # **** add a book ****
    def add_book(self, book):
        self.books.append(book)

This code snippet describes the Library class. So far it has two methods. The first one is the constructor which declares a list which will hold the books in the library. The second is used to add a book to the library.

# **** ****
C:\Users\johnc\workspace0\ClassesInPython>dir
04/27/2020  07:47 AM    <DIR>          .
04/27/2020  07:47 AM    <DIR>          ..
04/27/2020  07:56 AM               371 book.py
04/27/2020  07:54 AM               214 library.py
04/26/2020  09:21 AM    <DIR>          __pycache__

# **** ****
C:\Users\johnc\workspace0\ClassesInPython>python
Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.

# **** ****
>>> from book import Book
>>> from library import Library

# **** ****
>>> b1 = Book("Harry Potter", "J.K. Rowling")
>>> b2 = Book("My Sister's Keeper", "Jodi Picoult")

# **** ****
>>> l = Library()

# **** display books in the library (should be empty) ****
>>> l.books
[]

# **** ****
>>> l.add_book(b1)

# **** display books in the library (worked; but somewhat cryptic) ****
>>> l.books
[<book.Book object at 0x000002161C168D88>]

In this screen capture we import the Book and Library classes and instantiate two books. We then create a library in which we will keep track of our books. The contents of the library are displayed. As expected there are no books in yet.

We add a book to the library and display our library. Something (the memory address used to represent the library object) is displayed but is of little help.

# **** Book class ****
class Book:

    # **** constructor method ****
    def __init__(self, title, author=None):
        self.title = title
        self.author = author

    # **** print info about the book ****
    def print_info(self):
        print(self.title + " is written by " + self.author)
        print(f"{self.title} is written by {self.author}")
    
    # **** representation of a book (equivalent to: toString in Java) ****
    def __repr__(self):
        return self.title

The Book class currently has three methods. The constructor is used to create a book object. The print_info method provides us with information about a book. In this case we have it displayed twice using different formats for the print function. The final method __repr__ is equivalent to the toString method in Java. It returns a representation on the book object. Perhaps we will need to implement something similar to display the library object.

In Python 3 the range function takes advantage of lazy evaluation. This means that we specify a range but it is not until execution that the function starts generating the values in the specified range.

ORM stands for Object Relational Mapper. SQLAlchemy is a Python SQL toolkit and Object Relational Mapper that gives application developers the full power and flexibility of SQL. It provides a full suite of well known enterprise-level persistence patterns, designed for efficient and high-performing database access, adapted into a simple and Pythonic domain language.

In the examples in this post, we will not get into persisting objects in a database.

C:\Users\johnc\workspace0\ClassesInPython>python
Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.

# **** ****
>>> from library import Library
>>> from book import Book

# **** ****
>>> l = Library()
>>> b1 = Book("Harry Potter", "J.K. Rowling")
>>> b2 = Book("My Sister's Keeper", "Jodi Picoult")

# **** check if the library is empty ****
>>> l.books
[]

# **** ****
>>> l.add_book(b1)
>>> l.books
[Harry Potter]

# **** ****
>>> b1.title = "Harry Potter and the Goblet of Fire"
>>> b1
Harry Potter and the Goblet of Fire
>>> l.books
[Harry Potter and the Goblet of Fire]

# **** ****
>>> b1.print_info()
Harry Potter and the Goblet of Fire is written by J.K. Rowling
Harry Potter and the Goblet of Fire is written by J.K. Rowling

# **** ****
>>> l.add_book(b2)
>>> l.books
[Harry Potter and the Goblet of Fire, My Sister's Keeper]

We start by doing some imports. We then create a library and a couple books. For sanity reasons we check if the library is empty. All seems well so far.

We then add a book to the library. The book shows as being in the library. The operation repeats with a second book. The library now shows two books.

C:\Users\johnc\workspace0\ClassesInPython>python
Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.

# **** works ****
>>> text = "This is a sentence. This is another one."
>>> text.split(".")
['This is a sentence', ' This is another one', '']

# **** does not work ****
>>> text = "This is a sentence! This is another one."
>>> text.split(".")
['This is a sentence! This is another one', '']

# **** does not work ****
>>> text = "Mr. Jones walked to the store. This is another one."
>>> text.split(".")
['Mr', ' Jones walked to the store', ' This is another one', '']

In this screen capture we divert from the subject. We are interested in getting the first sentence in the specified text which could be the entire contents of a book. Once we get the first sentence we could perform some additional processing on it.

The first approach is to split the text using the ‘.’ which is a common way to terminate a sentence. It seems to work.

We then try different text. This time the first sentence is terminated with a ‘!’ so when we split the text we are not able to extract the first sentence.

The third and last text also fails when extracting the first sentence.

We could have attempted to use regular expressions and we could have passed most if not all of the issues encountered, but in practice it would not work consistently.

The answer is to use a Natural Language Toolkit. NLTK is a leading platform for building Python programs to work with human language data. It provides easy-to-use interfaces to over 50 corpora and lexical resources such as WordNet, along with a suite of text processing libraries for classification, tokenization, stemming, tagging, parsing, and semantic reasoning, wrappers for industrial-strength NLP (Natural Language Processing) libraries, and an active discussion forum.

Let’s now take a look at Inheritance in Python.

# **** ****
from book import Book

# **** LibraryBook inherits from Book class ****
class LibraryBook(Book):
    pass

In this example we import the Book class. We then create a new class named LibraryBook which inherits from the Book class. The pass statement is used when a statement is required syntactically but you do not want any command or code to execute. The pass statement is a null operation; nothing happens when it executes.

C:\Users\johnc\workspace0\ClassesInPython>python
Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.

# **** ****
>>> from library_book import LibraryBook

# **** ****
>>> b = LibraryBook("Harry Potter", "J.K. Rowling")
>>> b.title
'Harry Potter'
>>> b.author
'J.K. Rowling'
>>> b.print_info()
Harry Potter is written by J.K. Rowling

In this screen capture we import the LibraryBook class. We create a book. The book has a title and an author. We then display information for the library book. Note that our newly created LibraryBook class does not contain such methods. It has inherited them from the Book class.

# **** ****
from book import Book

# **** LibraryBook inherits from Book class ****
class LibraryBook(Book):

    # **** ****
    #pass

    # **** constructor ****
    def __init__(self, title, author, inventory):
        # self.title = title
        # self.author = author
        super().__init__(title, author)

        self.inventory = inventory
        self.borrowers = []

    # **** check out book from the library ****
    def check_out(self, name):

        # **** check inventory (should raise an exception) ****
        if self.inventory &lt; 1:
            print("Sorry, not available.")
            return

        # **** update the necessary fields ****
        self.inventory -= 1
        self.borrowers.append(name)

We are now adding some specific behavior to the LibraryBook class.  To keep the behavior we have inherited in the constructor we make use of the __init__ method in the supper class (Book). We then add a couple new fields. The inventory represents the number of copies currently available and the borrowers list is used to list the people that borrowed the book.

The check_out method is used when a person checks out a LibraryBook. We first make sure that we have a copy available in our inventory. If we do not we need to display a message and return. In practice we could have thrown an exception. This will be covered in a future post.

If we have a copy of the book available, we decrement the inventory count and add the name of the borrower to the list.

Of course in production code we might add additional checks (e.g., the borrower has already checked out a copy of the book) and balances.

C:\Users\johnc\workspace0\ClassesInPython>python
Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.

# **** ****
>>> from library_book import LibraryBook

# **** missing inventory ****
>>> b = LibraryBook("Harry Potter", "J.K. Rowling")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __init__() missing 1 required positional argument: 'inventory'

# **** ****
>>> b = LibraryBook("Harry Potter", "J.K. Rowling", 2)

# **** ****
>>> b.title
'Harry Potter'
>>> b.author
'J.K. Rowling'
>>> b.inventory
2

# **** ****
>>> b.print_info()
Harry Potter is written by J.K. Rowling

# **** check out the book ****
>>> b.check_out("John Canessa")
>>> b.borrowers
['John Canessa']
>>> b.inventory
1

# **** check out the book ****
>>> b.check_out("James Bond")
>>> b.borrowers
['John Canessa', 'James Bond']

# **** attempt to check out the book ****
>>> b.check_out("Jane Smith")
Sorry, not available.

# **** verify we have no copies left ****
>>> b.inventory
0

# **** __repr__ works as before (we have not overriden it) ****
>>> b
Harry Potter

We start by importing the LibraryBook. We then create a book but the operation fails. We missed the inventory. We try again with an inventory of 2 and the operation succeeds.

As a sanity check we display different fields from the book. They all appear to work fine. We also use the print_info method which returns the proper information.

John Canessa checks out a copy of the book. We display information about the book and all appears fine.

James Bond (did not know he was interested in Harry Potter) checks out a copy of the same book. As we can see we have two borrowers so far.

Jane Smith comes along and attempts to check out the same book. As expected the operation fails. Our library only has two copies of the book and both are out. We verify this by getting the inventory of the book.

In this example we also use the __repr__ method which has not be overridden in the LibraryBook class and works as expected.

Some advice when working with any object oriented programming (OOP) language is to always plan / design the class before starting implementation. You might start on a path and latter discover an impasse which will require a rewrite of the code that you might have spent hours or days on it. In my opinion, you should always design (within reason) your software before coding it. By design I do not mean writing code on paper. That is completely useless in practice. As you can see, in this post, when using Python you can check syntax and ideas on the interactive console and then implement it in your actual source code using an IDE. In our case we are using the VSCode IDE.

C:\Users\johnc\workspace0\ClassesInPython>python
Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.

# **** class A ****
>>> class A:
...     def foo(self):
...             print("hello")
...

# **** class B ****
>>> class B:
...     def bar(self):
...             print("goodbye")
...

# **** class c inherits from A and B (multiple inheritance) ****
>>> class C(A,B):
...     pass
...

# **** ****
>>> obj = C()
>>> obj.foo()
hello
>>> obj.bar()
goodbye

This is an example of inheritance. We define class A and within a method named foo which displays the string “hello”. We then define class B which holds a single method named bar which displays the string “goodbye”.

Finally we create class C which inherits from classes A and B. Note that we do not override the inherited methods.

We then call the methods foo and bar which seem to display the expected strings.

C:\Users\johnc\workspace0\ClassesInPython>python
Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.

# **** define class A ****
>>> class A:
...     def foo(self):
...             print("hello")
...

# **** define class B ****
>>> class B:
...     def foo(self):
...             print("goodbye")
...

# **** define class C which inherits from A and B ****
>>> class C(A,B):
...     pass
...

# **** instantiate class C ****
>>> obj = C()

# **** call a method which was defined in both classes ****
>>> obj.foo()
hello

In this new example, we also create a class A with a foo method which displays the string “hello”. We then create class B with a single method which we also name foo and prints the string “goodbye”. The class C is defined to inherit from classes A and B. No changes are made to the existing methods.

We instantiate an object for class C and invoke the method foo. The foo method from class A is used. It is important for the language to be deterministic so when we encounter this inheritance situation, the resulting class behaves in a predefined fashion.

C3 linearization is used by Python to achieve consistency.

In computing, the C3 super class linearization is an “algorithm” used primarily to obtain the order in which methods should be inherited in the presence of multiple inheritances. In other words, the output of C3 super class linearization is a deterministic

Method Resolution Order (MRO).

C3 super class linearization results in three important properties:

o a consistent extended precedence graph,

o preservation of local precedence order, and

o fitting the monotonicity criterion.

Python’s Guido van Rossum summarizes C3 super class linearization thusly:

Basically, the idea behind C3 is that if you write down all of the ordering rules imposed by inheritance relationships in a complex class hierarchy, the algorithm will determine a monotonic ordering of the classes that satisfies all of them. If such an ordering cannot be determined, the algorithm will fail.

Python has mechanisms to perform unit testing just like other languages (i.e., Java).

unittest — Unit testing framework

The unittest unit testing framework was originally inspired by JUnit and has a similar flavor as major unit testing frameworks in other languages. It supports test automation, sharing of setup and shutdown code for tests, aggregation of tests into collections, and independence of the tests from the reporting framework.

C:\Users\johnc\workspace0\ClassesInPython>python
Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.

# **** ****
>>> from library_book import LibraryBook

# **** ****
>>> b = LibraryBook("Harry Potter", "J.K. Rowling", 2)

# **** ****
>>> b.print_info()
Harry Potter is written by J.K. Rowling

In this example we create a library book. Once done we display information of the book making a call to the print_info method. This method was inherited from the class Book.

C:\Users\johnc\workspace0\ClassesInPython>python
Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.

# **** ****
>>> names = ["Colton", "Brian", "David"]
>>> ",".join(names)
'Colton,Brian,David'
>>> ", ".join(names)
'Colton, Brian, David'

In this example we create a list with three names. We then use the join method to generate a string with the contents of the list. In the first pass the names are separated by a ‘,’ and it appears that they need better spacing. In the second example we specify the string ‘, ‘ and obtain better results. We will use the join method in a few to display lists.

C:\Users\johnc\workspace0\ClassesInPython>python
Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.

# **** ***
>>> from library_book import LibraryBook
>>> b = LibraryBook("Harry Potter", "J.K. Rowling", 2)

# **** ****
>>> b.check_out("Colton")
>>> b.print_info()
Title: Harry Potter
Author: J.K. Rowling
Inventory Remaining: 1
Borrowers: Colton

# **** ****
>>> b.check_out("John")
>>> b.print_info()
Title: Harry Potter
Author: J.K. Rowling
Inventory Remaining: 0
Borrowers: Colton, John

In this example we create a LibraryBook with two copies in inventory. Colton checks out a copy. We display the book information. Then John checks out the same book. We display the information about the book. Note how the borrowers have been displayed.

C:\Users\johnc\workspace0\ClassesInPython>python
Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.

>>> x = 53
>>> f"x = {x}"
'x = 53'
>>> "x is {}".format(x)
'x is 53'

This example illustrates different and equivalent formats to display a value. We start by setting x to a value of 53. We proceed to display it in two different ways. Lately the first format is becoming more popular due to its simplicity.

C:\Users\johnc\workspace0\ClassesInPython>python
Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.

# **** ****
>>> from library_book import LibraryBook

# **** ****
>>> b = LibraryBook("Harry Potter", "J.K. Rowling", 2)
>>> b.print_info()
Title: Harry Potter
Author: J.K. Rowling
Inventory Remaining: 2
Borrowers:

# **** ****
>>> b.check_out("John Canessa")
>>> b.print_info()
Title: Harry Potter
Author: J.K. Rowling
Inventory Remaining: 1
Borrowers: John Canessa

# **** ****
>>> b.check_in("John Canessa")
>>> b.print_info()
Title: Harry Potter
Author: J.K. Rowling
Inventory Remaining: 2
Borrowers:

>>> b.check_in("John Canessa")
Invalid borrower.

In this example we import the LibraryBook class. We create a book with two copies and display its information.

John Canessa comes along and checks out a copy of the book. We display the information. The same user checks in the book. The information on the book is displayed. All is well so far and all the copies of the book have been checked in.

We attempt to check in the same book. The operation fails because there are no copies out for the book for the specified user.

# **** Book class ****
class Book:

    # **** constructor method ****
    def __init__(self, title, author=None):
        self.title = title
        self.author = author

    # **** print info about the book ****
    def print_info(self):
        print(self.title + " is written by " + self.author)
        #print(f"{self.title} is written by {self.author}")
    
    # **** representation of a book (equivalent to toString in Java) ****
    def __repr__(self):
        return self.title

The Book class has not been altered.

# **** ****
from book import Book

# **** LibraryBook inherits from Book class ****
class LibraryBook(Book):

    # **** ****
    #pass

    # **** constructor ****
    def __init__(self, title, author, inventory):
        # self.title = title
        # self.author = author
        super().__init__(title, author)

        self.inventory = inventory
        self.borrowers = []

    # **** check out book from the library ****
    def check_out(self, name):

        # **** check inventory (should raise an exception) ****
        if self.inventory < 1:
            print("Sorry, not available.")
            return

        # **** update other fields ****
        self.inventory -= 1
        self.borrowers.append(name)
    
    # **** print information about a book (overrides super class in book) ****
    def print_info(self):
        print(f"Title: {self.title}")
        print(f"Author: {self.author}")
        print(f"Inventory Remaining: {self.inventory}")
        print(f"Borrowers: {', '.join(self.borrowers)}")
    
    # **** check in a book into the library ****
    def check_in(self, name):

        # ***** check the borrower name ****
        if self.borrowers.__contains__(name) == False:
            print("Invalid borrower.")
            return

        # **** update other fields ****
        self.inventory += 1
        self.borrowers.remove(name)

As we have suspected by the output captured in some of the latest screen shots, the LibraryBook class has been updated.

The constructor does not seem to have been edited.

The check_out method allows us to check out a book. We check if there is at least a single copy and if not return an error. If all is well so far, we update the inventory and add the name of the person checking out the book to the borrowers list. We could implement a check to make sure that the same person does not check out multiple copies of a book.

The print_info method displays information about the library book. Note how the contents of the borrowers list are displayed.

We have also added a method to check_in a library book. We do so by checking if the person returning the book is the one who borrowed it. We then increase the number of copies in our inventory and remove the name of the borrower from our list.

# **** import(s) ****
from book import Book

# **** Library class ****
class Library:

    # **** constructor method ****
    def __init__ (self):
        self.books = []

    # **** add a book ****
    def add_book(self, book):
        self.books.append(book)

    # **** check out book ****
    def check_out(self, title, name):

        # **** look for the book in the library O(n) ****
        for book in self.books:
            if book.title == title:
                book.check_out(name)
                return
        
        # **** ****
        print("Book not found.")

    # **** check in book ****
    def check_in(self, title, name):

        # **** check if the book in the library O(n) ****
        for book in self.books:
            if book.title == title:
                print("Book not found.")
                return

        # **** ****
        book.check_in(name)

The Library class could have implemented a mechanism to check out and check in books. Perhaps such operations could have been limited / transferred to the LibraryBook instead. You can test the three classes using a driver program. Given the amount of time it took to generate this post I will leave it as a future enhancement to the code.

The entire code for this project can be found in my GitHub repository.

If you have comments or questions regarding this, or any other post in this blog, or if you would like for me to serve of assistance with any phase in the SDLC (Software Development Life Cycle) of a project associated with a product or service, please do not hesitate and leave me a note below. If you prefer, send me a private message using the following address:  john.canessa@gmail.com. I will reply as soon as possible.

Keep on reading and experimenting. It is the best way to learn, refresh your knowledge and enhance your developer toolset!

One last thing, many thanks to all 746 subscribers to my blog!!!

John

Twitter:  @john_canessa

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.