You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Decorators currently stress mypy's support for functional programming to the point that many decorators are impossible to type. I'm intending this issue as a project plan and exploration of the issue of decorators and how to type them. It's a collection point for problems or incompletenesses that keep decorators from being fully supported, plans for solutions to those problems, and fully-implemented solutions to those problems.
Decorators that can only decorate a function of a fixed signature
You can define your decorator's signature explicitly, and it's completely supported:
You can even use type variables to get some flexibility in argument and return types, but this falls over as soon as you don't know the exact number of arguments to expect.
Your decorated function's arguments can't be called by name.
Decorators that do not change the signature of the function
Nearly fully supported. Here's how you do it:
fromtypingimportTypeVar, Callable, castT=TypeVar('T')
defprint_callcount(f: T) ->T:
x=0defret(*args, **kwargs):
nonlocalxx+=1print("%d calls so far"%x)
returnf(*args, **kwargs)
returncast(T, ret)
@print_callcountdeflol(x: int) ->str:
return"lol"*xreveal_type(lol) # E: Revealed type is 'def (x: builtins.int) -> builtins.str'
Notes:
Mypy trusts that you can call f. You can set a bound on T to be Callable, but you don't need to for it to typecheck.
This business doesn't typecheck without the cast. That's a symptom of the fact that mypy doesn't understand the function nature of the argument to print_callcount, and there's no way to declare that argument as a Callable explicitly without losing the argument type of lol later. We'd like to minimize the places where casts are required. Doing so requires something along the lines of "variadic argument variables", discussed below.
Decorators that take arguments ("second-order"?)
Plenty of decorators "take arguments" by actually being functions that return a decorator. For example, we'd like to be able to do this:
The syntax here is fungible, but we would need a way to do approximately this thing -- capture the types and kinds of all a function's arguments in some kind of variation on a type variable.
Variadic type variables alone (python/typing#193) get you some of the way there, but lose all keyword arguments of the decorated function.
Things to do:
Implement variadic type variables (fill in PR when I have it)
Write up detailed proposal for semantics of argument variables
... and how they interact with *args and **kwargs
... and their relationship to variadic type variables and the expand operation
... and the semantics of an easy-to-use SomeArguments-style alias, so nobody has to actually engage with the details of the above when writing normal decorators.
Come to some kind of mypy-community consensus or near-consensus on that proposal. It'll be in mypy_extensions not typing at first -- this can be fodder for the future of PEP484, but while we're playing in such experimental land, not yet.
Great to see this may be really happening! The Twitter discussion gave me a new suggestion for what to name "second-order decorators" -- people seemed to like "decorator factory" best.
Reacted by pyk, Lukas Juhrich, Josiah Kaviani, Danil Ivanov, Daisy Leigh Brenecki, Dominic Davis-Foster, Ben S, Andreas Stenius, Leonardo Lai, Felipe S. S. Schneider and 3 more
Reacted by Martin Larralde, Taine Zhao, Aleksey Izmailov, Kevin Orr, Josiah Kaviani, Vinícius Dantas, read with ai, Daisy Leigh Brenecki, Alexander Pushkov, Changsheng Wu and 13 more
Here are some thoughts about decorators that tweak arguments/return types. This proposal combines features from @sixolet's proposal and other sources, such as #1927 and python/typing#193.
Argspec type variables
We'd add a new kinds of type variable: argspec. An argspec type variable represents an arbitrary sequence of callable argument types/kinds, such as DefaultArg(int, 'b') using the syntax recently implemented by @sixolet. (We could also generalize this to variadic type variables that represent simple sequences of types, but that's out of scope for this issue.)
Outside a stub, we hit a problem: it's going to be tricky to type check implementations of functions that use an argspec type variable in their signatures. We can sidestep this issue by not supporting this at all -- instead we'd require a separate external signature declaration and an implementation for such functions, and the implementation can't use argspecs. This is analogous to how overloaded functions with implementations work. Sketch of how to implement contextmanager:
The name declared_type is analogous to decorated_type (see #3291), but I'm not convinced that this the best name we can think of. Maybe we should even combine declared_type and decorated_type.
__call__
Now what about a decorator that is not a callable but a more general object? We could still generalize this approach by making it possible to use an argspec to define the type of __call__ (here I assume a stub file):
Note that Args can be used for both *args and **kwargs -- actually, it must always be used in such a way if used outside a Callable type, as only *args: Args doesn't really make sense.
Expand
We can support an Expand[...] operator that lets us tweak argument lists. For example, here we add an extra int argument to the beginning of an argument list:
Outside a stub, we hit a problem: it's going to be tricky to type check implementations of functions that use an argspec type variable in their signatures.
How so? I'm aware that it's difficult for general variadic functions (as mentioned in python/typing#193), but I'd expect that most decorators will just treat values of Args type as opaque objects to be passed on to their wrapped function.
@JukkaL I have been pondering this problem for the last two weeks, and haven't started an implementation of any solution because I hadn't solved it yet in my head. I like a whole lot about your suggestions, and I think they get us a lot further.
Some wiggly bits I have:
I don't think argspec is a kind of type variable. I think it's different enough it should be its own thing.
This uses a more direct method than redefinition to declare the type of the function aside from its implementation. It also neatly sidesteps the problem of how to define an argspec in the def syntax at all, for some weird signatures:
... I don't think these weird signatures are important enough to specifically jump through hoops on their own. Should do this form if we feel like it's cleaner.
The following is speculation and musing and I'm not particularly strongly attached to it.
I have some kind of sneaking suspicion that what we're calling "argspecs" here and variadic type variables are more closely related than (at least I) suspected. I'm looking for a way to relate these two concepts -- for example, a way to cleanly and pleasantly write the signature of map.
(Here Expand takes an second argument, specifically the argspec/variadic type var to expand. This turns it into some kind of comprehension.)
Whichever of these forms we pick, the form for declaring the type of a decorated function should be exactly the same -- they shouldn't be two concepts.
I don't think argspec is a kind of type variable. I think it's different enough it should be its own thing.
That's fair. Anyway, we don't need to decide this very early, since this is only a matter of syntax.
This uses a more direct method than redefinition to declare the type of the function aside from its implementation.
I have no strong opinion either way right now, but I have a feeling that the flexible callable syntax can make more complex signatures hard to read. Again, this is mostly a matter of syntax and we can bikeshed it later after we've agreed on basic principles. And I agree that making this consistent with how we declare the decorated signature of a function would be nice.
I have some kind of sneaking suspicion that what we're calling "argspecs" here and variadic type variables are more closely related than (at least I) suspected.
Yes, I think that a variadic type variables would mostly behave like an argspec, but it wouldn't have any argument kinds (just a sequence of types). I left this out from my proposal because it's not very directly related to the current issue. A variadic type variable would also allow things like Tuple[Expand[X]], which argspecs wouldn't support.
How so? I'm aware that it's difficult for general variadic functions (as mentioned in python/typing#193), but I'd expect that most decorators will just treat values of Args type as opaque objects to be passed on to their wrapped function.
Yes, most decorators probably are trivial in that respect. However, any non-trivial decorators could be very tricky to type check. I'm worried that users have a significant number of those (though still a minority of all decorators) and if we don't type check them properly it will be a never-ending stream of bug reports and ideas about handling various edge cases. Also, as these are otherwise quite similar to variadic type variables, it might feel a little odd if we could type check one but not the other.
Maybe we can allow a simpler version of Expand for Python 3.5 and later:
Unfortunately that doesn't work with variadic type variables for things like Tuple[Expand[X]], and I'd rather not have two different syntax variants for expanding type variables.
How should be defined decorators that reduce number of variables? Just in an opposite way?
Yes, though changing the number of arguments might be something we implement only a bit later as the feature would be quite useful even without it.
It's been over a year w/o updates to this thread, but it's still the first one that comes up for me in a google search for mypy and decorators. Is this still the right place to look for updates/status/plans for decorator typing improvements?
Reacted by snejku, Albin Skott, Dmitry Figol, Marko Ristin, Wesley Bowman, Brian Cloutier, Davin K. Tanabe, Pedro Tabacof, Simon Gurcke, Cesar Canassa and 26 more
Thanks! From reading the PEP, this will not help with decorators that change the argument type or return type, correct? Like the supply_zero example above?
The trade-off here is that by going in and out of this ListVariadic, we lose the names of the arguments, meaning that, for example, foo(y="A", z=True) would not be accepted by Pyre in the second example, even though it would work in the runtime.
For reference, this is what supply_zero could look like
Supporting mutation in the full-fidelity ParameterSpecifications would require rich handling of name collision, which would get very complex very quickly. In my opinion working out the specification/implementation of that isn't worth blocking the rest of this, since it seems like the combination of these two features can get us a lot of the way there.
Reacted by Anton Agestam, Jan Rüegg and Frederik Aalund
Thanks for the heads-up and detailed explanation! This looks very useful indeed as well. What I'm actually looking for currently is a way to let decorators transform arguments. Eg you have a function taking any number of arguments (like str, int, int), and the decorator turns it into a function taking a list of each argument (List[str], List[int], List[int]). The decorater itself would then do the work of taking elements from the list and giving them to the actual function. This seems to be still out of scope, even with ListVariadic's, as far as I can see?
@mrkmndz Thanks a lot, that seems to be exactly what I was looking for (except the keeping parameter names would be nice, of course). What's pyre and pyre_extensions and how is it connected to mypy?
Which parts of @mrkmndz's pdf are on a standards track? Is this a strawman implementation for a PEP? (Otherwise, very interesting ideas in this document.)
As for MyPy, from my conversations with @ilevkivskyi and @JukkaL I believe that they are planning on building out a compatible implementation on the MyPy side in near future with the help of @theodoretliu .
With regards to mapping a ParameterSpecification instead of a ListVariadic, one could definitely imagine an analogous Callable[Map[TParams, List], TR] syntax, but to me that seems too heavy-duty to implement for the amount of usages I have actually seen in practice. Map on ListVariadics comes up in enough places where we felt it was worth implementing there, and in my opinion, that same case does not exist for Parameter Specifications.
@kaste I will be working on drafting a PEP on ListVariadic, Map, and Concatenate in the next month. I am planning on deferring the IntVar related stuff (e.g. Index etc.) into another one once that has an example implementation. Syntax is definitely subject to change, but I would anticipate that most of the core ideas there are planned to be headed for a standards-track PEP.
I think the initial post of this issue should be updated to briefly mention the current state of decorator support. A while ago, I skimmed through this issue and got the impression that "second-order decorators" (without modifying arguments) are not supported by Mypy. Only later I discovered that in fact they are supported.
This is also a documentation issue, the current documentation only explanis bare decorators. I will look into submitting a PR for that.
Reacted by Eric Arellano, Cathal, Alex Kritchevsky, Tomas Sandven, karlicoss, FichteFoll, Matt Briançon, Bart Louwers, nreinhardt, Daan Rijks and 2 more
Activity
gvanrossum commentedon Apr 12, 2017
Great to see this may be really happening! The Twitter discussion gave me a new suggestion for what to name "second-order decorators" -- people seemed to like "decorator factory" best.
elazarg commentedon Apr 12, 2017
"decoratory" :)
JukkaL commentedon May 4, 2017
Here are some thoughts about decorators that tweak arguments/return types. This proposal combines features from @sixolet's proposal and other sources, such as #1927 and python/typing#193.
Argspec type variables
We'd add a new kinds of type variable: argspec. An argspec type variable represents an arbitrary sequence of callable argument types/kinds, such as
DefaultArg(int, 'b')using the syntax recently implemented by @sixolet. (We could also generalize this to variadic type variables that represent simple sequences of types, but that's out of scope for this issue.)This is how we'd define one:
For example, we could use this in a stub for
contextlibto define the signature ofcontextmanager:Decorator implementations
Outside a stub, we hit a problem: it's going to be tricky to type check implementations of functions that use an argspec type variable in their signatures. We can sidestep this issue by not supporting this at all -- instead we'd require a separate external signature declaration and an implementation for such functions, and the implementation can't use argspecs. This is analogous to how overloaded functions with implementations work. Sketch of how to implement
contextmanager:The name
declared_typeis analogous todecorated_type(see #3291), but I'm not convinced that this the best name we can think of. Maybe we should even combinedeclared_typeanddecorated_type.__call__Now what about a decorator that is not a callable but a more general object? We could still generalize this approach by making it possible to use an argspec to define the type of
__call__(here I assume a stub file):Note that
Argscan be used for both*argsand**kwargs-- actually, it must always be used in such a way if used outside aCallabletype, as only*args: Argsdoesn't really make sense.Expand
We can support an
Expand[...]operator that lets us tweak argument lists. For example, here we add an extraintargument to the beginning of an argument list:The relationship between
ArgsandExpand[Args]is like the relationship betweenalistand*alist.JukkaL commentedon May 4, 2017
Oh and the updated
TypeVaranddeclared_typewould initially live inmypy_extensions.ilevkivskyi commentedon May 4, 2017
I have two clarifying questions/proposals:
Expandfor Python 3.5 and later:rkr-at-dbx commentedon May 4, 2017
How so? I'm aware that it's difficult for general variadic functions (as mentioned in python/typing#193), but I'd expect that most decorators will just treat values of
Argstype as opaque objects to be passed on to their wrapped function.sixolet commentedon May 5, 2017
@JukkaL I have been pondering this problem for the last two weeks, and haven't started an implementation of any solution because I hadn't solved it yet in my head. I like a whole lot about your suggestions, and I think they get us a lot further.
Some wiggly bits I have:
I don't think argspec is a kind of type variable. I think it's different enough it should be its own thing.
When you have this code block:
Can we consider this instead:
This uses a more direct method than redefinition to declare the type of the function aside from its implementation. It also neatly sidesteps the problem of how to define an argspec in the
defsyntax at all, for some weird signatures:... I don't think these weird signatures are important enough to specifically jump through hoops on their own. Should do this form if we feel like it's cleaner.
The following is speculation and musing and I'm not particularly strongly attached to it.
I have some kind of sneaking suspicion that what we're calling "argspecs" here and variadic type variables are more closely related than (at least I) suspected. I'm looking for a way to relate these two concepts -- for example, a way to cleanly and pleasantly write the signature of
map.Maybe something like:
(Here
Expandtakes an second argument, specifically the argspec/variadic type var to expand. This turns it into some kind of comprehension.)JukkaL commentedon May 5, 2017
@sixolet
That's fair. Anyway, we don't need to decide this very early, since this is only a matter of syntax.
I have no strong opinion either way right now, but I have a feeling that the flexible callable syntax can make more complex signatures hard to read. Again, this is mostly a matter of syntax and we can bikeshed it later after we've agreed on basic principles. And I agree that making this consistent with how we declare the decorated signature of a function would be nice.
Yes, I think that a variadic type variables would mostly behave like an argspec, but it wouldn't have any argument kinds (just a sequence of types). I left this out from my proposal because it's not very directly related to the current issue. A variadic type variable would also allow things like
Tuple[Expand[X]], which argspecs wouldn't support.@rkr-at-dbx
Yes, most decorators probably are trivial in that respect. However, any non-trivial decorators could be very tricky to type check. I'm worried that users have a significant number of those (though still a minority of all decorators) and if we don't type check them properly it will be a never-ending stream of bug reports and ideas about handling various edge cases. Also, as these are otherwise quite similar to variadic type variables, it might feel a little odd if we could type check one but not the other.
@ilevkivskyi
Unfortunately that doesn't work with variadic type variables for things like
Tuple[Expand[X]], and I'd rather not have two different syntax variants for expanding type variables.Yes, though changing the number of arguments might be something we implement only a bit later as the feature would be quite useful even without it.
toejough commentedon Jul 21, 2018
It's been over a year w/o updates to this thread, but it's still the first one that comes up for me in a google search for mypy and decorators. Is this still the right place to look for updates/status/plans for decorator typing improvements?
sobolevn commentedon Jun 16, 2019
Just for the record: if someone needs to change the return type of the function inside the decorator and still have typed parameters, you can use a custom
mypyplugin that literally takes 15 LoC: https://github.com/dry-python/returns/blob/92eda5574a8e41f4f5af4dd29887337886301ee3/returns/contrib/mypy/decorator_plugin.pySaved me a lot of time!
ilevkivskyi commentedon Jun 16, 2019
@sobolevn This is btw exactly how mypy preserves precise types for
@contextmanager.decorator_plugin for correct treatment of ReturnOutputs decorator
mrkmndz commentedon Dec 19, 2019
We have just submitted a PEP to provide an extension to the type system to allow for the modification of return types of functions by decorators without the use of plugins at python/peps#1259. We would love to hear feedback over on typing-sig@python.org, especially by anyone on this issue that has struggled with this in the past (thread is here https://mail.python.org/archives/list/typing-sig@python.org/thread/UDHSH4EVVIDKNLRX2YGCIUCBGZ5ALRKC/)
rggjan commentedon Dec 20, 2019
Thanks! From reading the PEP, this will not help with decorators that change the argument type or return type, correct? Like the
supply_zeroexample above?mrkmndz commentedon Dec 20, 2019
@rggjan The PEP will address decorators that alter the return type, but not that alter the arguments.
For altering the arguments, we have another PEP coming down the pipe that may fit your needs, ListVariadics.
For addition:
For removal:
For more details on ListVariadics, you can read this presentation from the last typing summit (https://github.com/facebook/pyre-check/blob/master/docs/Variadic_Type_Variables_for_Decorators_and_Tensors.pdf)
The trade-off here is that by going in and out of this ListVariadic, we lose the names of the arguments, meaning that, for example,
foo(y="A", z=True)would not be accepted by Pyre in the second example, even though it would work in the runtime.For reference, this is what supply_zero could look like
Supporting mutation in the full-fidelity
ParameterSpecificationswould require rich handling of name collision, which would get very complex very quickly. In my opinion working out the specification/implementation of that isn't worth blocking the rest of this, since it seems like the combination of these two features can get us a lot of the way there.rggjan commentedon Dec 23, 2019
Thanks for the heads-up and detailed explanation! This looks very useful indeed as well. What I'm actually looking for currently is a way to let decorators
transformarguments. Eg you have a function taking any number of arguments (likestr, int, int), and the decorator turns it into a function taking a list of each argument (List[str], List[int], List[int]). The decorater itself would then do the work of taking elements from the list and giving them to the actual function. This seems to be still out of scope, even with ListVariadic's, as far as I can see?mrkmndz commentedon Jan 2, 2020
@rggjan , this is actually in scope for ListVariadics, and in fact is already implemented (but unfortunately only documented at this point in https://github.com/facebook/pyre-check/blob/master/docs/Variadic_Type_Variables_for_Decorators_and_Tensors.pdf).
The addition you'll need here is
pyre_extensions.type_variable_operators.Map.For your example, you'll need this:
Note that again you're losing the names of those parameters by passing through a ListVariadic, but the transformation you're looking for is there.
Does that work for your use case?
rggjan commentedon Jan 3, 2020
@mrkmndz Thanks a lot, that seems to be exactly what I was looking for (except the keeping parameter names would be nice, of course). What's
pyreandpyre_extensionsand how is it connected tomypy?kaste commentedon Jan 3, 2020
Which parts of @mrkmndz's pdf are on a standards track? Is this a strawman implementation for a PEP? (Otherwise, very interesting ideas in this document.)
mrkmndz commentedon Jan 3, 2020
@rggjan Pyre (https://pyre-check.org/) is another implementation of PEP484 type checking, and pyre_extensions (https://pypi.org/project/pyre-extensions/) is our pip package for the runtime components of our extensions that are not yet standardized.
As for MyPy, from my conversations with @ilevkivskyi and @JukkaL I believe that they are planning on building out a compatible implementation on the MyPy side in near future with the help of @theodoretliu .
With regards to mapping a ParameterSpecification instead of a ListVariadic, one could definitely imagine an analogous
Callable[Map[TParams, List], TR]syntax, but to me that seems too heavy-duty to implement for the amount of usages I have actually seen in practice. Map on ListVariadics comes up in enough places where we felt it was worth implementing there, and in my opinion, that same case does not exist for Parameter Specifications.@kaste I will be working on drafting a PEP on ListVariadic, Map, and Concatenate in the next month. I am planning on deferring the IntVar related stuff (e.g. Index etc.) into another one once that has an example implementation. Syntax is definitely subject to change, but I would anticipate that most of the core ideas there are planned to be headed for a standards-track PEP.
Find a better way of typing decorators that don't change signature
rggjan commentedon Jan 6, 2020
Clear, thanks @mrkmndz for the information!
intgr commentedon Jan 26, 2020
I think the initial post of this issue should be updated to briefly mention the current state of decorator support. A while ago, I skimmed through this issue and got the impression that "second-order decorators" (without modifying arguments) are not supported by Mypy. Only later I discovered that in fact they are supported.
This is also a documentation issue, the current documentation only explanis bare decorators. I will look into submitting a PR for that.
NeilGirdhar commentedon Dec 24, 2021
Can this issue be closed now?
Now it can:
The other broken example depends on completion of #8645