Okay, I couldn't find a way to make cp or rsync by themselves do what you want, so I've come up with:
for n in dir1/* ; do ls ${n}/*.py >/dev/null 2>&1 ; if [[ $? == 0 ]]; then mkdir dir2/${n/dir1\//} ; cp ${n}/*.py dir2/${n/dir1\//} ; fi ; done
Indenting it properly makes it more readable:
for n in dir1/*
do
ls ${n}/*.py >/dev/null 2>&1
if [[ $? == 0 ]]
then
mkdir dir2/${n/dir1\//}
cp ${n}/*.py dir2/${n/dir1\//}
fi
done
So, what I'm doing is looking through dir1, assuming each file is a directory and trying to list any *.py files (with output and error disappearing into /dev/null). Obviously, trying this for py1.py and py2.py will fail because they are not directories. It should also fail for any directories that do not have any *.py files in them.
That means that when we are in the if, the result of the previous command ($?) should be something other than 0 and we do nothing. For the subdirs in dir1, the listing should succeed, and so $? will be 0, so we perform the bit inside the loop. That merely creates the subdir in dir2 (remembering to replace "dir1/" with nothing), and copies any *.py files across.
It's certainly not the only way to do this, and while it works for the example you gave, it doesn't work if you have extra levels of subdirs inside subdirs. (e.g. dir1/subdir1/subsubdir1/*.py
will not be copied across)