Eli Korvigo
Eli Korvigo

Reputation: 10483

Python: recursive isinstance checking

How can one check a complete type signature of a nested abstract class? In this example

In [4]: from typing import Sequence

In [5]: IntSeq = Sequence[int]

In [6]: isinstance([1], IntSeq)
Out[6]: True

In [7]: isinstance([1.0], IntSeq)
Out[7]: True

I want the last isinstance call to actually return False, while it only checks that the argument is a Sequence. I thought about recursively checking the types, but IntSeq has no public attributes that store the nested type(s):

In [8]: dir(IntSeq)
Out[8]: 
['__abstractmethods__',
 '__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__extra__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__le__',
 '__len__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__origin__',
 '__parameters__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__slots__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 '_abc_cache',
 '_abc_negative_cache',
 '_abc_negative_cache_version',
 '_abc_registry']

So it doesn't seem to be straightforward to get nested types. I can't find relevant information in the docs.

P.S. I need this for a multiple dispatch implementation.

Update

Thanks to the feedback from Alexander Huszagh and Blender we now know that abstract classes in Python 3.5 (might) have two attributes that store the nested types: __parameters__ and __args__. The former is there under both Linux (Ubuntu) and Darwin (OS X), though it is empty in case of Linux. The later is only available under Linux and stores the types like __parameters__ does under OS X. This implementation details add up to the confusion.

Upvotes: 4

Views: 1009

Answers (1)

Dimitris Fasarakis Hilliard
Dimitris Fasarakis Hilliard

Reputation: 160357

I see you're trying to implement something using a module that is still provisional; you're bound to encounter a changing interface if you do this.

Blender noticed that the __parameters__ argument holds the parameters to the type; this was true until, I believe 3.5.1. In my git clone of the most recent version of Python (3.6.0a4+) __parameters__ again holds an empty tuple, __args__ holds the argument and __origin__ is the first entry in its __bases__ attribute:

>>> intSeq = typing.Sequence[int]
>>> intSeq.__args__
(<class 'int'>,)
>>> intSeq.__parameters__
()
>>> intSeq.__origin__
typing.Sequence<+T_co>

Since 3.6 is when typing will, from what I understand from PEP 411, leave provisional and enter a stable state, this is the version you should be working with to implement your functionality.

Upvotes: 2

Related Questions