Contract of a decorator

2019-07-14 05:22发布

问题:

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?

回答1:

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
>>>


回答2:

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.



回答3:

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)