Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Python Object-Oriented Python Advanced Objects Constructicons

Anthony Grodowski
Anthony Grodowski
4,902 Points

How does Book pass it's output into Bookcase and could someone explain to me whole class Bookcase chunk of code?

How does class Book pass these self.title and self.author to class Bookcase? I mean I see the

for title, author in book_list:
    books.append(Book(title, author))

chunk of code but how class Bookcase knows that these informations come from class Book?

It doesn's seem like we're importing class Book into class Bookcase. I also don't get why do we need books and book_list in class Bookcase.

Another thing that confuses me is that we're using and instance object in a classmethod (books). We've set books variable that it's an instance object and then we're using that in a classmethod. Could someone please explain whole class Bookcase to me?

2 Answers

Chris Freeman
MOD
Chris Freeman
Treehouse Moderator 68,441 Points

Good question. A Bookcase instance is a list of Book instances. This list is accessible by the attribute books. The class Bookcase does not have an author or title attribute.

The class method argument book_list is an iterable consisting of pairs of a title string and a author string. The lines of code

# take each pair of tile and author strings...
for title, author in book_list:
    # create a Book instance using Book(title,author)
    # append new book instance to books attribute
    books.append(Book(title, author))

The key line is

    return cls(books)

where all Book instance in the books list is passed to the Bookcase.__init__(). During __init__, the list of Book instances is assigned to the attribute self.books. Each Book instance remains intact with its own title and author attributes.

The class Book is not imported into class Bookcase. The class Bookcase only has the attribute books that contains a list of instances of Books.

So the class method:

  • takes a list of titles and authors
  • create a list of Book instances
  • calls cls() to create an instance of Bookcase
  • where self.books is assigned this list

Post back if you need more help. Good luck!!!

Anthony Grodowski
Anthony Grodowski
4,902 Points

Thank you so much Chris for as always a profesional answer! I think I still didn't get a couple of things: where do we determine that book_list is made of title, author pairs? Do we just in

for title, author in book_list:
            books.append(Book(title, author))

tell python that book_list is a ?list?(I also don't know what type of data it is, we didn't explictly set it to list) that is based on title, author pairs by saying for title, author in book_list:?

Another thing I'm confused by is, why do I recieve different outputs by typing in the REPL: a)bc b)bc.books c)str(bc.books)? Isn't books attribute the same thing what the whole instnace is? And how do I recieve a "normal" output by typing str(bc.books[0]) into the REPL? How class Bookcase knows how to convert this into a string even tho we didn't provide __str__ in this class? And why by putting str(bc.books) into the REPL I don't get a "normal" output as I'm getting with str(bc.books[0])?

I'd also would like to make sure that I got whole process right, so:

First of all by typing into the REPL for example bc = Bookcase.create_bookcase([("Kwantechizm", "Andrzej Dragan"), ("Nienasycenie", "Stanisław Witkacy")]) we're creating a ?class instance? by:

1) In

def create_bookcase(cls, book_list):
        books = []

we're creating an empty list of books and an iterable book_list.

2) In

for title, author in book_list:
            books.append(Book(title, author))

we're appending books list with class Book instances from book_list.

3) In

return cls(books)

we're creating an actual class instance which is then passed into Bookcase.__init__(), which translates it into a "standard" instance.

Chris Freeman
Chris Freeman
Treehouse Moderator 68,441 Points

where do we determine that book_list is made of title, author pairs? Good question. My assertion that book_list should be a list was not entirely accurate. It could be any iterable as long as it meets expectations of the code.

For the line:

for title, author in book_list:

to work correctly:

  • book_list must be iterable because of the in.
  • each item in book_list must also be iterable of exactly two items because it must be unpacked into title, author

So book_list doesn't have to be list. It could be a tuple of tuples, or a list of tuples or a tuple of lists, etc. If book_list is not an iterable of two-item iterables, then the code above will raise an error because it would be able to continue otherwise. We don't need to tell python what type of object book_list is. Python will just try what it's given and raise an error if it doesn't work.

Side note: the title and author items don't explicitly have to be strings. Since Book.__init__() simply assigns each item to its corresponding attribute, the author and title could be numbers, or any other object.

Another thing I'm confused by is, why do I recieve different outputs by typing in the REPL: a) bc b) bc.books c) str(bc.books) ? Assuming you instantiated bc using something like:

>>> bc = Bookcase.create_bookcase((('t1', 'a1'), ('t2', 'a2')))
# bc points to the Bookcase instance
>>> bc
<__main__.Bookcase object at 0x7f467b2f7da0>

# bc.books points to the attribute books which is a list
>>> bc.books
[<__main__.Book object at 0x7f467b2f7dd8>, <__main__.Book object at 0x7f467b2f7e10>]

# being a list, bc.books returns a "shallow" string of *its* items. It does Not call the __str__ on each item 
>>> str(bc.books)
'[<__main__.Book object at 0x7f467b2f7dd8>, <__main__.Book object at 0x7f467b2f7e10>]'

# running str() on a Book instance in the bc.books list, *will* call the __str__ method
>>> str(bc.books[0])
't1 by a1'

Isn't books attribute the same thing what the whole instance is? They are different. books is the instance attribute which is a list. bc points to the whole bookcase instance.

And how do I receive a "normal" output by typing str(bc.books[0]) into the REPL? This is calling the __str__ method of the Book instance pointed to by bc.books[0].

How class Bookcase knows how to convert this into a string even tho we didn't provide __str__ in this class? The instance of Bookcase is not converting it to a string. Since bc.books[0] is referenced, it is accessing the __str__ method from that instance pointed to by bc.books[0].

And why by putting str(bc.books) into the REPL I don't get a "normal" output as I'm getting with str(bc.books[0])? str(bc.books) references the list pointed to by bc.books, so it is printing out the list and its items. It does not call the __str__ method on each item in the list.

I'd also would like to make sure that I got whole process right, so:

First of all by typing into the REPL for example bc = Bookcase.create_bookcase([("Kwantechizm", "Andrzej Dragan"), ("Nienasycenie", "Stanisław Witkacy")]) we're creating a ?class instance? by:

1) In

def create_bookcase(cls, book_list):
        books = []

we're creating an empty list of books and an iterable book_list.

2) In

for title, author in book_list:
            books.append(Book(title, author))

we're appending books list with class Book instances from book_list.

Good up to here. I would correct part 3.

3) In

return cls(books)

we're creating an actual class instance which is then passed into Bookcase.__init__(), which translates it into a "standard" instance.

The expression cls(books) is the same as Bookcase(books) since cls is assigned Bookcase because of the @classmethod. So we're creating an actual instance of Bookcase with our created list books being passed into instance self.__init__()

Anthony Grodowski
Anthony Grodowski
4,902 Points

Thank you! I think you forgot to answer particulary WHERE does book_list is being filled with title, author pairs so we can then iterate trough it.

What do you mean by "So we're creating an actual instance of Bookcase with our created list books being passed into instance self.__init__()"? So in return cls(books) we're creating a class instance and then Bookcase.__init__() takes books attribute from that class instance and makes it an instance? So overall we have Bookcase(books) class instance and a "standard" instance with list books?

Chris Freeman
Chris Freeman
Treehouse Moderator 68,441 Points

The object book_list is being created/filled with title, author pairs by some other code that calls Bookcase.create_bookcase or is just typed by the user.

What might be confusing is the books in cls(books), Bookcase(books), the argument in Bookcase.__init__(self, books=books), and the value set to the attribute self.books are all the same object.

cls(books) is exactly equivalent to Bookcase(books) in that they both create an instance of Bookcase. The argument books is passed to the __init__ method in both cases which assigns it to the self.books attribute.

There is a step between calling a class to create an instance and the running of __init__ that isn’t talked about much. The step is the running of the __new__ method. It is __new__ that passes the arguments to __init__.

The flow for class instance creation starts with

  • bc = Bookcase(book_list)
  • this calls Bookcase.__new__(book_list) to create the instance!
  • __new__ calls __init__ to initialize the instance
  • __init__ sets the attribute values of the instance. self.books set to book_list
  • Flow returns to __new__ which returns the created and initialized instance
  • bc now points to new instance of Bookcase

The flow for using the class method is essentially the same:

  • bc = Bookcase.create_bookcase(title_auth_list)
  • create_bookcase creates list of Book instances from title_auth_list, let’s call it “book_list”
  • create_bookcase calls cls(book_list) which is the same as Bookcase(book_list)

The process then continues as before:

  • this calls Bookcase.__new__(book_list) to create the instance!
  • __new__ calls __init__ to initialize the instance
  • __init__ sets the attribute values of the instance. self.books set to book_list
  • Flow returns to __new__ which returns the created and initialized instance
  • bc now points to new instance of Bookcase

Chris i just want to tel you: thank you so much, chief!