Christopher Pisz
Christopher Pisz

Reputation: 4010

Strange syntax with commas

I'm learning python. I just finished a tutorial on decorators. I went and found a decorator in some code, but see yet more strange and unfamiliar things.

def state(allowed=['*']):

    def decorator(func):
        func.__fsm_state__ = True
        func.__fsm_allowed__ = allowed
        return func

    if callable(allowed):
        func, allowed = allowed, ['*']
        return decorator(func)

    return decorator

I dunno what the following line does:

func, allowed = allowed, ['*']

Can someone explain?

Upvotes: 1

Views: 81

Answers (2)

Chemistree
Chemistree

Reputation: 432

State is, in this case, not a decorator directly, but rather a meta-decorator, or a decorator-generating function: It is not applied to a function directly, but applied to some other arguments, which it will use to return a "real" decorator:

def a(myargs): # applied to some arguments
    def b(func): # decorator
        do_smth(func, myargs)
    return b # calling a will return the decorator

@a("world")
def hello(): # do_smth(hello, "world") is called
    pass

When you type

@state(["something"])
def foo():
    pass

this will invoke the state function using ["something"] as the argument, which will in turn return the decorator function, which is finally applied to the function foo, setting the __fsm_state__ and __fsm_allowed__ attributes, depending on the parameters originally passed to @state.

When you instead use

@state()
def foo():
    pass

allowed (and, in turn, __fsm_allowed__) will be set to the default value of ["*"], which you can see in the declaration of the state function.

Buf if you miss the brackets, that is,

@state  # <- no () there
def foo():
   pass

The function foo is taken to be the parameter to state (so allowed is now foo instead of that list it's actually supposed to be), which might lead to subtle bugs - which is why in the definition of state, there is the check

if callable(allowed):

which catches the mistake of passing foo directly, and just assumes you meant the default arguments (allowed=["*"])

The following code,

func, allowed = allowed, ['*'] 
return decorator(func)

Which can be slightly simplified to

func = allowed
allowed = ["*"]
return decorator(func)
  1. Saves the function to func
  2. Sets the arguments to the default value and
  3. Applies the "real" decorator to the function,

Which effectively means that @state and @state() now do exactly the same thing.

In my opinion, the check should rather be an assertion, so you can quickly find and fix such inconsistencies in your code, but whoever wrote that decided to just ignore them silently.

Upvotes: 1

John Gordon
John Gordon

Reputation: 33335

Also, I dunno what the following line does:

func, allowed = allowed, ['*']

It's a slightly shorter way of writing

func = allowed
allowed = ['*']

Search for "tuple assignment" for more information.

Upvotes: 1

Related Questions