[Tutor] Difference between decorator and inheritance

Cameron Simpson cs at cskk.id.au
Fri Aug 2 18:52:45 EDT 2019


On 02Aug2019 11:26, bob gailer <bgailer at gmail.com> wrote:
>And now for something completely different...
>Decorators are not required to return a function!
>I use them to create a dictionary that maps function names to the 
>corresponding function object.

That is an interesting idea! But I want to counter it, briefly.

>This is very useful when associating actions with user-entered 
>commands. Example:
>
>def collect(func=None, d={}):
>    if not func: return d
>    d[func.__name__] = func
>
>@collect
>def add(a,b):
>    return a+b
>
># adds item to dictionary d (key = 'add', value = func)
># repeat for other user-command functions
># finally:
>cmd_dict = collect() # returns the dictionary
>cmd = input('enter a command>')
>func = cmd_dict.get(cmd)

I think you're conflating 2 things: having the decorator have a side 
effect outside the bare "do stuff around the call of an inner function", 
and returning a callable.

The feature of your remark is the side effect, which is useful. Not 
returning a callable is orthognal, and a misfeature.

Let me show you why:

    Python 3.7.4 (default, Jul 11 2019, 01:07:48)
    [Clang 8.0.0 (clang-800.0.42.1)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> d={}
    >>> def collect(f): d[f.__name__]=f
    ...
    >>> def g(x): print(x*2)
    ...
    >>> @collect
    ... def h(x): print(x*3)
    ...
    >>> g(8)
    16
    >>> h(8)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: 'NoneType' object is not callable

The return value of the decorator is bound to the function name in the 
current scope,typically the current module for a normal function or the 
enclosing class for a method.

Your decorator returns None (I'm ignoring the d={} funkiness for now).  
Therefore a side effect of using the decorator is that every function 
"foo" you use with this decorator defines the name "foo" in the current 
scope as None.

As you can see in the example above, that makes the h() function not 
usable on its own. (And litters the namespace with a useless "h" name 
whose value is None.)

Had my version of your decorator looked like this:

    def collect(f):
        d[f.__name__] = f
        return f

then h() would have remained independently useful, at no cost to the 
functionality of your decorator.

So I'm arguing that while you _can_ return None from a decorator (which 
is what is actually happening in your "not returning" phrasing), it 
remains _useful_ and _easy_ to return the decorated function itself 
unchanged.

I've concerns about your d={} trick too, but we can discuss those in 
another subthread if you like.

I'm hoping to convince you that your otherwise nifty @collect decorator 
could do with returning the function unchanged after doing its work.

Finally, there is another important reason to return the function (or 
another callable): nesting decorators. Look at this piece of code from a 
project I'm working on:

    @classmethod
    @auto_session
    @require(lambda console: isinstance(console, Console))
    @require(lambda top_node: isinstance(top_node, DirTreeNode))
    @require(lambda top_node: not hasattr(top_node, 'can_uuid'))
    def from_fstree(cls, console, top_node, *, session):

This is a method, but otherwise the situation is no different. Each of 
these decorators does a little task and returns a callable, ready for 
further decoration by outer decorators. So every one of them returns a 
suitable callable.

If your @collect decorator returned the function, it too could be 
happily placed in such a nesting of decorators and everyone is happy.  
Because it does not, it does not play well with others, because an outer 
decorator would not have a callable to work with; it would get the None 
that your @collect returns.

This is the other argument for always returning a callable: to 
interoperate with other decorators, or of course anything else which 
works with a callable.

Cheers,
Cameron Simpson <cs at cskk.id.au>


More information about the Tutor mailing list