Home > Software design >  Why can't I call other functions/commands after running main() from within script?
Why can't I call other functions/commands after running main() from within script?

Time:09-05

#!/usr/bin/env python3
import click

@click.command()
@click.option('--desiredrange', '-r', default=1)

def main(desiredrange):
    print(desiredrange)
    print('abcd')

main()
print('!!!!')

Running the above code gives me the following in the terminal:

1

abcd

But I do not get

!!!!

This scenario is true for other variations of code that include ANYTHING after the main() function. The script exits after executing this function. This is also true for any function i.e. if I placed the print('!!!!') inside another function, then called that function after main(), the script exits after main(), ignoring the second function.

If I removed 'click', such that the code looks like:

#!/usr/bin/env python3

def main():
    print(1)
    print('abcd')

main()
print('!!!!')

I will get the following printed to terminal:

1

abcd

!!!!

I can execute other functions after main(), as well as other commands. I run this script from terminal using ./script.py (applied chmod x script.py). I also get no errors from BOTH scenarios.

Why is this?

CodePudding user response:

The function named main that you defined isn't actually the one called directly by the line main(). Instead, the two decorators are creating a new value that wraps the function. This callable (I don't think it's necessarily a function, but a callable instance of click.core.Command; I'm not digging into the code heavily to see exactly what happens.) calls raises SystemExit in some way, so that your script exits before the "new" main actually returns.

You can confirm this by explicitly catching SystemExit raised by main and ignoring it. This allows the rest of your script to execute.

try:
    main()
except SystemExit:
    pass
print('!!!!')

Remember that decorator syntax is just a shortcut for function application. With the syntax, you can rewrite your code as

import click

def main(desiredrange):
    print(desiredrange)
    print('abcd')

x = main

main = click.command(click.option('--desiredrange', '-r', default=1)(main))

y = main
assert x is not y

main()
print('!!!!')

Since the assertion passes, this confirms that the value bound to main is not your original function, but something else.

CodePudding user response:

The click module runs your script in standalone_mode which invokes the command and then shuts down the Python interpreter, rather than returning.

You can call your main with standalone_mode=False and it'll return and then continuing executing your additional statements.

#!/usr/bin/env python3
import click

@click.command()
@click.option('--desiredrange', '-r', default=1)
def main(desiredrange):
    print(desiredrange)
    print('abcd')

main(standalone_mode=False)
print('!!!!')
> python source.py -r 2
2
abcd
!!!!

Also check the documentation for click.BaseCommand:

standalone_mode – the default behavior is to invoke the script in standalone mode. Click will then handle exceptions and convert them into error messages and the function will never return but shut down the interpreter. If this is set to False they will be propagated to the caller and the return value of this function is the return value of invoke().

  • Related