Several fixes for Python 3 behavior.

Attempts to throw a useful error if a file is passed in but not opened
in binary mode on Python 3. If a file-like object without a `mode` param
is passed in instead, then we make every attempt to decode to bytes,
falling back to UTF-8 if no encoding is specified.