Contract: A function that takes function as argument and returns a function[i.e., modified(or same) version of passed function]. Passed function, here is, square
, for example.
@floatify
def square(n):
return n*n
Is decorator suppose to return only decorated version of passed function, but nothing else?
It is supposed to only return a function, but nothing stops you from returning anything you'd want.
>>> def d(x):
... return "hello"
...
>>> @d
... def f():
... return "world"
...
>>> f
'hello'
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object is not callable
>>>
The decorating function ought to return a function because whatever it returns will get bound to the original function's name. So it's rather confusing if the returned object isn't a function (or some other callable, like a class constructor, or callable class instance).
Generally the returned function ought to have a function signature that's compatible with the original function, but I guess it's ok if the returned function also takes additional args. Also, the return type of the returned function ought to be compatible with that of the original function.
A decorated function is kind-of like a subclass of the original function, and so it makes sense to adhere to the Liskov substitution principle.
The decorating function could have side-effects: eg, it could modify some global. That may be useful, eg for logging purposes; OTOH, functions should generally avoid having side-effects.
FWIW, some of the standard function decorators return non-function callables, the most common one is probably @classmethod
.
There's nothing particularly magical about decorators. As Jared Goguen mentions in a comment,
@decorator
def some_function(args):
#etc
is identical to
def some_function(args):
#etc
some_function = decorator(some_function)
The second form is a little longer, but more powerful, since you can choose to bind the returned function to a different name, if you want. Some things that can be easily accomplished using the longer syntax can be difficult if not downright impossible to do using the @
syntax.
Decorator can be used as
- Registration decorator
- Functional decorator
- Parameterized decorator
- Class-based decorator
Code in your query is Functional decorator, that replace decorated function(square
) with wrapper
. wrapper
does the following:
- calls
f(n)
- applies
float
to result
and returns it.
def floatify(f):
def wrapper(n):
result = f(n)
return float(result)
return wrapper
Contract of functional decorator:
The replacement function usually honors the contract of the decorated function:
- Accept same number/kinds of args
- Return result of compatible type
The replacement function should preserve metadata from the decorated function
- Important for debugging and other meta-programming purposes, Use
@functools.wraps(f)