0

Yesterday I got caught in the old copy-versus-reference trap and it took me a long while to locate the problem. I wrote:

    if self.cash:
        self.__dict__['cash2'] = self.__dict__['cash1']
        self.__dict__['cash3'] = self.__dict__['cash1']

When I wanted:

    if self.cash:
        self.__dict__['cash2'] = list(self.__dict__['cash1'])
        self.__dict__['cash3'] = list(self.__dict__['cash1'])

I have two questions related to the disparate behaviors of the assignment operator (=).

  1. Why does Python behave this way?

  2. When would one want a reference instead of a copy?

Maybe if I grok this I can avoid shooting myself in the foot again.

martineau
  • 119,623
  • 25
  • 170
  • 301
John
  • 435
  • 6
  • 15
  • 3
    Question 2 is backwards. Copying a reference is far cheaper than copying an entire data structure, so ask yourself "When would one *need* a copy instead of a reference". – chepner Feb 12 '19 at 16:32
  • 3
    I don't understand why you call this "disparate behaviors". Python *never* makes a copy on assignment. – Daniel Roseman Feb 12 '19 at 16:33
  • And the fact that copying a reference is cheaper pretty much answers Question 1. – chepner Feb 12 '19 at 16:33
  • *Why does Python behave this way?* Because everything in Python is an object. Reference/Value doesn't really apply. The "value" that you refer to is just the the object itself. If you refer to the object directly, it's by reference. If you refer to a *copy* of the object, it's the value... but it's also another object. What seems to be "values" like `int` or `str` is just an artifact of their immutability. – r.ook Feb 12 '19 at 16:33
  • `=` behaves the same in *every* situation; the differences you may see are based on the difference between mutable and immutable values, not assignment itself. – chepner Feb 12 '19 at 16:34
  • 2
    Also see https://nedbatchelder.com/text/names.html – Martijn Pieters Feb 12 '19 at 16:35
  • Maybe you can think of it in different terms. All Python variables (that are not primitive, which are immutable anyway) are really just references, not the objects themselves. So if you do `a = b` you are copying the reference, whereas making a copy of an existing object requires constructing the object explicitly. – jdehesa Feb 12 '19 at 16:37
  • 1
    Python doesn't really have primitive types; even an `int` is an internal Python object that might wrap a single machine word, or an array of machine words for larger values. The details aren't visible in Python itself. (Put another way, a Python `int` is closer to a Java `Integer` than a Java `int`.) – chepner Feb 12 '19 at 16:43
  • @jdehesa not really true. Python variables work exactly the same regardless of the type of object they are referring to. There are no "primitive" variables or types in Python, not in a way that affects what you are talking about. – juanpa.arrivillaga Feb 12 '19 at 16:44
  • 1
    The important thing to grok is that in both cases the assignments statement are working **exactly the same**. It always says "the identifier on the left now refers to the object on the right". The right hand side can be any expression (i.e. something that evaluates to a value, i.e. an object). The issue is that the expressions on the right hand side are different. I think along with the Ned Batchelder link, the [following](http://foobarnbaz.com/2012/07/08/understanding-python-variables/) article is good and uses a useful metaphor: variables in python are like name tags. – juanpa.arrivillaga Feb 12 '19 at 16:46
  • 1
    @juanpa.arrivillaga Yes that's technically correct. I was trying to express the idea that, conceptually, while "primitives" (not really primitive, let's say "types for which there are literals", ie bool, int, float, str, bytes) may be treated as "copy-by-value" (or not, doesn't matter cause are immutable), vars for other objs are better thought of as refs/pointers. For me personally that's easier to "visualize" (maybe cause I learned programming with Java?) than "when you do `i = 3`, `i` contains a ref to an `int` object, not the value `3` itself". Others may find it more confusing though. – jdehesa Feb 12 '19 at 17:02
  • 1
    @jdehesa I think the problem is that is a slippery slope down the path to the common misconception that immutable and mutable objects have different assignment semantics. Anyway, consider `frozenset` or `tuple` objects, things I would hardly call "primitive" (frozenset's don't even have corresponding literals) and they work exactly the same as the "primitive" types, but again, **they all work exactly the same**. There really is no difference, and I think that is important to grok. Immutable objects *simply lack mutator methods*. If you are persistent enough you can mutate them. – juanpa.arrivillaga Feb 12 '19 at 17:05
  • 1
    @juanpa.arrivillaga Yes, you are right about that, it's a "leaky" mental model. However Python does implement some things in ways that can be very confusing for new users, for example, in `a = 2; b = a; a += 3` or `a = (1, 2); b = a; a += (3,)`, `b` is not modified, while in `a = [1, 2]; b = a; a += [3]` it is (and I understand why this is), so sometimes it does "feel" like there are reference types and value types. Anyway, you are right that it is better to really understand the actual data model to avoid surprises. – jdehesa Feb 12 '19 at 17:14
  • First, Martjin, I think that you are wrong to ding me for a repeated question.The others asked how, I provided how and asked WHY. Second, thanks for the Batchelder reference. He wrote "Although this behavior might be surprising, it’s essential. Without it, we couldn’t write methods that modify objects." which was an aha! moment for me. Finally, thanks to jdehesa and juanpa for a very interesting and informative discussion and the link to the tags article, which I also found useful. – John Feb 13 '19 at 21:51

0 Answers0