It turns out there are two reasons for this, both stemming from the same piece of code in ImageFile.py
if self._exclusive_fp and self._close_exclusive_fp_after_loading:
self.fp.close()
self.fp = None
If a file path is passed into Image.open() then it will be closed, but even if a file is opened explicitly it will be closed by the garbage collector after self.fp is assigned to None.
The solution takes three changes:
_open(), set __close_exclusive_fp_after_loading=self.is_animatedopen_rel, preserve the file pointer with self._fp = self.fpseek(), restore the file pointer with self.fp = self._fp