I started reading about python's += syntax and stumbled onto the following post/answer: Interactive code about +=
So what I noticed was that there seems to be a difference between frames and objects.
In the global frame, they point to the same object even though they're different variables; if the line
l2 += [item]
was instead
l2 = l2 + [item]
then 'l2' becomes a separate object when that line runs. My biggest question is when would you want a variable to point to a separate object? Also, why and when would you want to keep them pointed at the same object?
Any explanation or use cases would be greatly appreciated! Extra thanks if you can mention anything relevant to data science :)
Like all software objects, a frame object is actually a section of main memory that holds information and methods. With the help of the operating system and the graphics board, Java paints a picture on the computer monitor that represents the frame.
Stack frame object represents a program scope in Python. This object contains information about current execution state like code object, local and global variables and a lot of other data. Frames are stored in a stack-like structure.
A stack frame represents a single function call. You can visualize functions that call one another as virtual frames stacking on top of one another. The stack data structure is actually used for this! When one function call returns its data to its caller, then its stack frame is removed from the stack.
frame
and object
don't mean what you think they mean.
In programming you have something called a stack. In Python, when you call a function you create something called a stack frame. This frame is (as you see in your example) basically just a table of all of the variables that are local to your function.
Note that defining a function doesn't create a new stack frame, it's the calling a function. For instance something like this:
def say_hello():
name = input('What is your name?')
print('Hello, {}'.format(name))
Your global frame is just going to hold one reference: say_hello
. You can see that by checking out what's in the local namespace (in Python you pretty much have a 1:1 relationship between namespace, scope, and stack frames):
print(locals())
You'll see something that looks like this:
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x1019bb320>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/private/tmp/fun.py', '__cached__': None, 'say_hello': <function say_hello at 0x101962d90>}
Note the dunder (short for double underscore double underscore) names - those are automagically provided, and for the purposes of our discussion you can ignore them. That leaves us with:
{'say_hello': <function say_hello at 0x101962d90>}
That 0x
bit is the memory address where the function itself lives. So here, our global stack/frame contains just that one value. If you call your function and then check locals()
again, you'll see that name
isn't there. That's because when you call the function you create a new stack frame and the variable is assigned there. You can prove this by adding print(locals())
at the end of your function. Then you'll see something like this:
{'name': 'Arthur, King of the Brits'}
No dunder names here. You'll also note that this doesn't show a memory address. If you want to know where this value lives, there's a function for that.
def say_hello():
name = input('What is your name?')
print('hello {}'.format(name))
print(locals())
print(id(name))
return name
print(id(say_hello()))
That's what the example means when it's talking about a frame.
But what about objects? Well, in Python, everything is an object. Just try it:
>>> isinstance(3, object)
True
>>> isinstance(None, object)
True
>>> isinstance('hello', object)
True
>>> isinstance(13.2, object)
True
>>> isinstance(3j, object)
True
>>> def fun():
... print('hello')
...
>>> isinstance(fun, object)
True
>>> class Cool: pass
...
>>> isinstance(Cool, object)
True
>>> isinstance(Cool(), object)
True
>>> isinstance(object, object)
True
>>> isinstance(isinstance, object)
True
>>> isinstance(True, object)
True
They're all objects. But they may be different objects. And how can you tell? With id
:
>>> id(3)
4297619904
>>> id(None)
4297303920
>>> id('hello')
4325843048
>>> id('hello')
4325843048
>>> id(13.2)
4322300216
>>> id(3j)
4325518960
>>> id(13.2)
4322300216
>>> id(fun)
4322635152
>>> id(isinstance)
4298988640
>>> id(True)
4297228640
>>> id(False)
4297228608
>>> id(None)
4297303920
>>> id(Cool)
4302561896
Note that you also can compare whether or not two objects are the same object by using is
.
>>> True is False
False
>>> True is True
True
>>> 'hello world' is 'hello world'
True
>>> 'hello world' is ('hello ' + 'world')
False
>>> 512 is (500+12)
False
>>> 23 is (20+3)
True
Ehhhhh...? Wait a minute, what happened there? Well, as it turns out, python
(that is, CPython) caches small integers. So the object 512
is different from the object that is the result of the object 500
added to the object 12
.
One important thing to note is that the assignment operator =
always assigns a new name to the same object. For example:
>>> x = 592
>>> y = 592
>>> x is y
False
>>> x == y
True
>>> x = y
>>> x is y
True
>>> x == y
True
And it doesn't matter how many other names you give an object, or even if you pass the object around to different frames, you still have the same object.
But as you're starting to gather, it's important to understand the difference between operations that change an object and operations that produce a new object. Generally speaking you have a few immutable types in Python, and operations on them will produce a new object.
As for your question, when do you want to change objects and when do you want to keep them the same is actually looking at it the wrong way. You want to use a mutable type when you want to change things, and you want to use an immutable type if you don't want things to change.
For instance, say you've got a group, and you want to add members to the group. You might use a mutable type like a list to keep track of the group, and an immutable type like strings to represent the members. Like this:
>>> group = []
>>> id(group)
4325836488
>>> group.append('Sir Lancelot')
>>> group.append('Sir Gallahad')
>>> group.append('Sir Robin')
>>> group.append("Robin's Minstrels")
>>> group.append('King Arthur')
>>> group
['Sir Lancelot', 'Sir Gallahad', 'Sir Robin', "Robin's Minstrels", 'King Arthur']
What happens when a member of the group is eaten?
>>> del group[-2] # And there was much rejoicing
>>> id(group)
4325836488
>>> group
['Sir Lancelot', 'Sir Gallahad', 'Sir Robin', 'King Arthur']
You'll notice that you still have the same group, just the members have changed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With