Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.python > #105521 > unrolled thread
| Started by | "Marco S." <mail.python.org@marco.sulla.e4ward.com> |
|---|---|
| First post | 2016-03-22 20:54 +0100 |
| Last post | 2016-03-26 10:29 +0000 |
| Articles | 6 — 4 participants |
Back to article view | Back to comp.lang.python
Suggestion: make sequence and map interfaces more similar "Marco S." <mail.python.org@marco.sulla.e4ward.com> - 2016-03-22 20:54 +0100
Re: Suggestion: make sequence and map interfaces more similar Steven D'Aprano <steve@pearwood.info> - 2016-03-23 22:26 +1100
Re: Suggestion: make sequence and map interfaces more similar Marco Sulla <marcosullaroma@gmail.com> - 2016-03-26 01:18 +0100
Re: Suggestion: make sequence and map interfaces more similar Steven D'Aprano <steve@pearwood.info> - 2016-03-26 22:41 +1100
Re: Suggestion: make sequence and map interfaces more similar Marco Sulla <marcosullaroma@gmail.com> - 2016-03-26 01:35 +0100
Re: Suggestion: make sequence and map interfaces more similar Mark Lawrence <breamoreboy@yahoo.co.uk> - 2016-03-26 10:29 +0000
| From | "Marco S." <mail.python.org@marco.sulla.e4ward.com> |
|---|---|
| Date | 2016-03-22 20:54 +0100 |
| Subject | Suggestion: make sequence and map interfaces more similar |
| Message-ID | <mailman.34.1458721359.2244.python-list@python.org> |
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, 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. In one word, it will facilitate duck typing. 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. 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. This way we will have a simpler way to let people to use sequences or maps indifferently, and let the code untouched.
[toc] | [next] | [standalone]
| From | Steven D'Aprano <steve@pearwood.info> |
|---|---|
| Date | 2016-03-23 22:26 +1100 |
| Message-ID | <56f27d76$0$1600$c3e8da3$5496439d@news.astraweb.com> |
| In reply to | #105521 |
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
[toc] | [prev] | [next] | [standalone]
| From | Marco Sulla <marcosullaroma@gmail.com> |
|---|---|
| Date | 2016-03-26 01:18 +0100 |
| Message-ID | <mailman.24.1458986375.28225.python-list@python.org> |
| In reply to | #105542 |
> 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@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
>
[toc] | [prev] | [next] | [standalone]
| From | Steven D'Aprano <steve@pearwood.info> |
|---|---|
| Date | 2016-03-26 22:41 +1100 |
| Message-ID | <56f67588$0$1591$c3e8da3$5496439d@news.astraweb.com> |
| In reply to | #105734 |
On Sat, 26 Mar 2016 11:18 am, Marco Sulla wrote:
>> 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.
Marco, it is considered polite to give attribution to the person you are
quoting. In this case, that would be me. But it is still polite to say so.
>> 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.
Er... okay. What's that got to do with what we are discussing?
The point you might have missed is that treating lists as if they were
mappings violates at least one critical property of mappings: that the
relationship between keys and values are stable.
Consider a simple address book, mapping between (let's say) a person's name
and their address:
address_book = {
'Fred': '123 Smiths Road',
'Ishmael': '37a Penny Lane',
'Jane': '2019 Short Street',
'Sam': '48 Main Road',
}
Now we add a new item:
address_book['Annette'] = '97 The Boulevard'
and then retrieve Jane's address. With a real mapping, like dict, then we
know what will happen:
print(address_book['Jane'])
=> prints '2019 Short Street'
Adding or deleting entries in a mapping does not affect the remaining
key:value pairs. It is absolutely critical and fundamental to mappings that
the key:value pairs are stable.
But suppose we had a mapping which lacked that stability:
print(address_book['Jane']) # fundamentally broken mapping
=> prints '37a Penny Lane' # the address has mysteriously changed
Do you think this would be reasonable behaviour for a mapping? I hope you
would say "No".
Now, let's imagine that we treat lists as if they were mappings, where the
index is the key:
address_book = [
'123 Smiths Road', # key = 0, Fred
'37a Penny Lane', # key = 1, Ishmael
'2019 Short Street', # key = 2, Jane
'48 Main Road', # key = 3, Sam
]
Now we add a new item:
address_book.insert(0, '97 The Boulevard') # Annette
and retrieve Jane's address, using Jane's "key":
print(address_book[2])
=> prints '37a Penny Lane'
This is exactly the behaviour which (I hope) we have agreed would be broken
for a dict or other mapping. We do not have stability of "key":value pairs.
This violates the property that mapping key:value pairs should be stable.
Inserting new entries into a mapping, or deleting them, shouldn't affect
the remaining entries. But with a sequence, it can effect the relationship
between index and item.
That means that indexes are not keys, and sequences are not mappings.
For a sequence, this does not matter. There is no promise that items will
always be found at the same index you put them in. Many operations on
sequences will move items around:
sort
reverse
pop
insert
delete an item or slice
some slice assignments
shuffle
and more.
--
Steven
[toc] | [prev] | [next] | [standalone]
| From | Marco Sulla <marcosullaroma@gmail.com> |
|---|---|
| Date | 2016-03-26 01:35 +0100 |
| Message-ID | <mailman.25.1458986375.28225.python-list@python.org> |
| In reply to | #105542 |
Peter Otten 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, 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. > It will also break existing uses of duck typing. Yes, I suppose it's too late to introduce these methods to sequences. I'd like to see vdict implemented anyway. It can't break old code and add a map type that has a common interface with sequences.
[toc] | [prev] | [next] | [standalone]
| From | Mark Lawrence <breamoreboy@yahoo.co.uk> |
|---|---|
| Date | 2016-03-26 10:29 +0000 |
| Message-ID | <mailman.29.1458988205.28225.python-list@python.org> |
| In reply to | #105542 |
On 26/03/2016 00:35, Marco Sulla wrote: > > I'd like to see vdict implemented anyway. It can't break old code and add a > map type that has a common interface with sequences. > I cannot see this happening unless you provide a patch on the bug tracker. However I suspect you can get the same thing by subclassing dict. Why don't you try it and let us know how you get on? -- My fellow Pythonistas, ask not what our language can do for you, ask what you can do for our language. Mark Lawrence
[toc] | [prev] | [standalone]
Back to top | Article view | comp.lang.python
csiph-web