Search code examples
pythonslice

What does slice operation actually return in python


"All slice operations return a new list containing the requested elements" This is from the python tutorials.

But if this is the case then why does this piece of code behave this way:

>>> a = [11, 3, 1999]
>>> a[:] = [9, 78]
>>> a
[9, 78]
  1. if slicing returned a new list then why does the binding that I have done to the new one affect the original one? What does this suggest?

But then I have also observed this:

>>> b = [4, 5, 6]
>>> b[:].append(5)
>>> b
[4, 5, 6]
  1. This shows that indeed a new list is returned. What is really happening when we slice a list?

Solution

  • You are confusing expressions with assignment. Getting values (reading) is handled differently from setting values (writing).

    Assignment (setting) re-uses syntax to specify a target. In an assignment like a[:] = ..., a[:] is a target to which the assignment takes place. Using a[:] in an expression produces a new list.

    In other words: you have two different language statements, that are deliberately using the same syntax. They are still distinct however.

    See the Assignment statements reference documentation:

    assignment_stmt ::=  (target_list "=")+ (starred_expression | yield_expression)
    target_list     ::=  target ("," target)* [","]
    target          ::=  identifier
                         | "(" [target_list] ")"
                         | "[" [target_list] "]"
                         | attributeref
                         | subscription
                         | slicing
                         | "*" target
    

    [...]

    • If the target is a slicing: The primary expression in the reference is evaluated. It should yield a mutable sequence object (such as a list). The assigned object should be a sequence object of the same type. Next, the lower and upper bound expressions are evaluated, insofar they are present; defaults are zero and the sequence’s length. The bounds should evaluate to integers. If either bound is negative, the sequence’s length is added to it. The resulting bounds are clipped to lie between zero and the sequence’s length, inclusive. Finally, the sequence object is asked to replace the slice with the items of the assigned sequence. The length of the slice may be different from the length of the assigned sequence, thus changing the length of the target sequence, if the target sequence allows it.

    (Bold emphasis mine).

    Compare this with the Slicings section in the expressions reference documentation; slicing in an expression produces a slice() object, which the list.__getitem__ method interprets as a request for a new list object with the matching indices copied over. Other object types can choose to interpret a slice object differently.

    Note that there is a third operation, the del statement to delete references, including slices. Deletion takes the same target_list syntax and asks to remove the indices indicated by a slice.

    These three operations are, under the hood, implemented by the object.__getitem__() (reading), object.__setitem__() (writing) and object.__delitem__() (deleting) hook methods; the key argument to each of these operations is a slice() object, but only __getitem__ is expected to return anything.