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!

5
  • You can also check this stackoverflow.com/questions/22054698/… Commented Feb 20, 2017 at 14:07
  • @Kasramvd As the other question has no slicing, I do not believe that these are duplicates. Thanks for the reference anyway... Commented Feb 20, 2017 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. Commented Feb 20, 2017 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. Commented Feb 20, 2017 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. Commented Aug 25, 2018 at 19:47

4 Answers 4

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)
Sign up to request clarification or add additional context in comments.

Comments

1

This will work:

a = range(10)

a[:5] = [c**2 for c in a[:5]]

1 Comment

thanks, I presented this toy example as a mean of expressing my bigger issue, hence this straightforward answer doesn't really help...
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)

2 Comments

Isn't there a way to execute this idea without messing it with indices?
You can assign to a slice, but that doesn't change the fact that fnc1(a[:5]) passes a list to fnc1 that knows nothing about the original a.
0

Your issue was overlapping variables and incorrect slicing. The solution:

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

a = list(range(10))
func1(a[:3].copy())
print(a)
[0, 1, 4, 3, 4, 5, 6, 7, 8, 9, 10]
    

I also noticed that in the function, the b[:n] is redundant. You can simply put it as b.

The above solution is only if it sliced from the start. I also coded a solution for it to be sliced from the middle, was waiting for the weekend to post it.

def func1(b):
    n1 = len(b)
    n2 = len(a)
    if n1<=n2:
        first_element = b[0]
        last_element = b[n1-1]
        first_element_index = a.index(b[0],0,n2)
        last_element_index = a.index(b[n1-1],0,n2)
        if last_element_index - first_element_index==n1-1:
            a[first_element_index+1:last_element_index+1] = [x**2 for x in b]
        
        def rec(first,last,start,end,n1,n2):
            while start<=n2 and end<=n2:
                if a.index(last,start,end)-a.index(first,start,end)==n1-1:
                    a[a.index(first)+1:a.index(last)+1] = [x**2 for x in b]
                else:
                    return rec(first,last,a.index(first)+1,a.index(last)+1,n1,n2)
            a[:]=[x**2 for x in b]
    else:
          a[:] = [x**2 for x in b]

a = [1,8,9,4,5,7,2,6]

func1(a[6:8])

print(a)
                
[1, 8, 9, 4, 5, 7, 2, 4, 36]


The code is longer than usual because I confirm subset of a ..it can be shortened if subset is guaranteed. Similarly, it can be lengthened and made more robust by adding more checks and validations.

2 Comments

I feel like this answer misunderstands the question. There was never any attempt to use global variables to access a, it was always supposed to be accessed as a reference to the slice of the original list. Unfortunately, that's not how list slicing works in Python, so a different design is necessary. Using global variables is not a design I'd recommend.
@Blckknght I agree that using global variables is not good design. I just interpreted the question this way because what you are suggesting didn’t even strike me because it’s impossible to do in python. In fact I don’t know any language in which that is possible - so I didn’t get why OP would be asking that. So the thought didn’t even cross my mind.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.