[Python-Dev] Indexing builtin sequences with objects which supply __int__

Alex Martelli aleax@aleax.it
Fri, 21 Jun 2002 10:10:03 +0200


On Friday 21 June 2002 12:19 am, Todd Miller wrote:
	...
> >I'm concerned that this will also make floats acceptable as indices
> >(since they have an __int__ method) and this would cause atrocities
> >like
> >
> >print "hello"[3.5]
	...
> That makes sense.    What if we specifically excluded Float objects from
> the conversion?   Are there any types that need to be excluded?    If

"Any type that's float-like", and that's a very hard set to pin down.

Consider a user-written class that implements (e.g.) a number in decimal
form (maybe BCD), carefully crafted to "look&feel just like float" except for
its specifics (such as different rounding behavior).  How would you tell that
this class is NOT acceptable as a sequence index even though it has an
__int__ method while another class with an __int__ method IS OK?

It seems to me that one solution would be to add an attribute that is to
be exposed by types / classes that WANT to be usable as indices in this
way.  If, say, the object exposes an attribute _usable_as_sequence_index, then
the indexing code could proceed, otherwise, TypeError.

It's quite sad that a lot of ad-hoc approaches such as this one have to be
devised in each and every similar case, when PEP 246, gathering dust in
the PEP repository, offers such a simple, elegant architecture for them all.

Basically, PEP 246 lets you ask a "central mechanism", given an object X and
a "protocol" Y, to yield a Z (where Z is X if feasible, but in many cases 
might be a "version of X which is Y-fied without loss of information") such
that Z is "X or a version of X that satisfies protocol Y".  "Adaptation" is 
the name commonly used for this approach (also in PEP 246).  When X can't be 
adapted to Y, an exception gets raised.

Here, indexing code could ask for an adaptation of X to the "sequence index
protocol" and get either "a version of X usable as sequence index" or an
exception.  "A protocol" is normally a type or class, and "Z satisfies 
protocol Y" may then be roughly equated to "Z is an instance of Y", but the
concept is more general.  If Python had a formal concept of 'interface', a
protocol might also be an interface -- this is apparently what's holding up
PEP 246, waiting for such 'interfaces' to appear.  But "a protocol" may in
fact be any object at all and the concept of "satisfying" it is really a 
matter of convention between the code that requests adaptation and the
code that _provides_ adaptation.  The latter may live in X's type, or in the
Y protocol, or *outside of both* and get added to the "central mechanism"
dynamically -- so you get a chance to adapt two separately developed
frameworks without as much blood, sweat and tears as currently needed.
(The compile-time equivalent of this is in Haskell's "typeclass" mechanism,
but of course Python moves it to runtime instead.)


Back to your specific issue.  "An integer" is too BROAD a concept.  When
some client-code has an object X and "wants an integer equivalent of X"
it may have SEVERAL different purposes in mind.  int(X) can't guess and
so provides only ONE way -- for example, truncating the fractional part if
X is a float.  If the client-code could ask more precisely for "give me a
version of X to be used as a sequence index" it would still get back either
an int OR an exception, BUT, the int result would only be supplied if "it
was known" that X is indeed "usable without loss of information" for the
specific purpose of indexing a sequence.

The "it was known" part could reside in any one of three places:
a. the SequenceIndexing protocol could 'know' that e.g. every int X is OK
    as a sequence index, and immediately return such an X if asked for
    adaptation of it;
b. a type could 'know' its instances are OK as sequence indices, and
    supply the equivalent-for-THAT-purpose int on request;
c. a "third-party" adapter could know that, for this application, instances of
    type A are OK to use as sequence indices: the third-party adapter would
    be installed at application startup, get invoked upon such adaptation
    requests when X is an instance of type A, and provide the needed int.
See PEP 246 for one possible mechanism (at Python-level) to support
this, but the mechanism is of course fully negotiable.  The point is that we
NEED something like PEP 246 each and every time we want to perform
any task of this ilk.  Almost every time I see type-testing (as implicit in 
the idea "but do something different if X is a float", for example), I see a 
need for PEP 246 that stays unmet because PEP 246 is waiting...


Alex