21

These variable assignments work as I expect:

>>> a = 3
>>> b = a
>>> print(a, b)
(3, 3)
>>> b=4
>>> print(a, b)
(3, 4)

However, these assignments behave differently:

>>> class number():
...     def __init__(self, name, number):
...         self.name = name
...         self.number = number
... 
>>> c = number("one", 1)
>>> d = c
>>> print(c.number, d.number)
(1, 1)
>>> d.number = 2
>>> print(c.number, d.number)
(2, 2)

Why is c is same as d, unlike in (a, b) example? How can I do something like in (a, b) in (c, d) classes example? That is, copy the object and then change one part of it (that won't affect the object that I borrowed properties from)?

200_success
  • 7,286
  • 1
  • 43
  • 74
Gunnm
  • 974
  • 3
  • 10
  • 21
  • 3
    It's perfectly like `variable = variable`, when your variables are mutable types. Try it with a list. – Charles Duffy Apr 28 '15 at 17:51
  • 7
    Related: [Immutable vs mutable types - Python](http://stackoverflow.com/questions/8056130/immutable-vs-mutable-types-python) – Ashwini Chaudhary Apr 28 '15 at 17:51
  • 1
    Print the classes, and you'll see something like: `<__main__.number object at 0x7f455b87c7b8> <__main__.number object at 0x7f455b87c7b8> ` ... Note the same address :-) So you're setting a reference, and not copying the object. – Martin Tournoij Apr 28 '15 at 17:53
  • 4
    You aren't confused about "variable = object" vs "variable = number" - you're confused about "variable = thing" vs "variable.field = thing". – user253751 Apr 29 '15 at 01:26
  • 2
    This reminds me of an answer I posted to a different question which should shed some light on this: http://stackoverflow.com/questions/3059395/numpy-array-assignment-problem/3059553#3059553 – David Z Apr 29 '15 at 07:22

10 Answers10

16

These lines:

c = number("one", 1)
d = c

...are effectively:

  • Create a new instance of number and assign it to c
  • Assign the existing reference called c to a new variable d

You haven't changed or modified anything about c; d is another name that points to the same instance.

Without cloning the instance or creating a new instance, you can't do anything similar to how the primitive int is behaving.


To correct a bit of information, the explanation above is rather simplified and a bit incomplete in its nature, although it mostly describes what's going on at 10,000 feet.

For a closer look, we have to realize a few things about Python's variables, or "names", and how they interact with this program.

As mentioned above, you have the notion of "names" and "bindings", which are pretty straightforward to reason at:

a = 3
b = a

In this context, a is a name, and b is a binding to a. We haven't modified or changed anything about a.

As noted before, there are two types of data in Python: mutable and immutable. A name that points to immutable data, such as primitives and tuples, can be reassigned without any ill effect to any other bindings present on it, because no state is changing with respect to the binding.

This is why this reassignment does what we would expect it to:

print(a, b)
b = 4
print(a, b)

The result of b = 4 is that b is now pointing at a new copy of an integer, the value 4.

Recall that I did mention tuples as immutable data. You can't change the binding of a particular entity in your tuple...

t = ('foo', 'bar')
t[0] = 'baz' # illegal

...but you can have mutable data structures as part of those bindings.

t = ([1, 2, 3], 'bar')
t[0].append([4, 5, 6]) # ([1, 2, 3, [4, 5, 6]], 'bar')

So where does that leave our example?

c = number("one", 1)
d = c

number is a mutable type which is named as c, and its values can be changed at will between multiple different bindings to c.

Effectively, we've got a name and a binding to a name:

  • We have a new instance of number and refer to it by the name c.
  • Bind the reference c to another name d.

Again, nothing's changed about c, but it can be referenced through another name.

Unlike with the immutable data, when we reassign the value of d.number, we're reassigning the same binding that c is aware of:

>>> id(d.number)
36696408
>>> id(c.number)
36696408

This is why you require either a new instance or a copy. You have to refer to a different instance of number. With this simple binding, you're not going to accomplish that.

from copy import copy
c = number("one", 1)
d = copy(c)
id(c) # 140539175695784
id(d) # 140539175695856
Community
  • 1
  • 1
Makoto
  • 104,088
  • 27
  • 192
  • 230
  • 5
    This is the right answer, but I wanted to add that Python does not really have variables. They are more like labels. In the first case (a, b), a => 3, b=>3 then b=>4 so you are modifying 2 different labels. With case 2 (c, d), c and d are two different labels pointing to the same Object, so Object.number is just one label. –  Apr 28 '15 at 17:54
  • 1
    This is correct as far is it goes, but it's not really getting at the problem. The issue is not with those lines, because the corresponding lines in the first example do the same thing. The issue is with the *other* lines which later mutate `d`, instead of binding the name to a new object, as is done with `b`. – BrenBarn Apr 28 '15 at 18:00
  • 1
    Fair point. I'll correct it when I'm near a computer and incorporate Shay's information too. – Makoto Apr 28 '15 at 18:01
  • I've incorporated a decent number of changes and fixes with some outside references as well. The 10,000 feet view is nice and all, but understanding the inner workings is a plus too. – Makoto Apr 28 '15 at 19:40
10

I didn't see that anyone provided details on how to make these two cases work the same by copying the object instead of just assigning a new reference to the same object.

import copy
c = number("one", 1)
d = c
e = copy.copy(c)

print(c.number, d.number, e.number)
d.number = 2
e.number = 5
print(c.number, d.number, e.number)

This will give you:

1 1 1
2 2 5
Brad Budlong
  • 1,775
  • 11
  • 10
10

A picture worth a thousand words

a = 3
b = a
c = number("one", 1)
d = c

Block-and-pointer diagram, before


Step 2…

b = 4
d.number = 2

Block-and-pointer diagram, after

You can see why changing d.number would also affect c.


If, before Step 2, you do

import copy
d = copy.copy(c)

… then c and d are independent. Changing d.number will not affect c.

Block-and-pointer diagram, after copying

200_success
  • 7,286
  • 1
  • 43
  • 74
  • 1
    So...for my clarification...let's assume that the `number` class contained a property `numbers`, which referred to a list of numbers (instead of `number`). In that case, by my reading of the [`copy` documentation](https://docs.python.org/3.3/library/copy.html), you would want to use `copy.deepcopy()` due to the fact that `copy.copy()` would insert a reference to the `numbers` list from `c` into `d`, but `copy.deepcopy()` would create a duplicate list. Am I correct? – Deacon Apr 29 '15 at 13:23
8

You are focusing on the fact that these two pairs of lines are the same (both use plain =):

# one
a = 3
b = a

#two
c = number("one", 1)
d = c

What you're missing is that these two lines are different:

# one
b = 4

# two
d.number = 2

The reason they aren't the same is that d.number has a dot in it, and b does not.

Setting d = c does have the same effect as settign b = a. The difference is that doing d.number = 2 is not the same as doing b = 4. When you do b = 4, you assign a new object to the name b. When you do d.number = 2, you modify the object that is already referred to by the name d, without assigning a new object. If you change your second example to d = 2, using plain assignment instead of attribute assignment, you will see that c is unaffected, just as a is unaffected in your first example.

Although it can be confusing, = does not always mean the same thing in all contexts in Python. Assigning to a bare name (blah = ...) is not the same as assigning to an attribute (blah.attr = ...) or an item (blah[item] = ...). To understand what an = means, you need to look at the left-hand side (the assignment target) to see whether it is a bare name or some sort of expression.

As for how to get the effect of your a/b example in your c/d example, it depends on exactly what effect you want. If you want to wind up with d and c pointing to different objects that have different number attributes, you could do:

d = number("one", 2)

Notice that this is now parallel to b = 4 (because it uses assignment to a bare name). There are also other more complex solutions involving making a copy of the existing d object; have a look at the copy module in the standard library. Exactly what to do depends on what you're trying to accomplish with the code.

BrenBarn
  • 242,874
  • 37
  • 412
  • 384
  • 1
    The assignment operator *is* always the same. It always assigns a reference. You're over-complicating it a *lot*. – kirbyfan64sos Apr 28 '15 at 18:10
  • 4
    @kirbyfan64sos: No, it's not. `a = b` always binds a name to a reference; the object `a` has no control over this operation, and in fact `a` might not even previously exist. `a.attr = b` calls `a.__setattr__('attr', b)`, which means `a` has to already exist, and the entire operation is controlled by `a`. `a.attr = b` can copy b, or mutate b, or assign a totally different value, or do nothing, or delete your hard drive, if that's what `a` decides to do. `a = b` can only ever bind b to the name `a`. – BrenBarn Apr 28 '15 at 18:13
  • 1
    But (assuming the user didn't override __setattr__), it still ultimately ends up assigning a reference. – kirbyfan64sos Apr 28 '15 at 18:21
  • 1
    @kirbyfan64sos: But assuming the user didn't override `__setattr__` is exactly the problem. The difference is that with `a=...`, you don't need to assume anything, because it cannot be customized; with `a.x=...`, you can't know what will happen without knowing what `a` is and what behavior it defines. `a.x=...` may not assign anything if `a` doesn't want it to. Moreover, in the context of this question, it doesn't matter whether `d.number = 2` "assigns a reference" to the number 2 or assigns a copy or some other value; the problem is with the reference to the object `d`, not the number 2. – BrenBarn Apr 28 '15 at 18:24
5

In Python, everything is a reference. Take this, for example:

a = '1221398210p3'
b = a
print(id(a) == id(b)) # True

Now, when you do this:

b = 4

you're just changing the number b references. Not the number itself. Now, in your later example:

c = number("one", 1)
d = c
...
d.number = 2

d = c sets d to reference c. Now, when you set d.number, you're setting the attribute on the object that c and d reference. Like I said, everything is a reference.

In Python, variables are more like labels (or, in C terms, pointers): they're not a value. They just point to the real value.

kirbyfan64sos
  • 10,377
  • 6
  • 54
  • 75
2

The reason for this difference is immutable v. immutable objects - an int is an immutable object, your class is a mutable object.

In this example (similar to your first example), both x and y point the same immutable object (the integer, 1). That object can never change, so when you assign y = 2, y now points to a different immutable object (int(2)).

>>> x = 1  # x points to the object int(1).
>>> y = x  # So does y.
>>> assert x is y
>>> y = 2
>>> assert x is not y
>>>    

Your class is a mutable object, similar to a list. In this second example (similar to your second example), both x and y point to the same mutable object ([]). When you append 0 to that, it places it in the list that they both reference. When you assign [0] to y, you are assigning a different list to y, but that list happens to be equal to x, even though it's a different object.

>>> x = []
>>> y = x
>>> assert x is y  # x and y reference the same object.
>>> y.append(0)  # We append 0 to the list that both x and y reference.
>>> x
[0]
>>> y
[0]
>>>
>>> y = [0]  # Creating a new list object.
>>> assert x is not y  # x and y are different objects...
>>> assert x == y  # ...but they are equal.
>>>    
Deacon
  • 3,615
  • 2
  • 31
  • 52
  • 3
    I have a bit of a pet peeve with this characterization. The issue is not with immutable or mutable *objects*. The issue is with mutating and non-mutating *operations*. As your example shows, even when `y` is a mutable list, `y = [0]` does not mutate it. What matters is not that `y` *can* be mutated, but that `y.append` *actually mutates it* (just like, in the question `d.number = 2` mutates `d`). – BrenBarn Apr 28 '15 at 18:30
  • In fact, [small integers aren't completely immutable in Python, if you're willing to muck around with the internals](http://www.reddit.com/r/Python/comments/2441cv/can_you_change_the_value_of_1/). The "immutability" of integers vs. the mutability of the object is not what made the code behave the way it did. It's the fact that we didn't try to mutate the integers. – 200_success Apr 29 '15 at 07:48
  • Alternatively stated, if you defined the `number` class to be immutable (implemented using `collections.namedtuple`, for example), then `c` and `d` wouldn't behave like `a` and `b`. Rather, you would get an error when trying to set `d.number = 2`. – 200_success Apr 29 '15 at 07:51
  • @200_success - OK, I followed the link to [how to make 1 = 2 in Python](http://www.reddit.com/r/Python/comments/2441cv/can_you_change_the_value_of_1/) (OK, not really the title, but might as well be :-D ). All I can say is, "Oh, my aching head." So, it sounds like I'm fairly close to the target. Where do you suggest I start reading to close the gap in my knowledge? – Deacon Apr 29 '15 at 12:42
  • @BrenBarn - My previous comment to 200_success applies to you as well. Suggestions on where to start closing this particular gap? – Deacon Apr 29 '15 at 12:43
  • While it's true that the `numbers` class in this question is mutable, and integers are supposed to be immutable, it's just not relevant to this question. You can start by reading [my answer](http://stackoverflow.com/a/29930723/1157100). =) – 200_success Apr 29 '15 at 12:46
  • 1
    @DougR.: There are some articles out there that talk about how this works. [Here](http://foobarnbaz.com/2012/07/08/understanding-python-variables/) is a short one. [Here](http://nedbatchelder.com/text/names1.html) is a longer one. – BrenBarn Apr 29 '15 at 16:37
  • @BrenBarn - I read the one at [foobarnbaz.com](http://foobarnbaz.com/2012/07/08/understanding-python-variables/) earlier today, will review the second when I've got a moment. The one at [foobarnbaz.com](http://foobarnbaz.com/2012/07/08/understanding-python-variables/) says what I was **trying** to say, only much, much better than I did. After reading it, I think that the biggest issue with mine may have been the presentation. – Deacon Apr 29 '15 at 16:44
2

"What's in a name? that which we call a rose

By any other name would smell as sweet;"

--- Act II, Scene II Romeo Juliet

In Python,

  • a variable is just a symbolic reference to an object or literal
  • an object is an instance of a type or class
  • unless explicitly enforced, all variables are copied by referenced (assignments, parameter passing and function return value)

Now let's understand your example

a and b refers to two different integer literals

a = 3
b = a

print(a, b)

b now refers to a new integer literal (4)

b=4

c refers to an object which is an instance of class number

c = number("one", 1)

d refers to the same object that c points to

d = c

Change the number attribute of the object referred by d. As d and c refers the same object, this also affects c.

d.number = 2
Community
  • 1
  • 1
Abhijit
  • 62,056
  • 18
  • 131
  • 204
2

Why is c same as d, unlike in (a, b) example?

In python the variable is not an object.
“Hamlet was not written by Shakespeare; it was merely written by a man named Shakespeare.” Python make a crucial distinction between a thing, and the label we use to refer to that thing. “The man named Shakespeare” is a man. “Shakespeare” is just a name. If we do:

l = []

then [] is the empty list. l is a variable that points to the empty list, but l itself is not the empty list.

enter image description here

So, in python variables work more like a tag. When you do an assignment in Python, it tags the value with the variable name.

a = 1

enter image description here

and if you change the value of the variable, it just changes the tag to the new value in memory.

enter image description hereenter image description here

Assigning one variable to another makes a new tag bound to the same value as show below.

b = a  

enter image description here

If you assign a new value 3 to b, then it will bound to that new value and a will still be bound to 2.

In second snippet, d = c will make both c and d to point to the same object. Modifying any object member will cause that member to point to the new value and not the name a and b. They will still point to the same object.

How can I do something like in (a, b) in (c, d) classes example? That is, copy the object and then change one part of it (that won't affect the object that I borrowed properties from)?

Python 2.7.10: 8.17. copy — Shallow and deep copy operations:

Assignment statements in Python do not copy objects, they create bindings between a target and an object. For collections that are mutable or contain mutable items, a copy is sometimes needed so one can change one copy without changing the other. This module provides generic shallow and deep copy operations (explained below).

Interface summary:

copy.copy(x)

Return a shallow copy of x.

copy.deepcopy(x)

Return a deep copy of x.

>>> import copy
>>> c = number("one", 1)
>>> d = copy.copy(c)        # Make a copy of object c  
>>> id(d), id(c)
(42898856, 43279384)  

Modifying any one of c or d object will not affect one another.


References:

haccks
  • 104,019
  • 25
  • 176
  • 264
1

Assigning an object reference to a variable is exactly like assigning a number to a variable. NEITHER of them involves copying anything. It's precisely the same in Python's intellectual ancestors Lisp and Smalltalk. It just happens that numbers cannot be mutated.

>>> x = 10**50
>>> y = 10**50
>>> z = x
>>> x is y
False
>>> x is z
True

We see here that Python discriminates between making a copy (x and y are equal but distinct) and not making a copy (x is z), and that assigning a number doesn't make a copy.

raok
  • 191
  • 3
0

This wasn't clarified in other answers, but if I recall correctly, integers in python are not actually primitives. They are immutable integer objects. So when you have multiple variables all holding the value 3, you actually have multiple references to the single instance of Integer that holds the value 3.

So with this in mind;

a = 3
b = a

Results in variables a and b each pointing to the Integer holding the value 3. Likewise,

c = number("one", 1)
d = c

Results in variables c and d each pointing to the same instance of number.

This same thing occurs with

b = 4

Now b is a reference to the Integer 4.

However, when d.number is set

d.number = 2

The member variable "number" of the instance of the class "number", which is pointed to by both c and d is updated to reference the Integer 2. The important concept here is that you are modifying a member variable of an object that is referenced by two separate variables.

Sources: http://www.laurentluce.com/posts/python-integer-objects-implementation/

GenFrank64
  • 32
  • 1