1

I'm trying to figure out, which Python idiom has been used at next lines.

state = state0

while state:
    state = state()

I'm confused why here is state0 instead of state0()? And what this line

state = state() 

is doing? Why there isn't state0() ?

from random import random
from time import sleep


    def state0():
        print("state0")
        # delay and decision path to simulate some application logic
        sleep(.5)
        if random()>.5:
            return state1
        else:
            return state2
    
    def state1():
        print("state1")
        # delay and decision path to simulate some application logic
        sleep(.5)
        if random()>.5:
            return state0
        else:
            return state2
    
    def state2():
        print("state2")
        # delay and decision path to simulate some application logic
        sleep(.5)
        if random()>.5:
            return state0
        else:
            return None
    
    state=state0    # initial state
    
    
    while state:
        state=state()  # launch state machine
    
    print("Done with states")
a121
  • 798
  • 4
  • 9
  • 20
Haraldii
  • 41
  • 1
  • 6
  • 3
    Functions are objects. You can see that your functions actually return other functions. So you initially set `state` to be the `state0` function. Then in a loop you call `state` and update it with the return value which is also a function. So every time you call the next function until `None` is returned – Tomerikoo Dec 08 '20 at 13:27
  • Thanks, I think I got this functions are objects concept. I'm still bit lost that why I don't need to use empty parenthesis when calling function? – Haraldii Dec 08 '20 at 13:32
  • 1
    `state = state0` *doesn't* call `state0`; it makes `state` another name for `state0`. Inside the loop, you repeatedly call *some* function (whatever `state` refers to), with each function returning a reference to the function that should be called next. – chepner Dec 08 '20 at 13:33
  • 1
    As I said functions are just objects. Using their name is referencing the object. So you can assign multiple variables with the same function object. Only when you use `()` it is actually ***calling*** the function – Tomerikoo Dec 08 '20 at 13:33
  • Well, this is feature of Python. "When we call a function with parentheses, the function gets execute and returns the result to the callable. In another case, when we call a function without parentheses, a function reference is sent to the callable rather than executing the function itself." – Haraldii Dec 08 '20 at 13:34
  • 1
    "call a function with parentheses" is redundant; the parentheses *are* the call. Without the parentheses, you simply have a reference to the object. Syntacially, `foo()` is valid no matter *what* `foo` refers to, but you'll get a `TypeError` at run time if `foo` isn't callable. – chepner Dec 08 '20 at 13:35
  • I don't know where you got this quote from, but it's a bit confusing. Another version of what chepner was saying above: *"when we call a function without parentheses"* - doesn't make sense at all. If we don't use parenthesis, there is no call to begin with... Also the use of *callable* there seems to be wrong... – Tomerikoo Dec 08 '20 at 13:38
  • https://www.geeksforgeeks.org/python-invoking-functions-with-and-without-parentheses/ – Haraldii Dec 08 '20 at 13:40
  • 1
    Well, I would try to avoid random resources for information. Try to stick with ***official*** documentations, like: [Defining Functions](https://docs.python.org/3/tutorial/controlflow.html#defining-functions). From there: *A function definition associates the function name with the function object in the current symbol table. The interpreter recognizes the object pointed to by that name as a user-defined function. Other names can also point to that same function object and can also be used to access the function* – Tomerikoo Dec 08 '20 at 13:41

2 Answers2

4

Python allows for variables to be function references. What is happening in your example is essentially a longer version of this. At first, the function state0 is assigned to state. Since there are no brackets there, Python does not call the function, but rather assigns the function to the variable.

The while loop in your example simply checks whether state is anything not defined as falsey (i.e. not None, empty list, etc.). In every iteration of the loop, the variable state is assigned a new function by calling the function it was previously assigned to up until you reach the point where state2 returns None which breaks the while loop.

Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
C Hecht
  • 932
  • 5
  • 14
1

As others have mentioned, you can assign function to a variable and execute it later as needed.

Here is a simple example of how it might be used (though a bit artificial).

Suppose that you really want to write a recursive function that computes factorial. Easy enough, you can write something like this.

def fact(n, res=1):
    return res if n==0 else fact(n-1, res=res*n)

The problem is that if you try to execute this on big n, let's say 10000, this will cause the callstack overflow and you will receive

RecursionError: maximum recursion depth exceeded in comparison

One way to work around this problem is to return a function instead of the result and then execute it on your own.

def fact(n, res=1):
    return res if n==0 else lambda: fact(n-1, res=res*n)

Now you can call it like this.

fact(5)()()()()()

Which will give you the correct result. The advantage is that you are not limited by the size of the callstack anymore. Obviously, you wouldn't want to write all those braces so you can write a loop to do it for you. This loop will check whether the result is callable and based on that it will either execute the function or return a result.

def loop(func, n):
    res = func(n)
    while hasattr(res, "__call__"): 
        res = res()
    return res

Now you can use your fact function with n being 10000, without overflowing the callstack by calling

loop(fact, 10000)
Matus Dubrava
  • 13,637
  • 2
  • 38
  • 54