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 Functional Python Functional Workhorses Map

Brendan Whiting
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
Brendan Whiting
Front End Web Development Techdegree Graduate 84,738 Points

Why does this function succeed in not modifying the original?

In this video we have this example:

def sales_price(book):
    """Apply a 20% discount to the book's price"""
    book = copy(book)
    book.price = round(book.price-book.price*.2, 2)
    return book

sales_books = list(map(sales_price, BOOKS))
print(BOOKS[0].price) # 13.55
print(sales_books[0].price) # 10.84

I'm having trouble understanding why this succeeds in not modifying the original. A book gets passed into the sales_price function. On the first line inside the function we say: book = copy(book). I understand the 'copy' makes a duplicate of the book object, but then it would seem that we're putting that copy into the place where the original was. This seems like a 'pass by reference' vs. 'pass by value' thing. But if we're passing by value then why was the 'copy' part necessary? I'm confused.

Thanks.

1 Answer

Chris Freeman
MOD
Chris Freeman
Treehouse Moderator 68,457 Points

Mocking up your code with data and print statements. Running the code you'll see that after the copy the local variable book points to the copy instead of the original book reference passed in.

from copy import copy
class Book():
    def __init__(self, price):
        self.price = price

book1 = Book(13.55)
BOOKS = [ book1 ]

def sales_price(book):
    """Apply a 20% discount to the book's price"""
    print("local variable 'book' points to book1")
    print("SP1: book id: ", id(book))
    print("Use 'copy' to create a new object, return reference to new object")
    print("assign local variable 'book' to new object reference")
    book = copy(book)
    print("SP2: book id: ", id(book))

    book.price = round(book.price-book.price*.2, 2)
    return book

print("At start, BOOKS contains one item")
print("Id of BOOKS[0]: ", id(BOOKS[0]))
print("Id of book1: ", id(book1))
print("running map....")
sales_books = list(map(sales_price, BOOKS))
print(BOOKS[0].price) # 13.55
print(sales_books[0].price) # 10.84

produces the following output:

>>> At start, BOOKS contains one item
Id of BOOKS[0]:  139688759237768
Id of book1:  139688759237768
running map....
local variable 'book' points to book1
SP1: book id:  139688759237768
Use 'copy' to create a new object, return reference to new object
assign local variable 'book' to new object reference
SP2: book id:  139688759705896
13.55
10.84
Brendan Whiting
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
Brendan Whiting
Front End Web Development Techdegree Graduate 84,738 Points

I guess what I don't understand is on this line of code:

book = copy(book)

book is book1, both times it's referenced. So how does book1 not also end up being the new copy?

Chris Freeman
Chris Freeman
Treehouse Moderator 68,457 Points

It's good that you are working to understand this. It is key to how Python operates and will save your hours and hours of debug when some value or side-effect of an assignment goes haywire.

book = copy(book)

The incorrect assumption is: "book is book1, both times it's referenced."

Stepping back for a moment. As mentioned all objects are passed by references. This begs the question "Where are these references pointing to?". The answer is a "Heap" of objects that Python manages during execution. Objects no longer reference get marked for garbage collection so that objects allocated memory is freed up for reuse. Anything created exist solely in the Heap. Statements and functions operate only on references to these Heap objects.

Outside of the function, when book1 is instantiated, a Book instance is added to the heap, let's call it Blob1 and the pointer value to Blob1 is assigned to book1.

Inside the function, the local book is simply a pointer to an object. As the function begins, book is passed the reference that book1 is pointing at (a pointer to Blob1) . So now they both independently point to the same Blob1 object on the Heap.

When the copy command runs, it first creates a new copy of the object pointed to by book (Blob1 object on the heap) and returns a new pointer to the newly created object. Let's call the new Heap object Blob2. At this point we are still on the right-side of the statement in question. Both Blob1 and it's identical Blob2 exist on the Heap, with book1 and book still pointing to Blob1.

On to the left-side of the statement, book is assigned the value returned by the copy function (the pointer to Blob2). Now 'book1' is still pointing to Blob1, but 'book' is now pointing at Blob2. Subsequently, any changes to book within the function will only affect Blob2.

When you truly get this concept, you'll know it by the mind-blowing realization! You will see all Python code differently as you read through code.

Brendan Whiting
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
Brendan Whiting
Front End Web Development Techdegree Graduate 84,738 Points

Thank you for taking the time to explain that. I think I get it now. The thing that gets passed in as an argument is a pointer to an object. Inside the function, I can tell my parameter variable to now point to something else, but it won't go and change the other pointers outside the function - like the pointers that made up the BOOKS list, those stay pointing to the original.

Chris Freeman
Chris Freeman
Treehouse Moderator 68,457 Points

Correct! Someday down the road this will save you much time. Play with id() to see the value of the pointers to Heap objects. The value is basically the decimal value of the hexadecimal address pointer to where the object is in memory.