You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
It would be useful to be able to search a list in a functional-style manner. :
# list with structured datadata= [(i, chr(i)) foriinrange(100)]
# find index of "a" in classic manner:foridx, iteminenumerate(data):
ifitem[1] =="a":
break# functional-style search:idx=data.index("a", key=lambdai: i[1])
This idea came up in the discussion to pr #112498, in the context of searching for objects in a heap.
I'm neutral on this.
Seems that this really solves some use cases and this doesn't break any backward compatibility, so it's probably ok to have this feature :)
The example is not very motivating since the answer is data.index((ord('a'), 'a')) which is just ord('a'). A better example would a list of tuples of a character and a random number, so that the exact tuple being searched for is unknown. Can you find more use cases in the stdlib? (Perhaps search for breaks.)
Other types that are designed to mimic list or to satisfy the Sequence ABC would not be compatible with list any more if list changed the signature of its index method.
There already is an indexOf function that can be used to do this:
In [9]: fromoperatorimportindexOfIn [10]: data= [(i, chr(i)) foriinrange(100)]
In [11]: indexOf(map(lambdac: c[1] =='a', data), True)
Out[11]: 97
Using indexOf and map like this already works with any sequence (in fact any iterable).
If this is a common enough operation that using indexOf and map is too verbose then a function could be added:
In [12]: defindex_key(iterable, key):
...: """Return first item x in iterable such that key(x) is True"""
...: returnindexOf(map(key, iterable), True)
...:
In [14]: index_key(data, lambdac: c[1] =='a')
Out[14]: 97
If it is not worth adding a function for this that could work for any iterable then it is definitely not worth changing the interface of basic concrete types like list or the Sequence ABC.
Reacted by Shantanu, sobolevn, Kirill Podoprigora and Petr Viktorin
Sure, we could modify indexOf or add another one to operators since that one has a weird non-pep8 name. That would complement this idea nicely.
But the motivation here is specifically in the context of heaps which are based on concrete lists and where performance is important. The use of key functions is prevalent in the lib when searching for things, for example in the bisect module.
Regarding ABCs: list.index() has optional start and stop positional-only arguments and thus already has a different signature from Sequence.index(). Adding optional functionality does not break inheritance, any more than adding methods or overloading does.
Anyway, this is convenience/performance feature only, such as many features are, and maybe should be considered only in the context of #112498.
Fair enough. I didn't realize that indexOf had a c implementation, essentially PySequence_Index(). A PySequence_IndexKey() can be trivially added and the indexOf modified accordingly. Similarly, countOf could get the same enhancement, PySequence_CountKey().
Is everyone happy with indexOf and countOf?, btw? Would this be an opportunity to rename them (index_of) and keep the old ones around as aliases? Or is there some deeper significance to their strange capitalization? After all, they don't represent real operators.
Also, I should note, list.index() does not have any .rst documentation. Probably many of the old builtins don't. But that is why I was surprised when I stumbled upon its start and stop arguments.
It does, just not in the section specific to lists but in "Common Sequence Operations" of Sequence Types — list, tuple, range. Which the lists section also references: "Lists implement all of the common and mutable sequence operations. Lists also provide the following additional method:"
One more thing: can you please also provide some prototype of the type-hint that this new parameter should have? This would be very helpful for us in typeshed.
It does, just not in the section specific to lists but in "Common Sequence Operations" of [Sequence Types — list, tuple, range]
I see. It also mentions that "start" and "stop" arguments are not supported by all implementations. I only saw it for list, maybe it exists for tuple too. But anyway, it seems that modifying or adding an argument like this to a single implementation goes a bit against the grain. I guess enhancing indexOf with would work, but once we start using functional operators, there is perhaps no need create a special case for somethign which can be done with map.
One more thing: can you please also provide some prototype of the type-hint that this new parameter should have? This would be very helpful for us in typeshed.
Type annotations is only my third language, but it is something like:
Yup, messy. The type of the first argument changes depending on the presence of a key argument. Again, does not speak in the favour of this proposal :(
Actually, I guess the above is incorrect. In the end, a comparison is performed, PyObject_RichCompareBool(item, value), which can invoke the comparison operators on the list values. value isn't restricted to T:
It is interesting to note that the left side in the comparson is "item" and not "value" which makes it hard to create special comparison object to search for. the magic methods (__eq__ et al) are invoked for the list items, not the value. An oversight, maybe? (this is consistent with what PySequence_Index() does, sequence item is on the left of the comparison.)
I'd be happy with adding key for those, yes. One can use a map or generator expression, but that's not as obvious.
Changing list/sequence API is, of course, a much bigger deal -- IMO that would need discussion on Discourse not just on an issue here.
Note that operator has both a C implementation and a Python one, as per PEP-399.
Would this be an opportunity to rename them (index_of) and keep the old ones around as aliases?
That should be a separate change.
But it's OK to let them be; PEP 8 specifically says:
Some other good reasons to ignore a particular guideline: [...]
Because the code in question predates the introduction of the guideline and there is no other reason to be modifying that code.
I've been persuaded that changing the list.index() method is not a good idea. I guess I should close this. adding key to indexOf might be much simpler. I guess I should close this?
Activity
grigoriev-semyon commentedon Jan 6, 2024
+1
I think it would be useful
Eclips4 commentedon Jan 6, 2024
I'm neutral on this.
Seems that this really solves some use cases and this doesn't break any backward compatibility, so it's probably ok to have this feature :)
terryjreedy commentedon Jan 6, 2024
The example is not very motivating since the answer is
data.index((ord('a'), 'a'))which is justord('a'). A better example would a list of tuples of a character and a random number, so that the exact tuple being searched for is unknown. Can you find more use cases in the stdlib? (Perhaps search forbreaks.)kristjanvalur commentedon Jan 6, 2024
It was not supposed to be motivating but illustrate how to find an object without exact equivalence.
The original motivation is in the referenced PR where a search is performed for a dataclass instance based on an attribute, in the functional style.
Since adding this to the c implementation is trivial, it seems preferable compared to a convoluted approach using 'indexOf' and 'map'
It is equivalent to
data.index(value) if key is none else indexOf(map(key, data), value), which does the job but is needlessly complicated.oscarbenjamin commentedon Jan 6, 2024
Changing the interface of basic types like list and tuple is problematic especially when that interface is specified in an ABC:
https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes
Other types that are designed to mimic list or to satisfy the Sequence ABC would not be compatible with list any more if list changed the signature of its index method.
There already is an
indexOffunction that can be used to do this:Using
indexOfandmaplike this already works with any sequence (in fact any iterable).If this is a common enough operation that using
indexOfandmapis too verbose then a function could be added:If it is not worth adding a function for this that could work for any iterable then it is definitely not worth changing the interface of basic concrete types like list or the Sequence ABC.
kristjanvalur commentedon Jan 7, 2024
Sure, we could modify
indexOfor add another one tooperatorssince that one has a weird non-pep8 name. That would complement this idea nicely.But the motivation here is specifically in the context of heaps which are based on concrete lists and where performance is important. The use of
keyfunctions is prevalent in the lib when searching for things, for example in thebisectmodule.Regarding ABCs:
list.index()has optionalstartandstoppositional-only arguments and thus already has a different signature fromSequence.index(). Adding optional functionality does not break inheritance, any more than adding methods or overloading does.Anyway, this is convenience/performance feature only, such as many features are, and maybe should be considered only in the context of #112498.
oscarbenjamin commentedon Jan 7, 2024
Then it needs a performance justification:
There is not much speed difference between
list.indexandindexOf. Throw in a key function and that speed difference will be less significant:A list method is significantly less general than using
indexOfor making a function that can work with any iterable.In the first instance it would be better to see if
indexOfcan be optimised in any simple way rather than changing the interface oflist.index.kristjanvalur commentedon Jan 7, 2024
Fair enough. I didn't realize that
indexOfhad a c implementation, essentiallyPySequence_Index(). APySequence_IndexKey()can be trivially added and theindexOfmodified accordingly. Similarly,countOfcould get the same enhancement,PySequence_CountKey().Is everyone happy with
indexOfandcountOf?, btw? Would this be an opportunity to rename them (index_of) and keep the old ones around as aliases? Or is there some deeper significance to their strange capitalization? After all, they don't represent real operators.kristjanvalur commentedon Jan 7, 2024
Also, I should note,
list.index()does not have any .rst documentation. Probably many of the old builtins don't. But that is why I was surprised when I stumbled upon itsstartandstoparguments.pochmann3 commentedon Jan 7, 2024
It does, just not in the section specific to lists but in "Common Sequence Operations" of Sequence Types — list, tuple, range. Which the lists section also references: "Lists implement all of the common and mutable sequence operations. Lists also provide the following additional method:"
sobolevn commentedon Jan 7, 2024
One more thing: can you please also provide some prototype of the type-hint that this new parameter should have? This would be very helpful for us in
typeshed.Right now this seems rather hard in my opinion :(
kristjanvalur commentedon Jan 8, 2024
I see. It also mentions that "start" and "stop" arguments are not supported by all implementations. I only saw it for list, maybe it exists for tuple too. But anyway, it seems that modifying or adding an argument like this to a single implementation goes a bit against the grain. I guess enhancing
indexOfwith would work, but once we start using functional operators, there is perhaps no need create a special case for somethign which can be done withmap.A user can simply define
I guess we can drop this idea, then. Still, good talk.
kristjanvalur commentedon Jan 8, 2024
Type annotations is only my third language, but it is something like:
Yup, messy. The type of the first argument changes depending on the presence of a key argument. Again, does not speak in the favour of this proposal :(
kristjanvalur commentedon Jan 9, 2024
Actually, I guess the above is incorrect. In the end, a comparison is performed, PyObject_RichCompareBool(item, value), which can invoke the comparison operators on the list values.
valueisn't restricted toT:It is interesting to note that the left side in the comparson is "item" and not "value" which makes it hard to create special comparison object to search for. the magic methods (
__eq__et al) are invoked for the list items, not the value. An oversight, maybe? (this is consistent with whatPySequence_Index()does, sequence item is on the left of the comparison.)encukou commentedon Mar 13, 2024
I'd be happy with adding
keyfor those, yes. One can use amapor generator expression, but that's not as obvious.Changing list/sequence API is, of course, a much bigger deal -- IMO that would need discussion on Discourse not just on an issue here.
Note that
operatorhas both a C implementation and a Python one, as per PEP-399.That should be a separate change.
But it's OK to let them be; PEP 8 specifically says:
kristjanvalur commentedon Mar 13, 2024
I've been persuaded that changing the list.index() method is not a good idea. I guess I should close this. adding key to indexOf might be much simpler. I guess I should close this?
encukou commentedon Mar 13, 2024
Yes, I guess it's best to close this issue and open a new one; reusing it for indexOf could be confusing.