In your example, I would suggest that you raise error from the functions and only call the exit in __main__. The message will be passed using raise. Example:
import sys
def run_function(x):
if x == 4:
raise ValueError('error, x cannot be 4')
else:
print 'good x'
return 0
def main():
x=4
run_function(x)
return 0
if __name__ == "__main__":
try:
main()
except ValueError as e:
sys.exit(e)
This way, your function indicate that it received a wrong value, and it is the caller that decide to call a sys.exit based on the error.
A little more detail on the sys.exit:
The optional argument arg can be an integer giving the exit status
(defaulting to zero), or another type of object. If it is an integer,
zero is considered “successful termination” and any nonzero value is
considered “abnormal termination” by shells and the like. Most systems
require it to be in the range 0-127, and produce undefined results
otherwise. Some systems have a convention for assigning specific
meanings to specific exit codes, but these are generally
underdeveloped; Unix programs generally use 2 for command line syntax
errors and 1 for all other kind of errors. If another type of object
is passed, None is equivalent to passing zero, and any other object is
printed to stderr and results in an exit code of 1. In particular,
sys.exit("some error message") is a quick way to exit a program when
an error occurs.