Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Different slicing behaviors on left/right hand side of assignment operator

As a Python newbie coming from the C++ background, the slicing operator in Python (3.4.x) looks ridiculous to me. I just don't get the design philosophy behind the "special rule". Let me explain why I say it's "special".

On the one hand, according to the Stack Overflow answer here, the slicing operator creates a (deep) copy of a list or part of the list, i.e. a new list. The link may be old (earlier than python 3.4.x), but I just confirmed the behavior with the following simple experiment with python 3.4.2:

words = ['cat', 'window', 'defenestrate']
newList = words[:] # new objects are created; a.k.a. deep copy
newList[0] = 'dog'

print(words) # ['cat' ...
print(newList) # ['dog' ...

On the other hand, according to the official documentation here:

Assignment to slices is also possible, and this can even change the size of the list or clear it entirely:
>>>

>>> letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
>>> letters ['a', 'b', 'c', 'd', 'e', 'f', 'g']
>>> # replace some values
>>> letters[2:5] = ['C', 'D', 'E']
>>> letters
['a', 'b', 'C', 'D', 'E', 'f', 'g']
>>> # now remove them
>>> letters[2:5] = []
>>> letters
['a', 'b', 'f', 'g']
>>> # clear the list by replacing all the elements with an empty list
>>> letters[:] = []
>>> letters 
[]

Clearly, the slicing operator [:] does not do a deep copy here.

From the observation it seems to suggest that the slicing operator produces different behavior when it's on left/right side with respect to the assignment operator. I do not know any language in which an operator could produce similar behavior. After all, an operator is a function, just a syntactically special function, and a function's behavior should be self-contained, purely determined by all of its inputs.

So what can justify this "special rule" in Python design philosophy?

P.S. If my conclusion is not correct, there are really only two possibilities:

1, Python's slicing 'operator' is actually not an operator, so my assumption does not hold --- then what is it (the 'slicing operator' [:])?

2, The difference in behavior is caused by some latent factor not observed. The slicing operator's location (left/right hand side) with respect to the assignment operator accidentally co-exists with the observation of different behavior. They do not have causality relationship --- then what is the latent factor that causes the difference in behavior?

like image 795
h9uest Avatar asked May 13 '15 16:05

h9uest


1 Answers

Python operators are best considered as syntactic sugar for "magic" methods; for example, x + y is evaluated as x.__add__(y). In the same way that:

  • foo = bar.baz becomes foo = bar.__getattr__(baz); whereas
  • bar.baz = foo becomes bar.__setattr__(baz, foo);

the Python "slicing operator" * a[b] is evaluated as either:

  • a.__getitem__(b); or
  • a.__setitem__(b, ...);

depending on which side of the assignment it's on; the two aren't quite the same (see also How assignment works with python list slice). Written out in "longhand", therefore:

>>> x = [1, 2, 3]
>>> x.__getitem__(slice(None))  # ... = x[:]
[1, 2, 3]
>>> x.__setitem__(slice(None), (4, 5, 6))  # x[:] = ...
>>> x
[4, 5, 6]

The data model documentation explains these methods in more detail (e.g. __getitem__), and you can read the docs on slice, too.


Note that the slice is a shallow copy, not a deep one, as the following demonstrates:

>>> foo = [[], []]
>>> bar = foo[:]
>>> bar is foo
False  # outer list is new object
>>> bar[0] is foo[0]
True  # inner lists are same objects
>>> bar[0].append(1)
>>> foo
[[1], []]

* Well, not strictly an operator.

like image 128
jonrsharpe Avatar answered Sep 24 '22 15:09

jonrsharpe