Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save saulshanabrook/bdbadb8619eb193dc38fb59de5cf85d3 to your computer and use it in GitHub Desktop.
Save saulshanabrook/bdbadb8619eb193dc38fb59de5cf85d3 to your computer and use it in GitHub Desktop.

I would like to propose allowing runtime access to arguments of type application (parameters) on classes inside of classmethods and the __init__ function.

I originally brought this up on the thread for implementing PEP 585, because it might entail changes to the runtime data structure being created there, but was advised that this would have to be brought up separately and would likely result in it's own PEP if there is expressed desire for it and a reasonable way to implement it (https://mail.python.org/archives/list/typing-sig@python.org/message/TQQRXNXY5KU2BG3Y7LPVN4QOSTRZZ2L6/).

Motivation

This would allow a common syntax to create strongly typed collections to be analyzed by tools like MyPy and also used at runtime. Many built in collections do not need this feature, because at runtime they can be heterogeneous.

If this was supported, we would be able to use it in array and machine learning libraries to specify the inner type of collections. For example:

# pick up dtype in class type application
numpy.ndarray[numpy.float16]((2, 2))
# instead of currently have to specify it as argument
numpy.ndarray((2, 2), dtype=numpy.float16)
# Would then make it easier to type getitem access to inner type


# Also should allow classmethods to also use the type applications
numpy.ndarray[numpy.float16].arange(10)
# instead of passing in as arg
numpy.arange(10, dtype=numpy.float16])

Currently, type applications are not allowed on functions, but if they were (see this discussion from last year's typing summit: https://paper.dropbox.com/doc/Typing-and-mypy-usability--AtV9rppf4zifwroZanUhqOKKAg-58lNM498db1NalRQsme4U) then we could create arange as a function instead of a classmethod:

numpy.arange[numpy.float16](10)

Implementation

For reasons I will highlight below, my recommended approach is to provide a function in typing called get_parameters that, when run in a classmethod or __init__ method on a class that has had type application and has inherited from typing.Generic returns a tuple of them. Otherwise it either errors or returns None. It would behave similar to accessing the __parameters__ attribute on builtin collections proposed in PEP 585. It would not work on builtin generic collections, only custom ones that inherit from typing.Generic.

Taking a step back, one way to look at generic type applications are a way of partially executing a function with it's type level arguments, before we instantiate it with its values. In this light, I had a couple of different ideas on how we could make these type level arguments available at runtime.

Pass them as args,kwargs

The simplest would be to pass in the extra typing arguments as a kwarg to the classmethod or init function. However, this would break any existing classes that inherit from typing.Generic and it would cause some confusion to users, I believe who most likely wouldn't want these type applications in most cases.

We could allow an argument to typing.Generic that somehow triggers this behavior on and off, but overall this seems pretty messy.

Attaching them to the class

A more intuitive place for these arguments wold be on the class itself. In a classmethod, accessing cls.__parameters__ would seem sensible. However, how would they be accessible from the constructor? They could be on type(self).__parameters. But I believe this means creating a separate class instance for each parameterization of the generic class. I am not clear how this would work.

Also, in the case that we do add the ability to parameterize functions, this method would not work. There is no self arguments in functions that lets you get access to the calling function.

Using a global function

So this leaves us with the final option, of being able to access this information from a global function. However, I am not sure how we would be able to implement this. I had to do something similar in a library I wrote, to try to get the generic types that were set, and I had to end up wrapping every descriptor in the __getattr__ part of the generic class so that if it is a classmethod some global scope gets set during the execution of that class method.

I hoped someone might have a better idea about how this could work technically! I am happy to put together an example of my (slightly annoying and cumbersome) approach if that would be useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment