Why doesn't del do the same thing? - python

Why does the following code change both variables:
>>> a = []
>>> b = a
>>> a.append(9)
>>> a
[9]
>>> b
[9]
>>>
But the del statement does not achieve the same effect?
>>> a = []
>>> b = a
>>> del(a)
>>> a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> b
[]
>>>

When you do:
a = b
What you're doing is assigning the label b to the same object that the label a is refering to.
When you do:
a.append(9)
You're adding 9 to the list object pointed to by both a and b. It's the same object, so they show the same result.
When you do:
del a
You're deleting the reference to the object, not the object itself. If it's the only reference, then the object will be garbage collected. But in your case, there's another reference - b - so the object continues to exist.

Instead of "variables", think in terms of names and objects.
>>> a = []
This makes an empty list object and binds the name a to it.
>>> b = a
This simply says that b is now a new name for the object named by a. We have
>>> a is b
True
del a means that we're forgetting the name a: it is no longer bound to an object.
>>> del a
>>> a
Traceback (most recent call last):
File "<ipython-input-8-60b725f10c9c>", line 1, in <module>
a
NameError: name 'a' is not defined
But that you're no longer calling that list object a, only b, doesn't affect the object itself in any way. Objects don't care, or even know about, what names you've given them. [One semi-exception is that if an object no longer has any references, it may -- or may not, no promises -- be garbage-collected.]

The append method works against the actual object, while del works against the reference i.e. variable name.

(I already answered the question in the other question of yours, so I'm going to use it here as well, with slight modifications:)
del does not delete objects; in fact in Python, it is not even possible to tell the interpreter/VM to remove an object from memory because Python is a garbage collected language (like Java, C#, Ruby, Haskell etc).
Instead, what del does when called on a variable (as opposed to a dictionary key or list item) like this:
del a
is that it only removes the local (or global) variable not what it points to (every variable in Python holds a pointer/reference to its contents not the content itself). In fact, since locals and globals are stored as a dictionary under the hood (see locals() and globals()), del a is equivalent to:
del locals()['a']
(or del globals()['a'] when applied to a global.)
so if you have:
a = []
b = a
you're making a list, storing a reference to it in a and then copying that reference into b without copying/touching the list object itself. Therefore these two calls affect one and the same object:
>>> a.append(1)
>>> b.append(2)
>>> a
[1, 2]
>>> b
[1, 2]
>>> a is b # would be False for 2 identical but different list objects
True
>>> id(a) == id(b)
True
(id returns the memory address of an object)
whereas deleting b is in no way related to touching what b points to:
>>> a = []
>>> b = a
>>> del b # a is still untouched and points to a list
>>> b
NameError: name 'b' is not defined
>>> a
[]

Related

Why do variables containing lists in Python act differently from say variable containing integers in terms of storing/pointing towards values? [duplicate]

List reference append code
a = [1,2,3,4,5]
b = a
b.append(6)
print(a)
print(b)
#ans:
[1,2,3,4,5,6]
[1,2,3,4,5,6]
Integer reference in int
a = 1
b = a
b +=1
print(a)
print(b)
#ans:
1
2
how reference works in python integer vs list ? in list both value are same, why is in integer section a value is not 2 ?
In Python, everything is an object. Everything is a name for an address (pointer) per the docs.
On that page you can scroll down and find the following:
Numeric objects are immutable; once created their value never changes
Under that you'll see the int type defined, so it makes perfect sense your second example works.
On the top of the same page, you'll find the following:
Every object has an identity, a type and a value. An object’s identity never changes once it has been created; you may think of it as the object’s address in memory.
Python behaves just like C and Java in that you cannot reassign where the pointer to a name points. Python, like Java, is also pass-by-value and doesn't have a pass-by-reference semantic.
Looking at your first example:
>>> a = 1
>>> hex(id(a))
'0x7ffdc64cd420'
>>> b = a + 1
>>> hex(id(b))
'0x7ffdc64cd440'
>>> print(a)
1
>>> print(b)
2
Here it is shown that the operation b = a + 1 leaves a at 1 and b is now 2. That's because int is immutable, names that point to the value 1 will always point to the same address:
>>> a = 1
>>> b = 2
>>> c = 1
>>> hex(id(a))
'0x7ffdc64cd420'
>>> hex(id(b))
'0x7ffdc64cd440'
>>> hex(id(c))
'0x7ffdc64cd420'
Now this only holds true for the values of -5 to 256 in the C implementation, so beyond that you get new addresses, but the mutability shown above holds. I've shown you the sharing of memory addresses for a reason. On the same page you'll find the following:
Types affect almost all aspects of object behavior. Even the importance of object identity is affected in some sense: 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, but after c = []; d = [], c and d are guaranteed to refer to two different, unique, newly created empty lists. (Note that c = d = [] assigns the same object to both c and d.)
So your example:
>>> a = [1, 2, 3, 4, 5]
>>> hex(id(a))
'0x17292e1cbc8'
>>> b = a
>>> hex(id(b))
'0x17292e1cbc8'
I should be able to stop right here, its obvious that both a and b refer to the same object in memory at address 0x17292e1cbc8. Thats because the above is like saying:
# Lets assume that `[1, 2, 3, 4, 5]` is 0x17292e1cbc8 in memory
>>> a = 0x17292e1cbc8
>>> b = a
>>> print(b)
'0x17292e1cbc8'
Long and skinny? You're simply assigning a pointer to a new name, but both names point to the same object in memory! Note: This is not the same as a shallow copy because no external compound object is made.

Why can't I change attribute of a class in Python

We say classes are mutable in Python which means you can using references we can change the values that will be reflected in object. For example,
>>> A = [1, 2, 3]
>>> B = A
>>> B[2] = 5
>>> A
[1, 2, 5]
Here I can change the values of A object using B because list is a mutable type. My question is why can't I change the attributes of a class below using same concept:
class C:
apple = 2
def __init__(self):
self.dangerous = 2
D = C # D is pointing to same class C
D().dangerous = 5 # changing the value of class attribute D
D().apple = 3 # changing the value of apple here
print D().apple
print D().dangerous
OUTPUT:
2
2
Could anyone explain why the output is 2 and 2 but not 3 and 5 since we are saying that the class is a mutable type.
UPDATE : Referring to the answer by #zxq9, if you see the below diagram when do D=C, D is actually pointing to the same class rather a new object as you have described. Could you explain this:
Each time you place parens after a class, you are constructing a new instance object of the class. So the things you printed were brand-spanking new and did not reflect the short-lived assignments you had made previously.
Here is an example (expanded to cover the underlying reference to class C):
>>> class C:
... red = 2
... def __init__(self):
... self.blue = 2
...
>>> C.red
2
>>> C.blue
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'C' has no attribute 'blue'
>>> C().red
2
>>> C().blue
2
>>> #OOOOH!
...
>>> z = C()
>>> z.red
2
>>> z.blue
2
>>> D = C
>>> D.red
2
>>> D().red
2
>>> D().red = "over 9000!"
>>> D.red
2
>>> D.red = "No, really over 9000!"
>>> D.red
'No, really over 9000!'
>>> C.red
'No, really over 9000!'
>>> #OOOOOOHHHH!
...
Note that we did change the class directly when I assigned D.red = "No, really over 9000!" -- because that was referencing the class definition itself, not an instantiated object created from it. Note also that assigning an attribute of D (a copy) changed the attribute of C (the original) because in many (but not all) cases Python makes such assignments by reference, meaning that D is really an alias of C, not copy of the underlying structure. Read up on Python's deepcopy() method for more about that particularly startling detail.
Walk through the example code carefully, note the difference between referencing ClassName and calling ClassName(). The first is a reference via a variable name to a class definition -- a blueprint for generating instance objects that carries a constructor function __init__() with it. The second is an invokation of __init__() whose return value is an instance object of the class within which it is defined.
This is also why you can do things like this:
def some_fun(another_fun, value):
another_fun(value)
def foo(v):
return v + v
def bar(v):
return v * v
some_fun(foo, 5)
some_fun(bar, 5)
This feature lends Python a high degree of flexibility in building functional abstractions. (Now if only it had tail-call elimination...)
It is an interesting example.
The line D().dangerous = 5 will change the attribute "dangerous" of the instance D(); But the line print D().dangerous print out the attribute "dangerous" of ANOTHER instance D().
The line D().apple = 3 will create an attribute "apple" in the instance D() since this instance does not have the attribute "apple".
The line print D().apple will print out the attribute "apple" of the class D since the instance D() does not have the attribute "apple".
One way to change the attribute "apple" of the class through its instance is by using D().__class__.apple=3

Different ways of deleting lists

I want to understand why:
a = [];
del a; and
del a[:];
behave so differently.
I ran a test for each to illustrate the differences I witnessed:
>>> # Test 1: Reset with a = []
...
>>> a = [1,2,3]
>>> b = a
>>> a = []
>>> a
[]
>>> b
[1, 2, 3]
>>>
>>> # Test 2: Reset with del a
...
>>> a = [1,2,3]
>>> b = a
>>> del a
>>> a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> b
[1, 2, 3]
>>>
>>> # Test 3: Reset with del a[:]
...
>>> a = [1,2,3]
>>> b = a
>>> del a[:]
>>> a
[]
>>> b
[]
I did find Different ways of clearing lists, but I didn't find an explanation for the differences in behaviour. Can anyone clarify this?
Test 1
>>> a = [1,2,3] # set a to point to a list [1, 2, 3]
>>> b = a # set b to what a is currently pointing at
>>> a = [] # now you set a to point to an empty list
# Step 1: A --> [1 2 3]
# Step 2: A --> [1 2 3] <-- B
# Step 3: A --> [ ] [1 2 3] <-- B
# at this point a points to a new empty list
# whereas b points to the original list of a
Test 2
>>> a = [1,2,3] # set a to point to a list [1, 2, 3]
>>> b = a # set b to what a is currently pointing at
>>> del a # delete the reference from a to the list
# Step 1: A --> [1 2 3]
# Step 2: A --> [1 2 3] <-- B
# Step 3: [1 2 3] <-- B
# so a no longer exists because the reference
# was destroyed but b is not affected because
# b still points to the original list
Test 3
>>> a = [1,2,3] # set a to point to a list [1, 2, 3]
>>> b = a # set b to what a is currently pointing at
>>> del a[:] # delete the contents of the original
# Step 1: A --> [1 2 3]
# Step 2: A --> [1 2 3] <-- B
# Step 2: A --> [ ] <-- B
# both a and b are empty because they were pointing
# to the same list whose elements were just removed
Of your three "ways of deleting Python lists", only one actually alters the original list object; the other two only affect the name.
a = [] creates a new list object, and assigns it to the name a.
del a deletes the name, not the object it refers to.
del a[:] deletes all references from the list referenced by the name a (although, similarly, it doesn't directly affect the objects that were referenced from the list).
It's probably worth reading this article on Python names and values to better understand what's going on here.
Test 1: rebinds a to a new object, b still holds a reference to the original object, a is just a name by rebinding a to a new object does not change the original object that b points to.
Test 2: you del the name a so it no longer exists but again you still have a reference to the object in memory with b.
Test 3 a[:] just like when you copy a list or want to change all the elements of a list refers to references to the objects stored in the list not the name a. b gets cleared also as again it is a reference to a so changes to the content of a will effect b.
The behaviour is documented:
There is a way to remove an item from a list given its index instead
of its value: the del statement. This differs from the pop()
method which returns a value. The del statement can also be used to
remove slices from a list or clear the entire list (which we did
earlier by assignment of an empty list to the slice). For example:
>>>
>>> a = [-1, 1, 66.25, 333, 333, 1234.5]
>>> del a[0]
>>> a
[1, 66.25, 333, 333, 1234.5]
>>> del a[2:4]
>>> a
[1, 66.25, 1234.5]
>>> del a[:]
>>> a
[]
del can also be used to delete entire variables:
>>>
>>> del a
Referencing the name a hereafter is an error (at least until another
value is assigned to it). We'll find other uses for del later.
So only del a actually deletes a, a = [] rebinds a to a new object and del a[:] clears a. In your second test if b did not hold a reference to the object it would be garbage collected.
del a
is removing the variable a from the scope. Quoting from python docs:
Deletion of a name removes the binding of that name from the local or
global namespace, depending on whether the name occurs in a global
statement in the same code block.
del a[:]
is simply removing the contents of a, since the deletion is passed to the a object, instead of applied to it. Again from the docs:
Deletion of attribute references, subscriptions and slicings is passed
to the primary object involved; deletion of a slicing is in general
equivalent to assignment of an empty slice of the right type (but even
this is determined by the sliced object).
.
Of those three methods, only the third method actually results in deleting the list that 'a' points to. Lets do a quick overview.
When you right a = [1, 2, 3] it creates a list in memory, with the items [1, 2, 3] and then gets 'a' to point to it. When you write b = a this preforms whats' called a 'shallow copy,' i.e. it makes 'b' point to the same block of memory as 'a.' a deep copy would involve copying the contents of the list into a new block of memory, then pointing to that.
now, when you write a = [] you are creating a new list with no items in it, and getting 'a' to point to it. the original list still exists, and 'b' is pointing to it.
in the second case, del a deletes the pointer to [1,2,3] and not the array it's self. this means b can still point to it.
lastly, del a[:] goes through the data 'a' is pointing to and empties it's contents. 'a' still exists, so you can use it. 'b' also exists, but it points to the same empty list 'a' does, which is why it gives the same output.
To understand the difference between different ways of deleting lists, let us see each of them one by one with the help of images.
>>> a1 = [1,2,3]
A new list object is created and assigned to a1.
>>> a2 = a1
We assign a1 to a2. So, list a2 now points to the list object to which a1 points to.
DIFFERENT METHODS EXPLAINED BELOW:
Method-1 Using [] :
>>> a1 = []
On assigning an empty list to a1, there is no effect on a2. a2 still refers to the same list object but a1 now refers to an empty list.
Method-2 Using del [:]
>>> del a1[:]
This deletes all the contents of the list object which a1 was pointing to. a1 now points to an empty list. Since a2 was also referring to the same list object, it also becomes an empty list.
Method-3 Using del a1
>>> del a1
>>> a1
NameError: name 'a1' is not defined
This deletes the variable a1 from the scope. Here, just the variable a1 is removed, the original list is still present in the memory. a2 still points to that original list which a1 used to point to. If we now try to access a1, we will get a NameError.

how can i set a variable in a array in python?

i have the problem where i set a variable but it creates a new one instead, i am not quite sure what is going on here. I have tried using global, setting the variables first and tried to use a tupple but just can't get it working. but this is the problem:
>>> variable = 1
>>> variableList = [variable]
>>> variableList[0] = 2
>>> print(variable)
1
as you can see it variable stays 1 although i set it to 2, is there a easy way to fix this?
Doing variableList = [variable] actually created a new reference(variableList[0]) to the object 1. And when you did variableList[0] = 2, it removed one reference from 1 and assigned variableList[0] to 2. So, using an assignment you can never modify other references.
>>> import sys
>>> variable = 1000
>>> sys.getrefcount(variable)
2
>>> variableList = [variable]
>>> sys.getrefcount(variable) # Reference count increased by 1
3
>>> variableList[0] = 2
>>> sys.getrefcount(variable) #Reference count decreased by 1
2
In fact even you've used +=, that too wouldn't have affected variable because you don't modify a immutable object, you simply assign a new object to that variable name.
>>> a = 100
>>> b = a
>>> b += 10 #This too only affects b
>>> a
100
>>> b
110
But, if variable points to a mutable object and you perform some in-place operation on that object from either variable or variableList[0], then you'll see that both of them have changed.
>>> a = []
>>> b = [a]
>>> b[0].append(1) #in-place operation on a mutable object affects all references
>>> a
[1]
>>> b
[[1]]
you don't set variable. you just change variableList content.
variableList[0] it's a variable like variable so the command variableList = [variable] just copy its value.
it is just like this:
>>> a = 1
>>> b = a
>>> b = 2
>>> print(a)
1
You're reassigning the 0th element of the list (not Array), to the Integer 2, you are not overwriting the variable variable.
>>> variable = 1
>>> variableList = [variable]
>>> variableList[0] = 2
>>> print(variable)
>>> 1
>>> print(variableList)
[2]
It suggest you also look up mutability, as it's important to note that integers are immutable:
The value of some objects can change. Objects whose value can change are said to be mutable; objects whose value is unchangeable once they are created are called immutable.
When you did :variableList = [variable]
you created the first element of veribleList and made its value to balue of variable but it dont makes this first element a varible you just coppied its value so when you change variableList[0] it has nothing to do with variable

The immutable object in python

I see a article about the immutable object.
It says when:
variable = immutable
As assign the immutable to a variable.
for example
a = b # b is a immutable
It says in this case a refers to a copy of b, not reference to b.
If b is mutable, the a wiil be a reference to b
so:
a = 10
b = a
a =20
print (b) #b still is 10
but in this case:
a = 10
b = 10
a is b # return True
print id(10)
print id(a)
print id(b) # id(a) == id(b) == id(10)
if a is the copy of 10, and b is also the copy of 10, why id(a) == id(b) == id(10)?
"Simple" immutable literals (and in particular, integers between -1 and 255) are interned, which means that even when bound to different names, they will still be the same object.
>>> a = 'foo'
>>> b = 'foo'
>>> a is b
True
While that article may be correct for some languages, it's wrong for Python.
When you do any normal assignment in Python:
some_name = some_name_or_object
You aren't making a copy of anything. You're just pointing the name at the object on the right side of the assignment.
Mutability is irrelevant.
More specifically, the reason:
a = 10
b = 10
a is b
is True, is that 10 is interned -- meaning Python keeps one 10 in memory, and anything that is set to 10 points to that same 10.
If you do
a = object()
b = object()
a is b
You'll get False, but
a = object()
b = a
a is b
will still be True.
Because interning has already been explained, I'll only address the mutable/immutable stuff:
As assign the immutable to a variable.
When talking about what is actually happening, I wouldn't choose this wording.
We have objects (stuff that lives in memory) and means to access those objects: names (or variables), these are "bound" to an object in reference. (You could say the point to the objects)
The names/variables are independent of each other, they can happen to be bound to the same object, or to different ones. Relocating one such variable doesn't affect any others.
There is no such thing as passing by value or passing by reference. In Python, you always pass/assign "by object". When assigning or passing a variable to a function, Python never creates a copy, it always passes/assigns the very same object you already have.
Now, when you try to modify an immutable object, what happens? As already said, the object is immutable, so what happens instead is the following: Python creates a modified copy.
As for your example:
a = 10
b = a
a =20
print (b) #b still is 10
This is not related to mutability. On the first line, you bind the int object with the value 10 to the name a. On the second line, you bind the object referred to by a to the name b.
On the third line, you bind the int object with the value 20 to the name a, that does not change what the name b is bound to!
It says in this case a refers to a copy of b, not reference to b. If b
is mutable, the a wiil be a reference to b
As already mentioned before, there is no such thing as references in Python. Names in Python are bound to objects. Different names (or variables) can be bound to the very same object, but there is no connection between the different names themselves. When you modify things, you modify objects, that's why all other names that are bound to that object "see the changes", well they're bound to the same object that you've modified, right?
If you bind a name to a different object, that's just what happens. There's no magic done to the other names, they stay just the way they are.
As for the example with lists:
In [1]: smalllist = [0, 1, 2]
In [2]: biglist = [smalllist]
In [3]: biglist
Out[3]: [[0, 1, 2]]
Instead of In[1] and In[2], I might have written:
In [1]: biglist = [[0, 1, 2]]
In [2]: smalllist = biglist[0]
This is equivalent.
The important thing to see here, is that biglist is a list with one item. This one item is, of course, an object. The fact that it is a list does not conjure up some magic, it's just a simple object that happens to be a list, that we have attached to the name smalllist.
So, accessing biglist[i] is exactly the same as accessing smalllist, because they are the same object. We never made a copy, we passed the object.
In [14]: smalllist is biglist[0]
Out[14]: True
Because lists are mutable, we can change smallist, and see the change reflected in biglist. Why? Because we actually modified the object referred to by smallist. We still have the same object (apart from the fact that it's changed). But biglist will "see" that change because as its first item, it references that very same object.
In [4]: smalllist[0] = 3
In [5]: biglist
Out[5]: [[3, 1, 2]]
The same is true when we "double" the list:
In [11]: biglist *= 2
In [12]: biglist
Out[12]: [[0, 1, 2], [0, 1, 2]]
What happens is this: We have a list: [object1, object2, object3] (this is a general example)
What we get is: [object1, object2, object3, object1, object2, object3]: It will just insert (i.e. modify "biglist") all of the items at the end of the list. Again, we insert objects, we do not magically create copies.
So when we now change an item inside the first item of biglist:
In [20]: biglist[0][0]=3
In [21]: biglist
Out[21]: [[3, 1, 2], [3, 1, 2]]
We could also just have changed smalllist, because for all intents and purposes, biglist could be represented as: [smalllist, smalllist] -- it contains the very same object twice.

Categories