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)
- Saves the function to func
- Sets the arguments to the default value and
- 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.