while read F ; do foo arg $F ; done < /tmp/zz
You're redirecting the while loop's standard input from /tmp/zz (I'm assuming this is a file). That doesn't just include the read in the condition test, it includes all the statements in the body of the loop (Unless they're otherwise redirected, of course).
Your python code is only testing if standard input is a tty/terminal, and assuming that it's a pipe otherwise. But standard input can be from any type of open file descriptor that can be read from; tty, pipe, regular file, socket, etc.
A typical approach to accepting both filenames and standard input as a program's data is looking at the number of command line arguments; if it expects filenames but there aren't enough arguments to have any, read from standard input instead.
If using bash or zsh (And maybe other shells that extend basic POSIX sh), if you want to have a program inside a loop like this read from a script's original standard input, one technique is to redirect to a different descriptor than 0 (Standard input's) and tell read to read from it instead:
while read -r -u 3 f; do foo arg "$f" ; done 3< /tmp/zz
which opens the file /tmp/zz as descriptor number 3.