Constructors of subclasses and instantiation

Here’s the entire file:
https://repl.it/@bennypr0fane/Tome-Rater

Now there’s a file for populating the database that you can find in my repl in the same directory, populate.py:

from TomeRater import *

Tome_Rater = TomeRater()

#Create some books:
book1 = Tome_Rater.create_book("Society of Mind", 12345678)
novel1 = Tome_Rater.create_fiction("Alice In Wonderland", "Lewis Carroll", 12345)
novel1.set_isbn(9781536831139)
nonfiction1 = Tome_Rater.create_non_fiction("Automate the Boring Stuff", "Python", "beginner", 1929452)
nonfiction2 = Tome_Rater.create_non_fiction("Computing Machinery and Intelligence", "AI", "advanced", 11111938)
novel2 = Tome_Rater.create_fiction("The Diamond Age", "Neal Stephenson", 10101010)
novel3 = Tome_Rater.create_fiction("There Will Come Soft Rains", "Ray Bradbury", 10001000)

#Create users:
Tome_Rater.add_user("Alan Turing", "alan@turing.com")
Tome_Rater.add_user("David Marr", "david@computation.org")

#Add a user with three books already read:
Tome_Rater.add_user("Marvin Minsky", "marvin@mit.edu", user_books=[book1, novel1, nonfiction1])

#Add books to a user one by one, with ratings:
Tome_Rater.add_book_to_user(book1, "alan@turing.com", 1)
Tome_Rater.add_book_to_user(novel1, "alan@turing.com", 3)
Tome_Rater.add_book_to_user(nonfiction1, "alan@turing.com", 3)
Tome_Rater.add_book_to_user(nonfiction2, "alan@turing.com", 4)
Tome_Rater.add_book_to_user(novel3, "alan@turing.com", 1)

Tome_Rater.add_book_to_user(novel2, "marvin@mit.edu", 2)
Tome_Rater.add_book_to_user(novel3, "marvin@mit.edu", 2)
Tome_Rater.add_book_to_user(novel3, "david@computation.org", 4)


#Uncomment these to test your functions:
Tome_Rater.print_catalog()
Tome_Rater.print_users()

print(Tome_Rater.most_positive_user())
print(Tome_Rater.highest_rated_book())
print("Most read book:")
print(Tome_Rater.most_read_book())

When I run this, I get the following TRaceback:

Traceback (most recent call last):
  File "populate.py", line 7, in <module>
    novel1 = Tome_Rater.create_fiction("Alice In Wonderland", "Lewis Carroll", 12345)
  File "/home/ben/Nextcloud/codn/programming-with-Python.Cdcdm/Projekte/TomeRater/TomeRater.py", line 142, in create_fiction
    return Fiction(title, isbn, author)
  File "/home/ben/Nextcloud/codn/programming-with-Python.Cdcdm/Projekte/TomeRater/TomeRater.py", line 97, in __init__
    super().__init__(title, isbn)
TypeError: super() takes at least 1 argument (0 given)

I’m pretty taken aback, because this has already worked in an earlier version (not the first one, which did not work) and I don’t remember making any changes to these functions since then! But now, it doesn’t and it seems the time has come for me to understand constructors of subclasses…

In the first version (which did not work), I had:

class Book:
    def __init__(self, title, isbn):
        self.title = title
        self.isbn = isbn
        self.ratings = []

class Fiction(Book):
    def __init__(self, author):
        self.author = author

alice_i_w = Fiction("Lewis Carroll")
print(alice_i_w)

will likely display,

<__main__.Fiction object at 0x7fbc68d61400>

which is not what we want. There are two dunder methods we can turn to…

def __repr__(self):
    return "Title: {}\nISBN: {}\nRatings: {}".format(self.title, self.isbn, self.ratings)

print (alice_i_w)

Another issue arises because the title and isbn are not initialized.

class Book:
    def __init__(self, title, isbn):
        self.title = title
        self.isbn = isbn
        self.ratings = []

class Fiction(Book):
    def __init__(self, author, title, isbn):
        super(Fiction, self).__init__(title, isbn)
        self.author = author
    def __repr__(self):
        return "Title: {}\nAuthor: {}\nISBN: {}\nRatings: {}".format(
          self.title, 
          self.author,
          self.isbn, 
          self.ratings
        )

alice_i_w = Fiction("Lewis Carroll", "Alice in Wonderland", 12345678901)
print(alice_i_w)
Title: Alice in Wonderland
Author: Lewis Carroll
ISBN: 12345678901
Ratings: []

Edit: The other dunder method, btw, is __str__(). Check the docs for details on what makes them different from one another, or how both may be implemented (assuming they can be).


Aside

In true technical fashion there are no constructors in Python; they are initializers. A constructor returns an object that is newly created where __init__() only sets the attributes since the object exists already.

class Object:
    pass
object = Object()

With no methods or initialization, object is still an instance object.

2 Likes

I already had a __repr__ method for Book and Fiction. I hope that link on top to my whole file worked? Anyhow also thanks for pointing me at the __str__ dunder.

class Fiction(Book):
    def __init__(self, author, title, isbn):
        super(Fiction, self).__init__(title, isbn) 
        self.author = author
  1. The only difference I see to my code is here you added Fiction, self into super(). If this is the proper way to instantiate a subclass, then it’s properly weird and defies what I’ve come to know as a very comprehensible, consistent logic of Python syntax.
    super(Fiction, self), if I try to put it in plain language, means
    “invoke the superclass of Fiction and of myself (me being also Fiction), i.e. invoke the super class of Fiction and Fiction”.

  2. Then, you’re saying:

Which I thought I was doing with __init__(title, isbn)…?

  1. I’m still in the dark about how my current initializer has worked as it is before. I called the populate.py file on TomeRaterat in an earlier version and everything was initialized perfectly, but now it wants more arguments…

I was made aware in my course materials, in the introduction of the concept, that when developers mention a constructor, in Python what they mean isn’t really a constructor, but then in the further descriptions, they stuck with the term constructor, so I stuck with it…

1 Like

My knowledge is not exhaustive by any means. It may not be abreast of the current 3.7 version. Truth is I didn’t follow your link, only went by the code snippet in your post. I’ll have a closer look, to be fair. Might be I’ll learn something new today.

So long as you are aware of the difference in the diction, it shouldn’t matter.

I got the same error message. The subclass needs to pass values up to the parent class so they can be initialized, else the init method can’t work. If this is implicit in 3.7 then I need to catch up.

I wanted to be more specific about this: I meant that when learning Python, I haven’t come across any redundant ways of expressing things. Any information is introduced once, and later referred to, but not both. This would be the first exception in my experience with Python so far - this one makes a knot of redundancies in my brain. However, my course hasn’t really covered how to instantiate a subclass, so I’m on new territory here.

super(Fiction, self).__init__(title, isbn)

as suspected can be written as you have it in your code source,

super().__init__(title, isbn)
1 Like

I’m still getting that error, and none the wiser about it :frowning_face:
This is my python version:

Python 3.7.3 (default, Mar 27 2019, 22:11:17)
Type "copyright", "credits" or "license" for more information.

IPython 7.4.0 -- An enhanced Interactive Python.

It works (copied & pasted without edit from your first post, above), or at least runs without error, in repl.it running 3.6.1 and in PyCharm on my desktop, 3.7.1. There is one small inconsistency (which does not throw an error) that I noticed at the line cited in your error message:

line 95

class Fiction(Book):
    def __init__(self, title, author, isbn):
        super().__init__(title, isbn)
        self.author = author

but the calling statement (line 142) has the arguments in a different order.

return Fiction(title, isbn, author)

1 Like

Going over your repl line by line.

Line 64 should be removed. A setter doesn’t need to return anything since it acts directly upon the owner instance.

Line 77 should come before line 74 since it checks for zero length.

    def get_average_rating(self):
        ratings_total = 0
        for rating in self.ratings:
            ratings_total += rating
        if len(self.ratings) > 0:    # line 77
            average = ratings_total / len(self.ratings)
        else:
            average = None
        return average

Suggested revision

    def get_average_rating(self):
        if len(self.ratings) < 1: return None
        ratings_total = 0
        for rating in self.ratings:
            ratings_total += rating or 0    # count None as 0
        average = ratings_total / len(self.ratings)
        return average

What code are you running that raises the error?

1 Like

I noticed that when testing the create_fiction and create_nonfiction methods, fixed it now. Thank you!

1 Like

Thanks for the pointer, fixed that.
I wrote:

 if len(self.ratings) < 1:
    return

Is that different from how wrote it?

Well… The error came up when I ran the testing file populate.py on it - but now, the error is gone! While I was waiting for a reply from the moderator about this constructor error, I looked at some other problems, managed to fix them (without changing the constructor), and it suddenly compiles all nicely!
So, sorry that I’m unable now to reproduce what fixed it - I suspect though there was an error in the create_fiction function call (not the constructor), maybe it also had something to do with the wrong sequence of arguments that patrickd314 pointed out.
At the moment, for some reason, running the populate.py file on Repl.it does nothing… But its console output run on my computer looks like this:

New book added.
New fiction book added
ISBN has been updated

New non-fiction book added
New non-fiction book added
New fiction book added
New fiction book added
New user added
New user added
New user added
Added 'Society of Mind' to user marvin@mit.edu.
Added 'Alice In Wonderland' to user marvin@mit.edu.
Added 'Automate the Boring Stuff' to user marvin@mit.edu.
Added 'Society of Mind' to user alan@turing.com.
Added 'Alice In Wonderland' to user alan@turing.com.
Added 'Automate the Boring Stuff' to user alan@turing.com.
Added 'Computing Machinery and Intelligence' to user alan@turing.com.
Added 'There Will Come Soft Rains' to user alan@turing.com.
Added 'The Diamond Age' to user marvin@mit.edu.
Added 'There Will Come Soft Rains' to user marvin@mit.edu.
Added 'There Will Come Soft Rains' to user david@computation.org.
**Catalog:**
Society of Mind
rating: 1.0
Alice In Wonderland by Lewis Carroll
rating: 3.0
Automate the Boring Stuff, a beginner book on Python
rating: 3.0
Computing Machinery and Intelligence, a advanced book on AI
rating: 4.0
There Will Come Soft Rains by Ray Bradbury
rating: 4.0
The Diamond Age by Neal Stephenson
rating: 2.0
**End of catalog**

**List of users:**
user: Alan Turing
e-mail: alan@turing.com
books read: {'Society of Mind': 1, 'Alice In Wonderland': 3, 'Automate the Boring Stuff': 3, 'Computing Machinery and Intelligence': 4, 'There Will Come Soft Rains': 1}
user: David Marr
e-mail: david@computation.org
books read: {'There Will Come Soft Rains': 4}
user: Marvin Minsky
e-mail: marvin@mit.edu
books read: {'Society of Mind': 0, 'Alice In Wonderland': 0, 'Automate the Boring Stuff': 0, 'The Diamond Age': 2, 'There Will Come Soft Rains': 2}
**End of user list**

Most positive user:
Average rating: 2.4
user: Alan Turing
e-mail: alan@turing.com
books read: {'Society of Mind': 1, 'Alice In Wonderland': 3, 'Automate the Boring Stuff': 3, 'Computing Machinery and Intelligence': 4, 'There Will Come Soft Rains': 1}
Highest rated book:
Computing Machinery and Intelligence, a advanced book on AI
rating: 4.0
Most read book:
[There Will Come Soft Rains by Ray Bradbury
rating: 4.0]
**Good Reads book rating database**
Number of users: 3
Number of books: 6

I think it might be about ready to hand in!

2 Likes

Still, you might want to study this…

def keys_by_value(d, v):
  '''
  d => dictionary type
  v => single value
  a => key associated with matching value
  return => list of keys
  '''
  return [a for a, b in d.items() if b == v]
1 Like

So it turns out that the TypeError went away when I switched to Spyder IDE 3.3.3 - before I’d been doing the coding in Geany IDE v 1.29. When I run the same code in Geany, it still throws the error, and does nothing (emtpy command prompt) on Repl.it, it works fine only in Spyder.
Any ideas what this is all about?

Certainly looks more elegant than my helper function :slightly_smiling_face:

1 Like