This was inspired by the discussion surrounding a limitation in Python's type
system such that Sequence[str] and str represent the same type. Although
I don't think this effort has been successful in addressing that problem, it
is nevertheless interesting for its own sake and may prove useful.
By creating an abstract base class with both __init_subclass__ and
__subclasshook__, it's possible to inject type constraints into subclasses'
init process without the user needing to think about it beyond subclassing the
ABC, and it's possible for str to be a virtual subclass.
Users can set constraints at subclass definition time with either a type annotation or keyword argument. They may also set constraints at object initialization if the class is generic, again with a keyword argument. Finally, it's possible to not explicitly declare the constraints at all, in which case they will be set to the type of the first item in the wrapped container.
Explicit declarations enable the use of multiple types, if desired.
A function with a Constrained[str] parameter should accept a str but not a
List[str]
A function which denotes a Sequence[str] parameter will still happily take a
str. Indeed, it would likely take any Constrained[str] where the Constrained
subtype has subclassed a Sequence type.
There are only two Container types in the Python standard library which
require all of their contents be of one type: str and array.array. The
latter is specialized for numeric operations and does not allow the storage of
arbitrary types.
Constrained is a mix-in class to be used with any Container type, though
designed initially around MutableSequence. The Constrained subtype
defined here, Vec, is essentially a UserList subclass with runtime
type enforcement.
By convention, list objects are generally expected to be homogeneous.
However, neither Python itself nor any static analysis tool I'm familiar with
at time of writing will make this convention into a reliable rule. Without
Constrained, it is always possible for a list to get an unexpected object
and cause problems (albeit remotely so).
Constrained, therefore, is useful anywhere you must be absolutely sure to
have a homogeneous container. This may be in data science applications, or
perhaps an additional optimization for eg. Cython or Pypy (though I assume both
already have more robust solutions).
- The issue on python/typing: python/typing#256
- Similar issue on Mypy: python/mypy#5090
- Pytype's workaround: https://github.com/google/pytype/blob/master/docs/faq.md#why-doesnt-str-match-against-string-iterables
- Mypy issue requesting a Pytype port: python/mypy#11001