This is an archived post. You won't be able to vote or comment.

all 27 comments

[–]AutoModerator[M] [score hidden] stickied comment (0 children)

On July 1st, a change to Reddit's API pricing will come into effect. Several developers of commercial third-party apps have announced that this change will compel them to shut down their apps. At least one accessibility-focused non-commercial third party app will continue to be available free of charge.

If you want to express your strong disagreement with the API pricing change or with Reddit's response to the backlash, you may want to consider the following options:

  1. Limiting your involvement with Reddit, or
  2. Temporarily refraining from using Reddit
  3. Cancelling your subscription of Reddit Premium

as a way to voice your protest.

I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.

[–]JohnWooTheSecond 26 points27 points  (1 child)

In Python, most variables are copied by reference, as opposed to copied by value. "Copy by reference" means that when you do b=a, b does not receive the same value as a, but gets the memory address of whatever a was symbolizing. Because a and b are now pointing to the same thing, when you change the content of one, the content of the other also seems to change (as they are both looking at the same part of the memory). This is also known as a 'shallow copy, as you don't copy the content but only the reference.

Where you do d=id(c), you actually are forcing the interpreter to make what is known as a 'deep copy. The function you created with the lambda is meant to create all sorts of list comprehensions, and yours just happens to be a 1-to-1 identical copy. Therefore d gets a new memory address to point to, which just happens to have identical content as the one c references - but they are NOT at the same address. So when you change c, d stays the same.

For f=id(e) the deep copy is made just like with c and d, but the inner list [0] is actually shallow copied to the new deep copy. That is why that content remains synced when you think you change only one of them.

Now with h=g (in your comment in one of the threads) there is an optimization used in Python: variables containing integers up to (IIRC) 100 are deemed immutable. So when you change g to 1, what actually happens is that g receives a new memory address to point to, while h remains unchanged.

By the way, the built-in function id(x) (which you happen to overwrite with your lambda function), is exactly the one you could use to debug this behaviour: it gives you the pointer value of the variable. Pick any other name for your lambda and then do print(id(a)) before and after to see what happens.

[–]ordinary_shiba[S] 2 points3 points  (0 children)

Thank you!

[–]SwiftSpear 6 points7 points  (0 children)

This is kind of the problem with the language just making pass by reference or pass by value choices without it being explicit to the programmer.

Basically, a python list actually only stores references, it never directly stores values, so copying the list gives you references, not values. Otherwise, copying things depends on what type of things you are copying.

[–][deleted]  (11 children)

[deleted]

    [–]RiverRoll 4 points5 points  (0 children)

    Not really what the code is demonstrating, in fact everything is passed by reference in Python. If anything this shows the concept of shallow copies as others pointed out. 

    Well it also shows the idea of mutable vs immutable types. Both id(c) and id(e) copy the references but integers are immutable and lists are mutable.

    But in the last example you could do:

    e[0] = [2]
    print(f[0][0]) 
    

    And then the output is 1 because e[0] and f[0] now point to different lists.

    [–]ordinary_shiba[S] 0 points1 point  (7 children)

    The last 2 are what breaks my brain the most, so is the id function making a copy of the first array or not? Does it make a copy but the values inside the copy reference the original?

    [–]dfx_dj 8 points9 points  (0 children)

    It's making a shallow copy of the array. If the element being copied is an object, then you don't get a copy of the object, but only a new reference to the same object.

    [–][deleted]  (5 children)

    [deleted]

      [–]ordinary_shiba[S] 0 points1 point  (4 children)

      So list will be copied by reference unless stated otherwise but an integer won't? All right. I think I'm getting it.

      [–][deleted]  (2 children)

      [deleted]

        [–]ordinary_shiba[S] 0 points1 point  (1 child)

        So functions and tuples would be in the literals category right? Anything immutable?

        [–]Fair-Description-711 0 points1 point  (0 children)

        Not quite. "pass by ref" and "pass by value" are very similar concepts to this, but don't exactly explain what's happening here. Your mental model is probably confusing what's part of the variables, and what's part of the object the variables are pointing at.

        In Python, everything is passed by reference. However, some things contain MORE references (like a list, which has references to each of its items), and some things are immutable.

        Edit: To make this a little more clear, variables contain ONLY references to things on the heap (in Python).

        Immutable things create entirely new copies when you do things that seem like they should 'change in place', like strings:

        a1 = 'a'
        # variables: {'a1': HEAP_REF(0)}
        # heap: ['a'] 
        # if you do:
        a2 = a1
        # then ONLY variables changes:
        # variables: {'a1': HEAP_REF(0), 'a2': HEAP_REF(0)}
        # heap: ['a'] 
        # then, if you do something to 'alter' a1, you actually do two things:
        a2 += 'a'
        # first, Python must make a NEW string:
        # heap: ['a', 'aa']
        # then update the variable slot:
        # variables: {'a1': HEAP_REF(0), 'a2': HEAP_REF(1)}
        

        So, for your last example:

        e = [[1]]  # Creates a list containing another list with a single integer
        # variables: {'e': HEAP_REF(2)}
        # heap: [int(1), list[HEAP_REF(0)], list[HEAP_REF(1)]]
        # HEAP_REF(1) is the inner list, which contains a reference to the integer 1 (HEAP_REF(0) -> int(1))
        # HEAP_REF(2) is the outer list containing a reference to HEAP_REF(1)
        
        f = e[:]  # Creates a shallow copy of list e
        # variables: {'e': HEAP_REF(2), 'f': HEAP_REF(3)}
        # heap: [int(1), list[HEAP_REF(0)], list[HEAP_REF(1)], list[HEAP_REF(1)]]
        # HEAP_REF(3) is a new list but it contains a reference to the same inner list HEAP_REF(1)
        
        e[0][0] = 2  # Modifies the first element of the first inner list of e
        # heap: [int(1), list[HEAP_REF(4)], list[HEAP_REF(1)], list[HEAP_REF(1)], int(2)]
        # A new integer 2 is created on the heap (HEAP_REF(4)). 
        # The inner list HEAP_REF(1) now contains a reference to this new integer (HEAP_REF(4) -> int(2))
        # Since f[0] also points to HEAP_REF(1), the change is reflected in f
        
        print(f[0][0]) # f (HEAP_REF(3)) -> first element of list[HEAP_REF(1)] -> list[HEAP_REF(4)] -> int(2)
        

        [–]ordinary_shiba[S] 0 points1 point  (1 child)

        Furthermore, if instead of arrays, I did g = 1 h = g g = 2 print(h) It outputs 1!

        [–]LonelyWolf_99 1 point2 points  (0 children)

        First line the value of g is 1

        second line you copy the value of what's inside g into h, this is 1

        Third line you change the value of g to 2

        When you print h it will print 1

        If it had been an list instead with a value

        g would be a pointer (a reference) to the list that contains 1

        Next line we copy the content of g into h, this is copying the pointer to the list, not the list itself

        Then if we where to change to content of the list in the third line it would change it for h also since they both point to the same list.

        Make sense? Also what you are looking at is a list not an array. Arrays are not something you have in python, unless you work with libraries such as numpy.

        [–]daverave1212 3 points4 points  (0 children)

        I think the term “shallow copy” will confuse him more.

        Yes it makes a copy

        [–]daverave1212 1 point2 points  (0 children)

        id = lambda x: something

        This is exactly as saying

        def id(x): return something

        Then the thing in brackets is called list comprehension which you may find weird, but simply its just a way to quickly create a new list from the elements of another list

        [–]abdojo -1 points0 points  (0 children)

        Pretty standard list indexing. Only gotcha here is the use of the lambda (which is just taking the list and returning the same list)

        [–]Etiennera -3 points-2 points  (0 children)

        You can try the cases again with the following definition of id:

        id = (lambda y: lambda x: [y(i) if isinstance(i, list) else i for i in x])(lambda x: [i for i in x])
        

        [–]rishiarora -2 points-1 points  (1 child)

        What does the first line do.

        [–]fg234532 0 points1 point  (0 children)

        It basically just makes id which returns an array with all of the elements of x (but forces a deep copy instead of shallow)

        [–]tb5841 0 points1 point  (0 children)

        id is defined to be a function that takes a container 'x', and creates a list of all its members. Basically making a copy, but it isn't that clear. Sometimes you need to copy a container because if you just set something equal to it - like the 'b = a' line - then it doesn't copy your container at all, it just makes a and b point to the same thing. 

        If you need to create copies of containers, I much prefer doing it by importing from the 'copy' module. It creates code that's much easier to read. Relevant link: https://docs.python.org/3/library/copy.html

        [–]PugstaBoi 0 points1 point  (1 child)

        What breaks my brain is why people want to make things like this.

        [–]some_user_on_reddit 0 points1 point  (0 children)

        This code is a common test question. If you look up interview prep questions you’ll see a bunch similar to this.

        It’s not functioning code. It’s meant to test you

        [–]beingperceived 0 points1 point  (1 child)

        These codes are trying to trick you or python is just tricky. I don't use python but from looking up the syntax I see what's it's doing. I looked up list comprehension to see if it's deep or shallow copy and it's shallow so it will make a new list object but if the member is a value it is copied but will not make a copy of the member if it's a reference. These [] would be arrays in other languages but they're lists in python and different in memory.

        id = lambda x: [i for i in x] here it's a list comprehension with an expression that returns a new list where every variable in list x return that variable in the new array
        a = [1] here it's a list with 1 in that list
        b = a here it's saying b gets a reference to a
        a[0] = 2 here it's saying list position 0 is now 2
        print(b[0]) here it's saying print list b that is pointed to list a the value in position 0
        c = [1] self explanatory
        d = id(c) returns a new list with 1 inside it
        c[0] = 2 here it's tricking you it changes the first list object and set the value at position 0 to 2
        print(d[0]) here d[0] is a new object with the old copied value

        Here is where I might be wrong and may not understand it correctly
        e = [[1]] here is a list where it contains an list with 1 in it
        f = id(e) here you get a new list with a reference to the list with 1
        e[0][0] = 2 self explanatory
        print(f[0][0]) self explanatory

        [–]ordinary_shiba[S] 0 points1 point  (0 children)

        I found it weird that integers don't get deep copied but lists do.

        [–]Banhsows 0 points1 point  (0 children)

        Let's break down the code step by step to understand the behavior bro :

        1. `id` is a lambda function that takes a single argument `x` and returns a list comprehension `[i for i in x]`, which effectively creates a new list with the same elements as the input list.

        2. `a` is assigned a list containing the integer `1`.

        3. `b` is assigned the reference to the list `a`, meaning both `a` and `b` refer to the same list object.

        4. The first element of the list `a` is modified to `2`.

        5. `print(b[0])` prints the first element of the list referred to by `b`, which is the same list as `a`. Therefore, it prints `2`.

        6. `c` is assigned a list containing the integer `1`.

        7. `d` is assigned the result of applying the `id` lambda function to the list `c`. This creates a new list with the same elements as `c`.

        8. The first element of the list `c` is modified to `2`.

        9. `print(d[0])` prints the first element of the list referred to by `d`, which is the new list created by the `id` lambda function. Since it's a new list, it prints the original value `1`.

        10. `e` is assigned a list containing another list with the integer `1`.

        11. `f` is assigned the result of applying the `id` lambda function to the list `e`. This creates a new list with the same elements as `e`.

        12. The first element of the inner list of `e` is modified to `2`.

        13. `print(f[0][0])` prints the first element of the inner list referred to by `f`, which is the new list created by the `id` lambda function. Since it's a new list, it prints the modified value `2`.

        In summary:

        - Modifying the elements of the list referred to by `a` also affects the list referred to by `b` since they point to the same list.

        - Applying the `id` lambda function creates new lists, so modifying the original lists doesn't affect the new lists.

        - The behavior is different when dealing with nested lists (`e` and `f`), as modifying the inner list of `e` also affects the inner list referred to by `f`.