6

Consider the following piece of code:

def func1(a):
    a[:] = [x**2 for x in a]

a = range(10)
print a  #prints [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
func1(a[:5])
print a  #also prints [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

I wish to send a slice of the list a and change it inside the function. My expected output is

[0, 1, 4, 9, 16, 5, 6, 7, 8, 9]

Which way is the idiomatic way to do so?

Thanks!

AvidLearner
  • 4,123
  • 5
  • 35
  • 48
  • You can also check this http://stackoverflow.com/questions/22054698/python-modifying-list-inside-a-function – Mazdak Feb 20 '17 at 14:07
  • @Kasramvd As the other question has no slicing, I do not believe that these are duplicates. Thanks for the reference anyway... – AvidLearner Feb 20 '17 at 14:17
  • That's a trivial difference, but the main problem is that changing a mutable object inside a function may reflect the caller. – Mazdak Feb 20 '17 at 14:21
  • @Kasramvd I disagree. I peeked on that question *before* I wrote mine, but was not sure what is the _pythonic_ way to deal with slices in these cases. – AvidLearner Feb 20 '17 at 14:30
  • 1
    @Kasramvd FYI I reopened, because the former dupe link was just related. I'm letting you know because I hate when someone reopens behind my back, but here I think it should be done. – Jean-François Fabre Aug 25 '18 at 19:47

3 Answers3

6

If you slice the list, you modify only a copy, so what you want to do doesn't work in the form you want.

But you could pass an optional slice object to func1 and if it's not None, use it to perform the slice assignment (else use [:])

I would do the following (used a lambda to avoid copy/paste of the formula and a generator expression to avoid creating a useless temporary list:

def func1(a,the_slice=None):
    e = lambda y : (x**2 for x in y)
    if the_slice:
        a[the_slice] = e(a[the_slice])
    else:
        a[:] = e(a)

testing:

a = list(range(10))
func1(a)
print(a)
a = list(range(10))
func1(a,slice(5))   # stop at 5
print(a)
a = list(range(10))
func1(a,slice(5,len(a),2))  # start at 5 / stop at the end, stride/step 2
print(a)

result:

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[0, 1, 4, 9, 16, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 25, 6, 49, 8, 81]
  • in the first case, the totality of the list was changed
  • in the second case, it only changed the first half.
  • in the third case, it changed the second half, but 1 value out of 2 (stride=2)
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
1

This will work:

a = range(10)

a[:5] = [c**2 for c in a[:5]]
zipa
  • 27,316
  • 6
  • 40
  • 58
  • thanks, I presented this toy example as a mean of expressing my bigger issue, hence this straightforward answer doesn't really help... – AvidLearner Feb 20 '17 at 13:55
0

a[:5] creates a new list. Hence, the changes that func1 applies to it are not mirrorred in a. You could add the slicing to the function:

def func1(a, start=None, stop=None, step=None):
    start = start if start is not None else 0
    stop = stop if stop is not None else len(a)
    step = step if step is not None else 1
    a[start:stop:step] = [x**2 for x in a[start:stop:step]]

func1(a, stop=5)
user2390182
  • 72,016
  • 6
  • 67
  • 89