TypeScript has a feature known as parameter decorators — literally a decorator that you can apply to a parameter of a function or method:
class BugReport {
// ...
print(@required verbose: boolean) {
// ...
}
}
Another example from NestJS:
class SomeController {
// ...
async findOne(@User() user: UserEntity) {
// ...
}
}
Note in the above examples that the decorators are decorating parameters of each method, not the methods themselves.
I don't think that parameter decorators like this exist in Python (at least as of v3.11, nor could I find any open PEPs that cover it); however, I'm curious to know if there is a way to implement something like this in Python?
It doesn't have to have the exact same syntax, of course; just the same effect.
I'm not super familiar with how parameter decorators work under-the-hood, but my best understanding is that they attach metadata to the corresponding function or method at compile time (so they would likely need to work in tandem with a function/method decorator, metaclass, etc.).
Python doesn’t have parameter decorators – only function decorators – but it does provide runtime access to type annotations, as well as typing.Annotated
.
from typing import Annotated
class BugReport:
@validate_required
def print(self, verbose: Annotated[bool, Required]) -> None:
...
import functools
import inspect
import typing
class Required:
pass
Required = Required()
def validate_required(fn):
required = {
name
for name, t in inspect.get_annotations(fn).items()
if typing.get_origin(t) is typing.Annotated
and Required in typing.get_args(t)[1:]
}
signature = inspect.signature(fn)
@functools.wraps(fn)
def validated(*args, **kwargs):
bound_args = signature.bind(*args, **kwargs)
for required_arg in required:
if bound_args.arguments[required_arg] is None:
raise ValueError(f"required argument is None: {required_arg!r}")
return fn(*args, **kwargs)
return validated
To do this particular kind of thing in practice, you might want to extend Typeguard instead, although I honestly wouldn’t recommend it; it’s too fragile and slow.