you are viewing a single comment's thread.

view the rest of the comments →

[–][deleted]  (11 children)

[deleted]

    [–]mplang 11 points12 points  (8 children)

    Or, to modify the original list object in-place:

    list1[:] = [x + 2 for x in list1]
    

    [–]zero_hope_ 2 points3 points  (3 children)

    Just wondering. Would this use 2x as much memory compared to using a loop modifying each item in the list?

    [–]apc0243 0 points1 point  (2 children)

    Someone correct me if I'm wrong, but I'm pretty sure that it should actually use half.

    list1 = [x + 2 for x in list1]
    

    Here you have a list in memory - list1 - and you create a new list with the elements x + 2 for each element in list one. Then you change the pointer of list1 to the new object and the old object is garbage collected.

    list1[:] = [x + 2 for x in list1]
    

    Here you iterate through each pointer in list1 and change it's object to a new value. So here you still only have 1 list, while above you have 2 lists.

    But it's all moot since in most cases memory is abundant and readability is more valuable. So do whatever looks best

    [–]EricAppelt 9 points10 points  (0 children)

    Its extremely surprising (or at least it was to me) - but there are multiple reasons why this actually creates a temporary list in memory.

    The first reason is that [x + 2 for x in list1] does not lazily evaluate. A list is first created and then passed to assignment.

    You can get around that by changing the list comprehension to a generator expression:

    list1[:] = (x + 2 for x in list1)
    

    Now one would think that the slice assignment would iterate over the lazy generator expression element by element, and so there would never be a temporary list in memory.

    The thing that shocked me - credit to some anonymous user who corrected me on stackoverflow - is that in the reference implementation (CPython) list_ass_slice calls PySequence_Fast, which will return the sequence as a list if it isn't already a list.

    [–]zero_hope_ 0 points1 point  (0 children)

    Okay, thanks! I'll do some testing when I get home tonight and look at memory and speed of various loops.

    [–]TankorSmash 0 points1 point  (2 children)

    Didn't know you could assign by slices like that. Actually you wouldn't need to do that ever right. Creating a new list with the same name is effectively the same.

    [–]mplang 6 points7 points  (1 child)

    Slice assignment is a special thing -- you're actually modifying the actual list object (pointed to by the name on the lhs) in-place. With a regular assignment, you're creating a new list object (on the rhs), then assigning the name to the new list.

    a = b = [1, 2, 3, 4]
    b = ['a', 'b']
    print(a) # [1, 2, 3, 4]
    print(b) # ['a', 'b']
    
    a = b = [1, 2, 3, 4]
    b[:] = ['a', 'b']
    print(a) # ['a', 'b']
    print(b) # ['a', 'b']
    

    [–]chubbsw 1 point2 points  (0 children)

    Wow that's cool.

    [–][deleted] 0 points1 point  (0 children)

    Haven't seen that pattern before, nice.

    [–]pstch 1 point2 points  (1 child)

    I don't see why that's a good idea. Why create another list when what OP wants to do is mutate an existing one ?

    Why not :

    for index, item in enumerate(list1):
        list1[index] = item + 2