• isinstance()

    From dn@21:1/5 to All on Thu Aug 3 10:14:56 2023
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Jon Ribbens@21:1/5 to PythonList@DancesWithMice.info on Wed Aug 2 23:38:10 2023
    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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Cameron Simpson@21:1/5 to PythonList@DancesWithMice.info on Thu Aug 3 11:14:35 2023
    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>

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Stefan Ram@21:1/5 to PythonList@DancesWithMice.info on Thu Aug 3 13:12:59 2023
    dn <PythonList@DancesWithMice.info> writes:
    Can you please explain why a multi-part second-argument must be a tuple
    and not any other form of collection-type?

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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From dn@21:1/5 to Jon Ribbens via Python-list on Sat Aug 5 11:07:19 2023
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From dn@21:1/5 to Chris Angelico via Python-list on Sat Aug 5 11:35:03 2023
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Chris Angelico@21:1/5 to dn via Python-list on Sat Aug 5 09:39:16 2023
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Chris Angelico@21:1/5 to dn via Python-list on Sat Aug 5 09:18:34 2023
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Grant Edwards@21:1/5 to Chris Angelico via Python-list on Fri Aug 4 19:37:37 2023
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)