Mailing List Archive

isinstance()
Can you please explain why a multi-part second-argument must be a tuple
and not any other form of collection-type?


The signature is: isinstance(object, classinfo)
leading to "classinfo" of:

1/ a single class/type, eg int
2/ a tuple of same, eg ( int, str, )
3/ a union type, eg int | str (v3.10+)

A question was asked about this at last night's PUG-meeting. The person
was using the union option, but from Python v3.8. Sorting that out, he
replaced the union with a list. No-go! Before correcting to a tuple...

Why does the second argument need to be a tuple, given that any series
of non-keyword arguments is a tuple; and that such a tuple will still
need to be delimited by parentheses. In other words, other forms of
delimiter/collection, eg list and set; being a series of
elements/members would seem no different.

Yes, the underlying C operation appears to only accept a single argument
(am not C-soned, mea culpa!).

There is some discussion about hashability, but isn't it coincidental
rather than constructive? (again, maybe I've not understood...)

Enquiring minds want to know...


Web.Refs:
https://docs.python.org/3/library/functions.html?highlight=isinstance#isinstance
https://docs.python.org/3/c-api/object.html?highlight=isinstance#c.PyObject_IsInstance
https://docs.python.org/3/reference/datamodel.html?highlight=isinstance

--
Regards,
=dn
--
https://mail.python.org/mailman/listinfo/python-list
Re: isinstance() [ In reply to ]
On 2023-08-02, dn <PythonList@DancesWithMice.info> wrote:
> Can you please explain why a multi-part second-argument must be a tuple
> and not any other form of collection-type?

The following comment may hold a clue:

if (PyTuple_Check(cls)) {
/* Not a general sequence -- that opens up the road to
recursion and stack overflow. */

https://github.com/python/cpython/blob/main/Objects/abstract.c#L2684

Plus an almost total lack of demand for change I should think.
--
https://mail.python.org/mailman/listinfo/python-list
Re: isinstance() [ In reply to ]
On 03Aug2023 10:14, dn <PythonList@DancesWithMice.info> wrote:
>Can you please explain why a multi-part second-argument must be a
>tuple and not any other form of collection-type?
>
>The signature is: isinstance(object, classinfo)
>leading to "classinfo" of:
>
>1/ a single class/type, eg int
>2/ a tuple of same, eg ( int, str, )
>3/ a union type, eg int | str (v3.10+)

help(isinstance) (in 3.8) says class_or_tuple.

I would speculate that requiring a tuple has two advantages:
- it may lend itself to promotion to a set for comparison with the MRO
of object (IIRC the "foo in (a,b,c)" is optimised this way also), and
this in turn may hint at the Hashable requirement
- it is _frugal_ in what we expect there, leaving open a future meaning
for something else in that place (for example, isinstance well
predates type annotations, and a looser kind of argument might have
precluded this new usage)

There's similar language in this try/except documentation:
file:///Users/cameron/var/doc/python/3.8.0/reference/compound_stmts.html#index-10

For an except clause with an expression, that expression is
evaluated, and the clause matches the exception if the resulting
object is “compatible” with the exception. An object is compatible
with an exception if it is the class or a base class of the
exception object or a tuple containing an item compatible with the
exception.

To my mind the tuple requirement lends itself to a distinct syntax (the
brackets) and frugal use of the meaning of what values can occur there.

Cheers,
Cameron Simpson <cs@cskk.id.au>
--
https://mail.python.org/mailman/listinfo/python-list
Re: isinstance() [ In reply to ]
On 03/08/2023 11.38, Jon Ribbens via Python-list wrote:
> On 2023-08-02, dn <PythonList@DancesWithMice.info> wrote:
>> Can you please explain why a multi-part second-argument must be a tuple
>> and not any other form of collection-type?
>
> The following comment may hold a clue:
>
> if (PyTuple_Check(cls)) {
> /* Not a general sequence -- that opens up the road to
> recursion and stack overflow. */
>
> https://github.com/python/cpython/blob/main/Objects/abstract.c#L2684
>
> Plus an almost total lack of demand for change I should think.

Thanks for the reference!


Am not proposing a change (have learned 'the rule' and accepted
life-as-it-is), but was curious about the restriction: why not any
(reasonable sequence)?


Such pondering continues in my subversive mind, given such thoughts as:

- "we're all adults here"
(eg frequent justification when arguing about Python not having "private
attributes" per-se).
So, if we introduce a complexity, on our own heads be it!
(hardly likely given that all we are likely to attempt is the
application of a simple and short 'list' of [un]acceptable types).
NB understood that the quoted-code is applied in many and other 'scalar
or tuple' situations.

- Python happily enables recursion, which "opens up the road to
recursion and stack overflow.".
So, why install a 'nanny' to save us from ourselves here?
Again: seems on-the-surface that such 'lists' (at the code-line level)
will be mono-dimensional 99.9% of the time.


NB having said that, the underlying mechanism *is* multi-dimensional:
"direct, indirect, or virtual) subclass thereof"
(https://docs.python.org/3/library/functions.html#isinstance)

Further: the 'rules' say: "classinfo is a tuple of type objects (or
recursively, other such tuples)". Thus, can write:

>>> target_object = ...
>>> inner_tuple = float, complex
>>> inner_tuple
(<class 'float'>, <class 'complex'>)
>>> isinstance( target_object, ( str, inner_tuple, int, ), )
False

I can't say I've ever used such construction in-the-wild - either
defining and then using an "inner_tuple", or even nesting. YMMV!


Any thoughts?

--
Regards,
=dn
--
https://mail.python.org/mailman/listinfo/python-list
Re: isinstance() [ In reply to ]
On Sat, 5 Aug 2023 at 09:08, dn via Python-list <python-list@python.org> wrote:
>
> On 03/08/2023 11.38, Jon Ribbens via Python-list wrote:
> > On 2023-08-02, dn <PythonList@DancesWithMice.info> wrote:
> >> Can you please explain why a multi-part second-argument must be a tuple
> >> and not any other form of collection-type?
> >
> > The following comment may hold a clue:
> >
> > if (PyTuple_Check(cls)) {
> > /* Not a general sequence -- that opens up the road to
> > recursion and stack overflow. */
> >
> > https://github.com/python/cpython/blob/main/Objects/abstract.c#L2684
> >
> > Plus an almost total lack of demand for change I should think.
>
> Thanks for the reference!
>
>
> Am not proposing a change (have learned 'the rule' and accepted
> life-as-it-is), but was curious about the restriction: why not any
> (reasonable sequence)?

There are quite a few places where the only option is a tuple.

>>> "spam".startswith(["sp", "h"])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: startswith first arg must be str or a tuple of str, not list
>>> try: 1/0
... except [ValueError, IndexError]: pass
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "<stdin>", line 2, in <module>
TypeError: catching classes that do not inherit from BaseException is
not allowed

It simplifies a lot of checks. Either it's a tuple or it's a single
thing. A *lot* of values are iterable, but only tuples are tuples.

As the C comment notes, this also means you don't have to worry about
recursion; otherwise, even core language features like exception
handling would have to iterate over a thing while ensuring that they
don't get stuck in self-referential objects. (Incidentally, it seems
that exception handling doesn't bother with recursion *at all*, and
won't catch "(a, (b, c))" - but even if recursion is handled, it can't
go infinite, short of some serious messing around in ctypes or the C
API.)

ChrisA
--
https://mail.python.org/mailman/listinfo/python-list
Re: isinstance() [ In reply to ]
On 05/08/2023 11.18, Chris Angelico via Python-list wrote:
> On Sat, 5 Aug 2023 at 09:08, dn via Python-list <python-list@python.org> wrote:
>>
>> On 03/08/2023 11.38, Jon Ribbens via Python-list wrote:
>>> On 2023-08-02, dn <PythonList@DancesWithMice.info> wrote:
>>>> Can you please explain why a multi-part second-argument must be a tuple
>>>> and not any other form of collection-type?
>>>
>>> The following comment may hold a clue:
>>>
>>> if (PyTuple_Check(cls)) {
>>> /* Not a general sequence -- that opens up the road to
>>> recursion and stack overflow. */
>>>
>>> https://github.com/python/cpython/blob/main/Objects/abstract.c#L2684
>>>
>>> Plus an almost total lack of demand for change I should think.
>>
>> Thanks for the reference!
>>
>>
>> Am not proposing a change (have learned 'the rule' and accepted
>> life-as-it-is), but was curious about the restriction: why not any
>> (reasonable sequence)?
>
> There are quite a few places where the only option is a tuple.
>
>>>> "spam".startswith(["sp", "h"])
> Traceback (most recent call last):
> File "<stdin>", line 1, in <module>
> TypeError: startswith first arg must be str or a tuple of str, not list
>>>> try: 1/0
> ... except [ValueError, IndexError]: pass
> ...
> Traceback (most recent call last):
> File "<stdin>", line 1, in <module>
> ZeroDivisionError: division by zero
>
> During handling of the above exception, another exception occurred:
>
> Traceback (most recent call last):
> File "<stdin>", line 2, in <module>
> TypeError: catching classes that do not inherit from BaseException is
> not allowed
>
> It simplifies a lot of checks. Either it's a tuple or it's a single
> thing. A *lot* of values are iterable, but only tuples are tuples.
>
> As the C comment notes, this also means you don't have to worry about
> recursion; otherwise, even core language features like exception
> handling would have to iterate over a thing while ensuring that they
> don't get stuck in self-referential objects. (Incidentally, it seems
> that exception handling doesn't bother with recursion *at all*, and
> won't catch "(a, (b, c))" - but even if recursion is handled, it can't
> go infinite, short of some serious messing around in ctypes or the C
> API.)
Yes. Thanks Chris!

The idea that such 'lists' be immutable (even, hashable), and therefore
a tuple, makes a certain amount of sense, and @Cameron mentioned
'frugality'.


My limitation is thinking only at the Python level (which as @Jon
pointed-out, is only part of the story).

Faced with a situation where an argument may be a scalar-value or an
iterable, I'll presume the latter, eg throw it straight into a for-loop.
If that fails (because the argument is a scalar), use try-except to
re-route the logic. Alternately, in an OOP situation, utilise
polymorphism so that the 'scalar' and 'iterable' instances both include
the appropriate method[name]. Accordingly, as long as the argument is an
iterable (includes an __iter__() method), the actual type/class is
more-or-less irrelevant.


However, as observed earlier - and with these additions, having learned
the 'rule', ie use a tuple; the brain is still pondering, but have
shrugged-shoulders and carried-on regardless...

--
--
Regards,
=dn
--
https://mail.python.org/mailman/listinfo/python-list
Re: isinstance() [ In reply to ]
On Sat, 5 Aug 2023 at 09:36, dn via Python-list <python-list@python.org> wrote:
> Faced with a situation where an argument may be a scalar-value or an
> iterable, I'll presume the latter, eg throw it straight into a for-loop.
> If that fails (because the argument is a scalar), use try-except to
> re-route the logic.

That's great as long as you aren't expecting to handle strings. The
string "spam" is sometimes equivalent to the list ["s", "p", "a", "m"]
and sometimes not.

ChrisA
--
https://mail.python.org/mailman/listinfo/python-list
Re: isinstance() [ In reply to ]
On 2023-08-04, Chris Angelico via Python-list <python-list@python.org> wrote:
> On Sat, 5 Aug 2023 at 09:36, dn via Python-list <python-list@python.org> wrote:
>
>> Faced with a situation where an argument may be a scalar-value or an
>> iterable, I'll presume the latter, eg throw it straight into a for-loop.
>> If that fails (because the argument is a scalar), use try-except to
>> re-route the logic.
>
> That's great as long as you aren't expecting to handle strings.

If you do that, you're obviously not expecting to handle strings. The
problem happens when you're not expecting to handle strings, and you
get passed one anyway.

It's like the Spanish Inquisition...

> The string "spam" is sometimes equivalent to the list ["s", "p",
> "a", "m"] and sometimes not.

And b"ABCD" is sometimes equivalent to the list [65,66,67,68] and
sometimes not.

Been there, fell in that hole.

More than a few times. :/

Famous Last Words: "I wasn't expecting to handle strings -- but I
should have been..."

--
Grant



--
https://mail.python.org/mailman/listinfo/python-list