-
Notifications
You must be signed in to change notification settings - Fork 5
Description
I was thinking...
-
If an object is a
lambda(which we can detect with reasonably reliable accuracy by looking at.__code__.co_name- see https://stackoverflow.com/a/56451124/4372452), then the only way it can be decorated with@syntax is if it was returned from a nested decorator:@foo @baz # this could return a `lambda` def qux(): ...
-
Ditto for callable objects which are neither classes nor functions but just instances of a class which has a
__call__method: they can also only be decorated with@syntax if they are returned from a nested decorator.
And then I thought well it sure would be nice if there was some standard way that all proper decorators were required to use to mark any such wrapper that they return as a wrapper, such a shame that this doesn't exist, maybe we could invent- oh right __wrapped__.
The only exceptions I can think of are functools.partial and bound methods. If we think it is sufficiently reasonable/acceptable for decorators to return those, then they are always ambiguous, because for one reason or another you can't set __wrapped__ on them, so we can't justifiably expect people to do that if returning them from a decorator. So we must let them fall through to the more complex tricks for determining what to do with regular functions and classes.
So, putting that all together, I would do something like this:
from functools import partial
from types import FunctionType, MethodType
def can_arg_be_a_decorator_target(arg):
if isclass(arg):
return True
elif isinstance(arg, FunctionType):
if arg.__code__.co_name == '<lambda>':
return hasattr(arg, '__wrapped__')
return True
elif callable(arg):
if isinstance(arg, (partial, MethodType)):
return True
return hasattr(arg, '__wrapped__')
return False