Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python decorator @func().attribute syntax error

I tried to find an answer here, but could not.

@obj.func # works
@obj.func(**kwargs)  #works
@obj.func1(**kwargs).func2   #-> syntax error 

I do not understand why the third form is a SyntaxError, it seems for me that is not violating any python syntax and it is clear for me what the user want to do (see example below).

I looked at pep 0318 of decorator implementation but didn't find any answers.

Here bellow, would be an example of use:

class ItemFunc(object):
    def __init__(self, fcall=None, **kwargs):
        self.defaults = kwargs
        self.fcall = None

    def __call__(self, *args, **kwargs):
        kwargs = dict(self.defaults, **kwargs)
        # do something more complex with kwargs 
        output = self.fcall(*args, **kwargs)
        # do something more with output  
        return output

    def caller(self, fcall):
        """ set call and return self """
        self.call = fcall # after some check obviously
        return self

    def copy(self,**kwargs):
        kwargs = dict(self.defaults, **kwargs)
        return self.__class__(self.fcall, **kwargs)

    def copy_and_decorate(self, **kwargs):
        return self.copy(**kwargs).caller 

Than you can use ItemFunc as a decorator:

@ItemFunc
def plot(**kwargs):
    pass

redcross = plot.copy(color="red", marker="+")
@redcross.caller
def plot_data1(**kwargs):
    pass

bluecross = redcross.copy(color="blue")
@bluecross.caller
def plot_data2(**kwargs):
    pass

But why this following 'short cut syntax' is forbidden :

@redcross.copy(color="blue").caller
def plot_data2(**kwargs):
    pass

But I can do:

@redcross.copy_and_decorate(color="blue")
def plot_data2(**kwargs):
    pass         

The first form looks for nicer, at least I understand better the intentions behind.

like image 997
user3240484 Avatar asked Sep 14 '15 14:09

user3240484


2 Answers

The Function definitions grammar does not allow for calls with further dotted names; the syntax is limited to dotted names and an optional call at the end:

decorated      ::=  decorators (classdef | funcdef)
decorators     ::=  decorator+
decorator      ::=  "@" dotted_name ["(" [argument_list [","]] ")"] NEWLINE
funcdef        ::=  "def" funcname "(" [parameter_list] ")" ":" suite
dotted_name    ::=  identifier ("." identifier)*

Note that that's not a full expression, but a very limited subset.

This echoes the PEP, which states:

The decorator statement is limited in what it can accept -- arbitrary expressions will not work. Guido preferred this because of a gut feeling [17] .

and

The rationale for having a function that returns a decorator is that the part after the @ sign can be considered to be an expression (though syntactically restricted to just a function), and whatever that expression returns is called. See declaration arguments [16] .

Emphasis mine.

The rationale is that Guido feels there isn't a real use case for allowing more:

So while it would be quite easy to change the syntax to @test in the future, I'd like to stick with the more restricted form unless a real use case is presented where allowing @test would increase readability. (@foo().bar() doesn't count because I don't expect you'll ever need that).

You'll have to convince Guido and the other core developers that your case is a proper usecase worthy of lifting these restrictions!

like image 140
Martijn Pieters Avatar answered Nov 10 '22 05:11

Martijn Pieters


As explained in the other answer, Guido had a "gut feeling" was the reason for the restriction originally.

This restriction is lifted in Python 3.9., allowing decorators to be any valid expression.

Previously, the grammar for decorators was:

decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE

In Python 3.9 the grammar is simplified to:

decorator: '@' namedexpr_test NEWLINE

This is the only grammar change between 3.8 and 3.9. See PEP 614 -- Relaxing Grammar Restrictions On Decorators for full details.

like image 21
wim Avatar answered Nov 10 '22 07:11

wim