I am aware that in python, integers from -5 to 256 can have the same ID. However, what are the consequences in the case where two immutable objects have the same ID? How about the consequences when two mutable objects have the same ID?
Thank you.
An id is definitionally unique at a given point in time (only one object can have a given id at once). If you see the same id on two names at the same time, it means it's two names referring to the same object. There are no "consequences" to this for immutable types like int, because it's impossible to modify the object through either alias (x = y = 5 aliases both x and y to the same 5 object, but x += 1 is roughly equivalent to x = x + 1 for immutable objects, rebinding x to a new object, 6, not modifying the 5 object in place); that's why optimizations like the small int cache you've observed are safe.
When two mutable objects have the same ID, they reference the same memory address.
a = 10
b = 10
print(id(a), id(b))
Output:
4355009952 4355009952
The only consequence of two mutable objects having the same ID is changing the value in one object will be reflected on the other.
a = 10
b = a
print(a, b)
print(id(a), id(b))
a = 6
print(a, b)
print(id(a), id(b))
Output:
10 10
4338298272 4338298272
6 10
4338298144 4338298272
Immutable objects having the same is not a consequence as they are immutable
Related
Python works so that I can update a list in place every time a function runs:
list_obj = list()
def increase_list_obj(list_obj, n):
list_obj.append(n)
print(list_obj)
for n in range(3):
increase_list_obj(list_obj, n)
print(list_obj)
OUTPUT:
[]
[0]
[0, 1]
[0, 1, 2]
Based on how the list persists I would expect that I can also update an int in place every time a function runs:
int_obj = 0
def increase_int_obj(int_obj):
int_obj += 1
print(int_obj)
for n in range(3):
increase_int_obj(int_obj)
print(int_obj)
OUTPUT:
0
0
0
0
EXPECTED:
0
1
2
3
Why does the int update not work the same way as the list update?
How are the persistence and scoping rules different for these two objects?
(I am NOT trying to suggest that the two should behave the same, I am curious about why they don't)
To preempt answers about how to update an int: I realize you can update the int value by just returning it from the function:
int_obj = 0
def increase_int_obj_v2(int_obj):
int_obj += 1
return int_obj
print(int_obj)
for n in range(3):
int_obj = increase_int_obj_v2(int_obj)
print(int_obj)
OUTPUT:
0
1
2
3
Thank you!
To better understand, you need to know some concepts
int is immutable and list is mutable
List of Mutable and Immutable objects
Objects of built-in type that are mutable are:
Lists
Sets
Dictionaries
User-Defined Classes
Objects of built-in type that are immutable are:
Numbers (Integer, Rational, Float, Decimal, Complex & Booleans)
Strings
Tuples
Frozen Sets
User-Defined Classes (It purely depends upon the user to define the characteristics)
what does it mean?
for more info about immutable and mutable read this
some objects passing to function by reference and some passing by value
(more)
Objects in Python can be modified from any scope. The thing about integers is they are immutable objects so whenever they are modified they make a new copy of themselves which will only exist within the scope it is created.
Lists on the other hand are like most other Python objects and will be modified in place allowing for changes to be made regardless of the current scope.
Here is an example using a custom object.
class HoldInt:
def __init__(self, integer):
self.integer = integer
int_obj = HoldInt(0)
def modify_int():
int_obj.integer += 1
print(int_obj.integer)
modify_int()
print(int_obj.integer)
0
1
Variables are local if there is an assignment within a function, otherwise they are global:
i += 1
is an assignment (i = i + 1)! Therefore i is a local variable within the function.
list_obj.append(n)
is a mutation! Therefore list_obj is looked up in the enclosing namespace which is the global one.
Side note: += can be a mutation for mutable types: list_obj += [n] would be a mutation as well.
I'm new to the language and I am a bit confused about references in Python.
Consider this code:
class A:
def __init__(self, x):
self.x = x
a = A(3)
v=[a]
print(f'obj before: v[0].x={v[0].x}')
a.x = a.x + 1
print(f'obj after: v[0].x={v[0].x}')
b = 3
w=[b]
print(f'int before: w[0]={w[0]}')
b = b + 1
print(f'int after: w[0]={w[0]}')
=====================
output:
obj before: v[0].x=3
obj after: v[0].x=4
int before: w[0]=3
int after: w[0]=3
Why do the obj and int versions of the code work differently?
a = A(3)
The variable a points to an object.
v=[a]
The first element of v points to the same object.
a.x = a.x + 1
Change the attribute "x" of the object.
v still contains the same object but its attribute has changed.
b = 3
The variable b points to the object 3.
w=[b]
The first element of w also points to the object 3.
b = b + 1
b now points to what you get when you perform addition on the object 3 and the object 1, which is the object 4.
w still contains the object 3. You never changed any attributes of this object and you never changed where the first element of w points to.
When you do this, you are modifying the object a:
a.x = a.x + 1
When you are doing this, you are changing what variable b refers to:
b = b + 1
In other words, there is a big difference between b and x in the above code: b is a variable and x is an attribute of a.
Assigning something to a variable does not modify any objects, and therefore affects only the variable to which the assignment was made*, whereas setting the value of an attribute modifies the object, which can be seen in any variable which references that object.
* There are also changes in refcounts affecting garbage collector, but is not relevant now.
This question already has answers here:
"is" operator behaves unexpectedly with integers
(11 answers)
Closed 7 years ago.
In python, I have declared two variables with same value. Strangely, they are pointing to same object. I need to understand how this objects and their corresponding values are assigned.
#!/usr/bin/python
a = 100
b = 100
print id(a)
print id(b)
--------------------
Output :
157375428
157375428
-------------------
I assume, a and b are two different variables with same value. Then why the same object is pointing to both of them ?
By calling id(a) you actually get same result as when calling id(100), a and b share the same instance of 100. I know this is quite confusing, almost every other programming language behaves differently. Maybe you shouldn't think a and b as variables but instead "named references" to objects.
Technically a and b are two different variables.
In Python a variable is a just a name. Values are somewhere else and a variable refers to a value.
From the Python documentation
For immutable types, operations that compute new values may actually
return a reference to any existing object with the same type and
value, while for mutable objects this is not allowed. E.g., after a = 1; b = 1, a and b may or may not refer to the same object with the
value one, depending on the implementation.
Python pre-allocates a number of integers (see http://blog.lerner.co.il/why-you-should-almost-never-use-is-in-python/). For instance, on my computer I have:
>>> x = 100
>>> y = 100
>>> x is y
True
But:
>>> x = 10**1000
>>> y = 10**1000
>>> x is y
False
In fact, we can see that only the first 256 positive integers are pre-allocated:
>>> x = 0
>>> y = 0
>>> while True:
... if not x is y:
... print x
... break
... x += 1
... y += 1
...
257
This behavior has me puzzled:
import code
class foo():
def __init__(self):
self.x = 1
def interact(self):
v = globals()
v.update(vars(self))
code.interact(local=v)
c = foo()
c.interact()
Python 2.6.6 (r266:84292, Sep 11 2012, 08:34:23)
(InteractiveConsole)
>>> id(x)
29082424
>>> id(c.x)
29082424
>>> x
1
>>> c.x
1
>>> x=2
>>> c.x
1
Why doesn't 'c.x' behave like an alias for 'x'? If I understand the id() function correctly, they are both at the same memory address.
Small integers from from -5 to 256 are cached in python, i.e their id() is always going to be same.
From the docs:
The current implementation keeps an array of integer objects for all
integers between -5 and 256, when you create an int in that range you
actually just get back a reference to the existing object.
>>> x = 1
>>> y = 1 #same id() here as integer 1 is cached by python.
>>> x is y
True
Update:
If two identifiers return same value of id() then it doesn't mean they can act as alias of
each other, it totally depends on the type of the object they are pointing to.
For immutable object you cannot create alias in python. Modifying one of the reference to an immutable object will simple make it point to a new object, while other references to that older object will still remain intact.
>>> x = y = 300
>>> x is y # x and y point to the same object
True
>>> x += 1 # modify x
>>> x # x now points to a different object
301
>>> y #y still points to the old object
300
A mutable object can be modified from any of it's references, but those modifications must be in-place modifications.
>>> x = y = []
>>> x is y
True
>>> x.append(1) # list.extend is an in-place operation
>>> y.append(2) # in-place operation
>>> x
[1, 2]
>>> y #works fine so far
[1, 2]
>>> x = x + [1] #not an in-place operation
>>> x
[1, 2, 1] #assigns a new object to x
>>> y #y still points to the same old object
[1, 2]
code.interact simply did (effectively) x=c.x for you. So when you checked their ids, they were pointing to the exact same object. But x=2 creates a new binding for the variable x. It is not an alias. Python does not have aliases, as far as I am aware.
Yes, in CPython id(x) is the memory address of the object x points to. It is not the memory address of the variable x itself (which is, after all, just a key in a dictionary).
If I understand the id() function correctly, they are both at the same memory address.
You don't understand it correctly. id returns an integer in respect of which the following identity is guaranteed: if id(x) == id(y) then x is y is guaranteed (and vice versa).
Accordingly, id tells you about the objects (values) that variables point to, not about the variables themselves.
Any relationship to memory addresses is purely an implementation detail. Python, unlike, e.g. C, does not assume any particular relationship to the underlying machine (whether physical or virtual). Variables in python are both opaque, and not language accessible (i.e. not first class).
I am breaking my old question to parts because it is very messy beast here. This question is related to this answer and this answer. I try to understand pointers, not sure even whether they exist in Python.
# Won't change x with y=4
>>> x = 0; y = x; y = 4; x
0
# Won't change y
>>> x = 0; y = x; x = 2; y
0
#so how can I change pointers? Do they even exist in Python?
x = 0
y.pointerDestination = x.pointerDestination #How? By which command?
x = 2
# y should be 0, how?
[Update 2: Solved]
Perhaps, contradictive statements about the existence There are no pointers in Python. and Python does not have the concept of a "pointer" to a simple scalar value.. Does the last one infer that there are pointers to something else, nullifying the first statement?
Scalar objects in Python are immutable. If you use a non-scalar object, such as a list, you can do this:
>>> x = [0]
>>> y = x
>>> y[0] = 4
>>> y
[4]
>>> x
[4]
>>> x is y
True
Python does not have the concept of a "pointer" to a simple scalar value.
Don't confuse pointers to references. They are not the same thing. A pointer is simply an address to an object. You don't really have access to the address of an object in python, only references to them.
When you assign an object to a variable, you are assigning a reference to some object to the variable.
x = 0
# x is a reference to an object `0`
y = [0]
# y is a reference to an object `[0]`
Some objects in python are mutable, meaning you can change properties of the object. Others are immutable, meaning you cannot change the properties of the object.
int (a scalar object) is immutable. There isn't a property of an int that you could change (aka mutating).
# suppose ints had a `value` property which stores the value
x.value = 20 # does not work
list (a non-scalar object) on the other hand is mutable. You can change individual elements of the list to refer to something else.
y[0] = 20 # changes the 0th element of the list to `20`
In the examples you've shown:
>>> x = [0]
>>> y = [x]
you're not dealing with pointers, you're dealing with references to lists with certain values. x is a list that contains a single integer 0. y is a list that contains a reference to whatever x refers to (in this case, the list [0]).
You can change the contents of x like so:
>>> print(x)
[0]
>>> x[0] = 2
>>> print(x)
[2]
You can change the contents of the list referenced by x through y's reference:
>>> print(x)
[2]
>>> print(y)
[[2]]
>>> y[0][0] = 5
>>> print(x)
[5]
>>> print(y)
[[5]]
You can change the contents of y to reference something else:
>>> print(y)
[[5]]
>>> y[0] = 12345
>>> print(x)
[5]
>>> print(y)
[12345]
It's basically the same semantics of a language such as Java or C#. You don't use pointers to objects directly (though you do indirectly since the implementations use pointers behind the scenes), but references to objects.
There are no pointers in Python. There are things called references (which, like C++ references, happen to be commonly implemented in pointers - but unlike C++ references don't imply pass-by-reference). Every variable stores a reference to an object allocated somewhere else (on the heap). Every collection stores references to objects allocated somewhere else. Every member of an object stores a reference to an object allocated somewhere else.
The simple expression x evaluates to the reference stored in x - whoever uses it has no way to determine that is came from a variable. There's no way to get a link to a variable (as opposed to the contents of it) that could be used to track changes of that variable. Item (x[y] = ...) and member (x.y = ...) assignments are different in one regard: They invoke methods and mutate existing objects instead of overwriting a local variable. This difference is mainly important when dealing with scoping, but you can use either of those to emulate mutability for immutable types (as shown by #Greg Hewgill) and to share state changes across function boundaries (def f(x): x = 0 doesn't change anything, but def g(x): x.x = 0 does). It's not fully up to emulating pass by reference though - unless you replace every variable by a wrapper object whose sole purpose is to hold a mutable val property. This would be the equivalent to emulating pass-by-reference through pointers in C, but much more cumbersome.