The parking machine implementation I am interesting in the topic of finite automata, so I started over by implementing a parking machine from this video: Computers Without Memory - Computerphile to understand conceptions. Didn't do much of an error checking - have made a minimal working version.

Short description of the task, but for better understanding watch the video:


You have the parking machine which is implemented by the finite automaton.
The parking lot costs 25 pence. The machine takes only 5, 10, 20 pence coins. Thus, any combination of these coins can be used to pay for parking.
After a sum of coins has reached 25 pence, the ticket is issued.
The machine gobble up all extra money above the 25 pence, like 20 + 20 pence = the ticket only, no change are given.


Questions:


What do you think about my approach? 
Is it optimal, extendable, suitable for real world applications or this technique has limiting factors and an another should be used?
How would you implement this machine? It will be good to see another solutions.


It is possible to do this by such way: Explicit state transition table, may be I will do this also. My target is to implement something more serious like a regex engine in the future, but I should read some books/articles before.

#!/usr/bin/python3

def get_coin(current_sum, *functions):
    print(f"Current sum = {current_sum} pence")
    coin = int(input("Insert coin\n"))
    if coin == 5:
        functions[0]()
    elif coin == 10:
        functions[1]()
    elif coin == 20:
        functions[2]()
    else:
        error(current_sum, functions)

def error(current_sum, functions):
    print("Error: only 5, 10, 20 pence coins are allowed\n")
    get_coin(current_sum, *functions)

def zero_pence():
    get_coin("0", five_pence, ten_pence, twenty_pence)

def five_pence():
    get_coin("5", ten_pence, fifteen_pence, twenty_five_pence)

def ten_pence():
    get_coin("10", fifteen_pence, twenty_pence, give_ticket)

def fifteen_pence():
    get_coin("15", twenty_pence, twenty_five_pence, give_ticket)

def twenty_pence():
    get_coin("20", twenty_five_pence, give_ticket, give_ticket)

def twenty_five_pence():
    input("Current sum = 25 pence, press the 'return' button to pay")
    give_ticket()

def give_ticket():
    print("""\nTake your ticket:

Date: 1 November 2019
Start time: \t20:21
End time: \t22:21\n
""")

def parking_machine():
    prompt = """\n\tThe parking machine.

Information:
1. The machine takes 5, 10, 20 coins.
2. The machine doesn't give a change.
3. The parking costs 25 pence.

Press 's' button to start inserting of coins.

"""
    prompt = '#' * 80 + prompt + '#' * 80 + '\n'

    while True:
        button = input(prompt)
        if button == 's':
            zero_pence()

parking_machine()
Not a finite state machine

The state machine in the posted code has infinite states.
This is caused by the recursive calls of get_coin and error.
Consider for example these states:


zero-pence with nothing entered yet
zero-pence, after zero-pence and an invalid input
zero-pence, after zero-pence and an invalid input twice
...


And so on. You can see these are different states,
because the content of the stack is different.

There are theoretically infinite zero-pence, five-pence, etc, states.
Practically there are finite, because after enough invalid inputs,
the stack will eventually overflow.

There should be precisely one zero-pence, five-pence, etc, states.

This is easy to fix by replacing the error function with a loop inside get_coin.

There are too many states

Even after making the states finite,
there will still be more states than required by the exercise.

Why, again, because of the stack.
For example, there are two ways to reach 10 pence:


0 -> 5 -> 5
0 -> 10


At this point, the machine should be in the 10 pence state,
but these two states are not the same,
because the stack stores two different histories.

If you want truly identical ten-pence, fifteen-pence, etc, states,
you have to eliminate history, you have to eliminate stack,
you have to eliminate function calls.

This is easy to fix by implementing state transitions in a loop,
instead of through function calls.

Don't use varargs if you don't need it

The get_coin function takes a variable number of functions as arguments.
But in fact it will only ever use 3,
and in fact it's only ever called with 3.
Therefore, it would be more natural to give those parameters dedicated, descriptive names.

Why convert input to int?

The get_coin function converts input to int,
but doesn't actually perform any numeric operations with it.

Use a more portable shebang

Not all systems have Python 3 installed in /usr/bin/python3.
A more robust, portable shebang would be:

#!/usr/bin/env python3