This is almost certainly determined by (the default) php.ini or equivalent setting:
max_execution_time = 30
TLDR: if you are certain that PHP is really exiting first, and something is killing your script, then you can either use daemon to wrap and monitor the Python process (in non-restarting mode, i.e. without --respawn), or add signal handers to the Python script.
In general, if you are able to run a shell as the userid of the script, then you should be able to strace or truss the process, e.g. on linux you can do this reasonably effectively:
$ sleep 60 &
[1] 10873
$ strace -e trace=signal,process -p $!
Process 10873 attached - interrupt to quit
+++ killed by SIGKILL +++
Process 10873 detached
The sleep process was terminated with a kill -9 from another terminal. kill -9 would not be common though, since a process would not be able to trap this and cleanly exit.
To monitor with daemon use a variation on:
daemon -i --errlog=/tmp/mypy.log -- /usr/bin/python [...]
This will log any signal related termination. Add --dbglog and --debug=2 if you want to log every exit regardless.
As noted elsewhere if the process has already terminated, it's gone. Only the parent (or init) would have been able to obtain its exit code, and unless it was logged (possibly using process accounting or auditing), the exit code is lost.
Internally for timeout handling on *nix platforms, PHP sets up a SIGALARM or SIGPROF with the specified timeout. That signal handler simply calls the internal zend_error() function. It does however also call any registered callbacks and error handlers, which may be of use to you.
If the default error handler kicks in, the PHP exit code will be, I believe, 255, since a timeout is an E_ERROR.
Also note, the convention of an exit code as 128+N, where N is the signal number, is due to the behaviour of certain shells (including bash) and many APIs. The actual process exit code after a signal is system dependent. It is probably just the signal number, this is generally the case on Linux. The wait() group of system calls provide better details of the process exit. PHP follows the shell convention, e.g. if you are using popen() and pclose() you will see 128+N as the exit code when a signal terminated the process.
Another consideration here is the behaviour of PHP time limits, if you check the set_time_limit() documentation you will see
The set_time_limit() function and the configuration directive max_execution_time only affect the execution time of the script itself. Any time spent on activity that happens outside the execution of the script such as system calls using system(), stream operations, database queries, etc. is not included when determining the maximum time that the script has been running. This is not true on Windows where the measured time is real.
You can prove this with a short script:
<?php
# for (;;);
$exe="sleep 20";
print "exit code: " . pclose(popen($exe, 'r'));
?>
Invoke with time php -d max_execution_time=5 -f sleep3.php.
The script will run for 20 seconds, there will be no error. If you kill the sleep process, e.g. kill -USR1 $(pgrep sleep) in another terminal, you will see an exit code of 138 (128+SIGUSR1).
If you uncomment the infinite for(;;) loop, the script will terminate after 5 seconds.