It's about filename expansion that happens before the shell runs grep. It happens because there is unquoted, unescaped asterisk (*) in the command. Such asterisk is special to the shell.
When you try to run grep .*py and there is no file that matches .*py (note .*py is not treated as regex in this context, the dot is not special), basically two things can happen:
The POSIX behavior is the shell runs grep with literal .*py as an argument. This is what you want. This is how the POSIX shell (sh) and compatible shells behave.
Non-POSIX behavior is the shell detects there is no match, prints something like no matches found: .*py and doesn't run grep at all. This is how Zsh behaves by default. (For comparison: in Bash you can switch to this behavior by shopt -s failglob.)
If there was a match (in any shell), e.g. files named .copy, .foo.py etc., then .*py would be replaced with one or more words being names and grep would see them, not the literal .*py string. The command would do something you don't want (compare this answer of mine). You don't want filename expansion, even if there is a match to .*py. Quote the string to suppress filename expansion. In this case it's enough to quote or escape * only. All these should work:
grep -i '.*py'
grep -i ".*py"
grep -i .'*'py
grep -i .\*py
I prefer quoting the entire argument. In some circumstances other possibilities are more useful, it's good to know there's more than one way.