On Sun, Sep 29, 2019 at 09:05:19AM -0700, Christopher Barker wrote:
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:
That applies to many interface checks. Both the built-in callable() and the Callable ABC don't tell you what arguments the object accepts, or what it does; the Sequence ABC check doesn't tell you what valid indexes are accepted. People may assume that valid indexes go from 0 through len-1 but we can make sequences that work with 1-based sequences, or like Fortran, with any bounds at all.
Including virtual "sequences" that accept indexes from -infinity to infinity, with no length.
"Consenting adults" applies. The ABC checks only test for the presence of an interface, they don't necessarily make guarantees about what the interface will do.
So if you think you *may* have a "callable_with_an_integer" -- the thing to do is:
try: result = squares except TypeError: print("Opps, couldn't do that")
I know that's meant in good faith, but please be careful of telling me what I should do on the basis of a wild guess of what my needs are. I've been programming in Python since 1.5 was the latest and greatest, and I think I'm capable of judging when I want to use a try...except EAFP check rather than a LBYL check for an interface.
And even if I personally am not, this is not about *me*. The community as a whole is, which is why Python supports ABCs and isinstance. If you want to argue against that, that ship has not only sailed but it's already docked at the destination and the passengers disembarked :-)
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 --
Well spotted :-)
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.
Don't forget that you don't need __iter__ or length to be iterable:
py> class Example: ... def __getitem__(self, idx): ... if idx in range(0, 4): return "spam"*idx ... raise IndexError ... py> list(Example()) ['', 'spam', 'spamspam', 'spamspamspam']
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.
I don't think it is so obvious.
Clearly we can't have an ABC for every imaginable combination of magic methods. There would be hundreds. What would we name them?
We should (and do!) have ABCs for the most common combinations, like Sequence, MutableMapping, Container etc.
And we should (so I am arguing) have ABCs for the underlying building blocks, the individual magic methods themselves. Developers can then combine those building blocks to make their own application-specific combinations.
But without those composable building blocks, we're reduced to testing for dunders by hand, which is problematic because:
- people get it wrong, using ``hasattr(instance, dunder)``
- ``getattr(type(instance), dunder, None) is not None`` is apparently still wrong, since that's not what collections.abc does, and presumably it has a good reason for not doing so.
- being part of the implementation, rather than public interface, in principle dunders may change in the future.
Obvious well-known dunders like __len__ will probably never go away. But the obvious and well-known dunder __cmp__ went away, and maybe one day the Sized protocol will change too. If you want to future-proof your code, you should test for the Sized interface rather than directly testing for the __len__ magic method.