Suggestion: make sequence and map interfaces more similar

Marco Sulla marcosullaroma at gmail.com
Fri Mar 25 20:18:47 EDT 2016


> That's a very superficial similarity: a list ['a', 'b', 'x', 'y'] is
> something like a mapping {0: 'a', 1: 'b', 2: 'x', 3: 'y'}. Seems logical,
> since in both cases we write collection[2] and get 'x' back.
>
> But think about it a bit more, and you will see that the behaviour is in
> fact *very different*. For example:
>
> the_list = ['a', 'b', 'x', 'y']
> # the_list is equivalent to {0: 'a', 1: 'b', 2: 'x', 3: 'y'}
> the_list.insert(0, 'z')
> # the_list is now equivalent to {0: 'z', 1: 'a', 2: 'b', 3: 'x', 4: 'y'}
>
> Every existing "key:value" pair has changed! What sort of mapping operates
> like that?

It's like you said: "Animals with four legs can usually run, eat etc. But
birds can fly! What sort of animal flies?"
Well. birds.


You can easily extend dict and create a new class that imitate list, tuple
or str without any problem. Sequences are an extension of maps.


> Have you ever actually wanted to use sequences or maps indifferently? To
do
> what?

Not my code, but this is an example:
https://github.com/thieman/dagobah/blob/master/dagobah/daemon/daemon.py
def replace_nones(dict_or_list), line 27

On 23 March 2016 at 12:26, Steven D'Aprano <steve at pearwood.info> wrote:

> On Wed, 23 Mar 2016 06:54 am, Marco S. wrote:
>
> > I noticed that the sequence types does not have these methods that the
> map
> > types  has: get(), items(), keys(), values().
> >
> > It could seem useless to have them for sequences,
>
> That's putting it mildly.
>
> > but I think it will ease
> > the creation of functions and methods that allow you to input a generic
> > iterable as parameter, but needs to use one of these methods in case the
> > parameter is a map.
>
> Perhaps it would. But countering that is the disadvantage that you're
> adding
> methods to sequences that have no sensible meaning for a sequence.
>
> Strings and floats are quite different things, but occasionally it makes
> sense to write a function that accepts either a string or a float. Perhaps
> we could introduce methods to strings and floats to blur the difference,
> to "ease the creation of functions and methods that allow you to input a
> generic scalar (string, float, int) as parameter..."?
>
> Some of these methods would be easy:
>
> class float:
>     def upper(self):
>         return self
>
>
> Some a bit more work:
>
>     def find(self, substring, start, end):
>         s = str(self)
>         return s.find(substring, start, end)
>
> but some perplex me. What would (27.25).expand_tabs() do?
>
>
> Of course this is silly. Even Perl and Javascript don't go this far in
> making floats and strings interchangeable. This is a good thing: if you're
> calling some_float.upper(), you've almost certainly made a programming
> error, and you don't want this nonsensical method call to silently succeed.
>
> So it is with sequences and mappings. They are *not* the same sort of
> thing,
> even though they have a few superficial similarities, and they shouldn't
> try to behave as the same thing.
>
> What should some_dict.append(None) do? The very concept is nonsense: dicts
> aren't *ordered sequences*, you can't append to a dict. Maybe you could
> give dicts a method *called* "append", but it wouldn't mean the same thing
> as list.append, and it probably wouldn't have the same signature:
>
>
> list.append(value)
>     Appends value to the end of the list.
>
> dict.append(key, value)
>     Adds a new key to the dict, with the given value.
>     Same as dict[key] = value.
>
>
> Forcing these two completely different methods to have the same name
> doesn't
> do anything useful. That's like putting a door handle on your iPhone, in
> case some day you want to treat your iPhone as a door.
>
>
> > In one word, it will facilitate duck typing.
>
> I think you have misunderstood the purpose and meaning of duck-typing.
>
> Duck-typing is not about forcing unrelated, dissimilar types to have the
> same interface just in case you want to write a function that will accept
> either type.
>
> Duck-typing is about accepting anything which "quacks like a duck". If all
> you need is something which lays an egg, you shouldn't care whether it is a
> chicken or a goose or a duck. It doesn't mean that dogs and cats should
> have a "lay_egg" method, just in case somebody wants to accept a duck or a
> dog.
>
>
> > For the same reason, I would suggest the introduction of a new map type,
> > vdict, a dict that by default iterates over values instead over keys. So
> a
> > vdict object "d" wiil have iter(d) == iter(d.values()), and should also
> > have a count() method, like sequence types.
>
> I don't really see the point in this. What sort of function will expect to
> iterate over a mapping, but not care whether it is getting keys or values?
>
> Sure, there are generic functions that will iterate over *any iterable*,
> and
> you can pass dict.keys() or dict.values(), and both will work fine. That's
> exactly what duck-typing is about.
>
> But let's imagine this hypothetical function that will accept any mapping,
> and some mappings will iterate over keys and some mappings iterate over
> values. What would you do with it?
>
> def walk(the_dict):
>     for obj in the_dict:
>         print("Key =", obj)  # That's wrong, it might be a value.
>         value = the_dict[obj]  # That's wrong too.
>         the_dict[obj] = "processed"  # Still wrong.
>
>
> There's nothing useful or interesting you can do with a mapping and
> something which might be a key, or might be a value, but you don't know
> which. You can treat them in isolation, as if they had nothing to do with a
> dict, and that's about it. But in that case, why insist on a dict?
>
> walk(the_dict.keys())
> walk(the_dict.values())
> walk(the_list)
> walk(the_iterator)
>
>
> > Indeed sequences are, in my humble opinion, a specialized case of maps,
> > when keys are numeric only, are always contiguous without gaps and start
> > from 0.
>
> That's a very superficial similarity: a list ['a', 'b', 'x', 'y'] is
> something like a mapping {0: 'a', 1: 'b', 2: 'x', 3: 'y'}. Seems logical,
> since in both cases we write collection[2] and get 'x' back.
>
> But think about it a bit more, and you will see that the behaviour is in
> fact *very different*. For example:
>
> the_list = ['a', 'b', 'x', 'y']
> # the_list is equivalent to {0: 'a', 1: 'b', 2: 'x', 3: 'y'}
> the_list.insert(0, 'z')
> # the_list is now equivalent to {0: 'z', 1: 'a', 2: 'b', 3: 'x', 4: 'y'}
>
> Every existing "key:value" pair has changed! What sort of mapping operates
> like that?
>
> del the_list[2]
> # the_list is now equivalent to {0: 'z', 1: 'a', 2: 'x', 3: 'y'}
>
> I've said to delete the "key" 2, but there it still is. "Key" 2 still
> exists, it just has a different value! And it is "key" 4 which has been
> deleted! But not the *value* attached to "key" 4, that's been moved to 3!
>
> What sort of crazy mapping behaves like this?
>
> The answer is, of course, *no* sort of mapping. Sequences are not mappings.
> The only reason we think of them as kinda-sorta like mappings is because of
> a superficial similarity between key:value and index:item. That similarity
> is real, but virtually everything else about mappings and sequences is
> different.
>
>
>
> > This way we will have a simpler way to let people to use sequences
> > or maps indifferently, and let the code untouched.
>
> Have you ever actually wanted to use sequences or maps indifferently? To do
> what?
>
> The only case I've ever seen of that is the dict constructor, and
> dict.update, which will accept either a mapping or a sequence of (key,
> value) pairs.
>
>
>
>
> --
> Steven
>
> --
> https://mail.python.org/mailman/listinfo/python-list
>



More information about the Python-list mailing list