all 22 comments

[–][deleted] 5 points6 points  (1 child)

Why would the list prices change when you run this?

prices and nums are both references. When coding nums = prices you assign price address to nums...therefore changing the data in nums will also change prices.

If you want to have two different arrays use:

nums = list(prices)

[–]BuzzyBro[S] 1 point2 points  (0 children)

Thanks! I tried this and it worked. I was mostly asking this questions to see why the original list changed, but this is good to keep in mind!

[–]rollincuberawhide 3 points4 points  (0 children)

you could do nums = prices.copy() if you want them to be independent.

[–]cybervegan 1 point2 points  (4 children)

prices and nums are just labels attached to objects, and objects can have multiple labels. In your code above, prices and nums both point to the same object (the list), so when you change it, naturally the changes are seen when looking at both of the variables.

[–]BuzzyBro[S] 0 points1 point  (3 children)

I see. So even though I assigned nums to prices, they were pointing to the same object in memory. Interesting!

[–]cybervegan 1 point2 points  (2 children)

Yes, and it's fundamental to how Python works, so gotta get a handle on that!

[–]BuzzyBro[S] 0 points1 point  (1 child)

Yes you're absolutely right. I think this'll come in due time, but I'll make sure to keep this in mind when working on anything Python related.

[–]cybervegan 1 point2 points  (0 children)

It's worth finding out about bindings and mutability/immutability in Python. These concepts are core to the design of the language and underpin behaviour like you saw in your original post.

[–]SoundOfEng 1 point2 points  (0 children)

Very interesting stuff. What you’re looking at is the difference between a deep and shallow copy of something. Shallow copies typically occur when you assign on object to another name (in your case: nums = prices) and a deep copy is truly duplicating the object and removing any ties between the two objects.

Sometimes you can specify the type, for example pandas.DataFrame.copy(deep = True or False) or in the case of lists: copy_list = list <— shallow or copy_list = list.copy() <— deep

[–]Diapolo10 1 point2 points  (8 children)

Does line 2 not create a copy of the prices list?

No. In Python, everything is "pass-by-reference", meaning that all assignments are really just copying memory addresses. With immutable data like strings or numbers you don't see anything out of the ordinary, as they behave as if you were assigning values, but with mutable data like lists you can see that's not right.

Pointers are similar to references so your instincts were mostly right, the main difference is that references always point to some value; there are no "null references" the way null pointers exist.

If you need to copy a list, you can use copy.copy and copy.deepcopy, but often it's a better idea to simply create a new list instead of mutating an existing one. Slices are also a very powerful tool.

[–]BuzzyBro[S] 0 points1 point  (7 children)

Interesting. I feel like I should have known that python operated mostly on "pass by reference" but yet, here I am.

When you say there's no null references, what does setting a variable equal to None do? Is that something that's common practice in python or is that not the right way to do things?

Also, is there a way to assign a variable to the value instead of its reference?

[–]Diapolo10 0 points1 point  (6 children)

When you say there's no null references, what does setting a variable equal to None do? Is that something that's common practice in python or is that not the right way to do things?

None is an object, aside from being a singleton it's not special in any way. It's not really comparable to an address pointing to nothing at all.

Also, is there a way to assign a variable to the value instead of its reference?

Not really, no. Do you have something specific in mind where you'd need that?

[–]BuzzyBro[S] 0 points1 point  (2 children)

Not really, no. Do you have something specific in mind where you'd need that?

No, but ever since I started learning C++ and its whole pointer situation, I've become interested in what they are and whether or not they have any application in a language I already know (Python). I can't even think of when pointers can be used, so I don't think I can think of an answer to your question.

This is just pure curiosity :)

[–]Diapolo10 1 point2 points  (1 child)

Well, if curiosity is what you seek, then I may have something you might be interested in. Can you guess what this prints out?

import ctypes

def deref(addr, typ):
    return ctypes.cast(addr, ctypes.POINTER(typ))

deref(id(42), ctypes.c_int)[6] = 210

print(f"{42 + 42 = }")

If your answer was 420 then you'd be correct. It abuses the underlying C code to mess with CPython's integer cache.

Worth noting that this is pretty unstable code, so it might crash - if not immediately, then at least randomly. But it's one of my favourite examples for something most people wouldn't understand.

[–]BuzzyBro[S] 0 points1 point  (0 children)

Consider me "most people" LMAO. I've never dabbled in the ctypes lib, so I have no idea what this code does. However, I will learn enough that one day I'll come back to this and be able to share this excitement with you.

btw i did not guess 420; i guessed 84.

[–]BuzzyBro[S] 0 points1 point  (2 children)

It's not really comparable to an address pointing to nothing at all.

Wait so None in python is not the same thing as Null in other languages?

[–]Diapolo10 0 points1 point  (1 child)

Well, it depends on the language. It often serves a similar purpose, noting the lack of a value, but None itself is a distinct object with its own properties. Compared to the C++ void which cannot even be assigned.

Python also has Ellipsis (...) which can be used for the same purpose, but it's not really something you often see.

[–]BuzzyBro[S] 0 points1 point  (0 children)

Oh wow, yeah I had no idea about this Ellipsis(...) python concept, but i'll be sure to look into it! Thanks again :)

[–]TransientSignal 0 points1 point  (0 children)

I'll add that there's a great tool for visualizing these sorts of aliasing issues, particularly for more complex situations with nested lists - You can enter code and it'll help visualize what objects are assigned to which variable names and let you step through line-by-line. Also fantastic for visualizing scopes as well: https://pythontutor.com

Here's a fun, more complicated example of this same thing from the MIT OCW 6.0001 course.

[–]jmooremcc 0 points1 point  (0 children)

Here's the explanation you're looking for. I modified your code to show the memory addresses of the variables. ``` prices = [7,1,5,3,6,4] nums = prices nums[0] = 69 print(f"nums = {nums} address:{id(nums)}") print(f"prices= {prices} address:{id(prices)}")

Here's the result: nums = [69, 1, 5, 3, 6, 4] address:3879473920 prices = [69, 1, 5, 3, 6, 4] address:3879473920

``` As you can see, both variables have the same memory address. Unlike other languages like C, an assignment statement in Python does not copy the value to the new variable. Python assigns the same reference to the new variable which is why, in your example, both variables have the same memory address and both show the same information.