On May 15, 2020, at 03:50, Steven D'Aprano email@example.com wrote:
On Thu, May 14, 2020 at 09:47:36AM -0700, Andrew Barnert wrote:
On May 14, 2020, at 03:01, Steven D'Aprano firstname.lastname@example.org wrote:
Which is exactly why Christopher said from the start of this thread, and everyone else has agreed at every step of the way, that we can’t change the default behavior of slicing, we have to instead add some new way to specifically ask for something different.
Which is why I was so surprised that you suddenly started talking about not being able to insert into a slice of a list rather than a view.
We’re talking about slice views. The sentence you quoted and responded to was about the difference between a slice view from a list and a slice view from a string. A slice view from a list may or may not be the same type as a slice view from a tuple (I don’t think there’s a reason to care whether they are or not), but either way, it being immutable will, I think, not surprise anyone. By contrast, a slice view from a string being not stringy _might_ surprise someone.
Not only that, but whatever gives you view-slicing must look sufficiently different that you notice the difference—and ideally that gives you something you can look up if you don’t know what it means. I think lst.view[10:20] fits that bill.
Have we forgotten how to look at prior art all of a sudden? Suddenly been possessed by the spirits of deceased Java and Ruby programmers intent on changing the look and feel of Python to make it "real object oriented"? *wink*
No, we have remembered that language design is not made up of trivial rules like “functions good, methods bad”, but of understanding the tradeoffs and how they apply in each case.
We have prior art here:
b'abcd'.memoryview # No, not this. memoryview(b'abcd') # That's the one.
'abcd'.iter # No, not that either. iter('abcd') # That's it
In fairness, I do have to point out that dict views do use a method interface,
This is a secondary issue that I’ll come back to, but first: the whole thing that this started off with is being able to use slicing syntax even when you don’t want a copy.
The parallel to the prior art is obvious:
itertools.islice(seq, 10, 20) # if you don’t care about iterator or view sliceviews.slice(seq, 10, 20) # if you do
The first one already exists. The second one takes 15 lines of code, which I slapped together and posted near the start of the thread.
The only problem is that they don’t solve the problem of “use slicing syntax”. But if that’s the entire point of the proposal (at least for Chris), that’s a pretty big problem.
Now, as we’d already been discussing (and as you quoted), you _could_ have a callable like this:
I can write that in only a few more lines than what I posted before, and it works. But it’s no longer parallel to the prior art. It’s not a function that returns a view, it’s a wrapper object that can be sliced to provide a view. There are pros and cons of this wrapper object vs. the property, but a false parallel with other functions is not one of them.
- Dict views came with a lot of backwards-compatibility baggage;
they were initially methods that returned lists; then methods that returned iterators were added, then methods that returned views were added, and finally in 3.x the view methods were renamed and the other six methods were removed.
This is, if anything, a reason they _shouldn’t_ have been methods. Changing the methods from 2.6 to 2.7 to 3.x, and in a way that tools like six couldn’t even help without making all of your code a bit uglier, was bad, and wouldn’t have been nearly as much of a problem if we’d just made them all functions in 2.6.
And yet, the reasons for them being methods were compelling enough that they remain methods in 3.x, despite that problem. That’s how tradeoffs work.
- There is only a single builtin mapping object, dict, not like
sequences where there are lists, tuples, range objects, strings, byte strings and bytearrays.
Well. there’s also mappingproxy, which is a builtin even if its name is only visible in types. And there are other mappings in the stdlib, as well as popular third-party libraries like SortedContainers. And they all support these methods. There are some legacy third-party libraries never fully updated for 3.x still out there, but they don’t meet the Mapping protocol or its ABC.
So, how does this distinction matter?
Note that there is a nearly opposite argument for the wrapper object that someone already made that both seem a lot compelling to me: third-party types. We can’t change them overnight. And some of them might already have an attribute named view, or anything else we might come up with. Those are real negatives with the property design, in a way that “more of the code we _can_ easily change is in the Objects rather than Lib directory of CPython” isn’t.