On Sun, Sep 29, 2019 at 5:04 AM Ricky Teachey <ricky@teachey.org> wrote:
That's the point that I would make as well. What can you do with an
object that is only known to be Subscriptable?

def do_subscriptable_things(obj):
    if isinstance(obj, Subscriptable):
        # Now what?

I'm going to echo this one -- the OP has shown that it's awkward to know whether an arbitrary object is "subscriptable" -- but not why you'd want to do that check at all. In fact, you do not use "EAFTP" to do a type check -- the whole point is that you use it like you expect ot use it, and if you get an error, you know it can't be used that way:

So the OP's code:

except TypeError:
    subscriptable = False
except (IndexError, KeyError):
    subscriptable = True
    subscriptable = True

if subscriptable:

Is exactly NOT EAFTP -- it's LBYL, but using exception catching to do the type check.

And after this code is run, now what? you don't know anything about HOW you can subscript that object -- can you use an index, can you use a key, can you do something really wierd and arbitrary:

Maybe if you want to use/abuse it as an alternative function calling syntax-- square brackets rather than parentheses.

Like the example given a bit earlier:

class Test(object):
    def __getitem__(self, idx):
        return idx**2

py> squares = Test()
py> squares[5]

So if you think you *may* have a "callable_with_an_integer" -- the thing to do is:

    result = squares[5]
except TypeError:
    print("Opps, couldn't do that")

Though what you'd probably really want to do is have your oddball class raise appropriate exceptions is the wrong kind of "index" was passed in.

If you do want to do this, maybe a "callable_with_brackets" ABC ??? (but please don't -- there is already a way to make objects callable...)

Now that I think about it a bit more, that example *could* make sense, but only if what you are trying to do is make an indexable lazy-evaluated  infinite sequence of all the squares -- in which case, you'd want to put a bit more in there. And now that I think about it, there doesn't appear to be an ABC for something that can be indexed and iterated, but does not have a length.

However, we need to think more about what ABCs are *for* anyway -- given Python's "magic method" system and Duck Typing, you can essentially create types that have any arbitrary combination of functionality -- do we need an ABC for every one? Obviously not. When I first learned about ABCs, I thought some about what the point was -- they are NOT the same as, say, ABCs in C++ -- Python doesn't need those. I came to the conclusion that the point of ABCs was to allow users to write code that expects standard built-ins that will also work with custom types that match that interface. 

But it's only useful for standard Base Classes -- making a custom ABC that matches the spec of a particular use case isn't very helpful -- unless various third parties are going to be using that spec, there's not point in an ABC. Modifying my point -- ABCs are useful for built ins or large projects with a wide user base that may have third parties writing extensions, plug ins, etc).

However, if a spec gets complicated, the ABC system breaks down anyway -- go look at what numpy is trying to do with "duck arrays" -- ABCs are not very helpful there.

In short: in python, __getitem__ can be [ab]used for virtually ANYTHING -- so if you have an object that isn't a mapping (KeyError), or a Sequence (IndexError), they you would need to know something more about what it is anyway in order to use it -- I can't see how simply knowing that you can toss some unknown type(s) of objects into the square brackets is useful.

So back to the OP: 

What do you want to be able to do here? What types do you want to be able to support, and how do you want to use them?


Christopher Barker, PhD

Python Language Consulting
  - Teaching
  - Scientific Software Development
  - Desktop GUI and Web Development
  - wxPython, numpy, scipy, Cython