I have a small Python program that reads in SQL statements from a file and runs them on a MySQL database. The file is encoded in UTF-8 and the database also uses UTF-8.
If I don’t set the database encoding I get the usual error that everyone asks about “‘latin-1’ codec can’t encode character…”. So I set the database and file encoding using
con.set_character_set('utf8')
fh = codecs.open(fname,'r','utf8')
Now it works, but it also works when i don’t set the file encoding (or just use the builtin open), just in on the the database. By “works” I mean that the resulting database records display properly in WordPress which assumes UTF-8.
If I wanted magic, I’d code in Ruby. What is Python doing in this case and why was it not necessary to tell it the file encoding?
Needless to say I’ve done a lot of searching on this, and my Google-foo is usually pretty good. There are tons of posts here and in blogs on why it is necessary to set the encoding and how to do it, but I haven’t found any on why it sometimes just works.
Edit:
I ran a simple test on this using a file containing “Thank you.”
file
E2 80 9C 54 68 61 6E 6B 20 79 6F 75 2E E2 80 9D
codecs utf8
201C 54 68 61 6E 6B 20 79 6F 75 2E 201D
Attempting to read it with codecs.open(myfile,’r’,’ascii’) returned “UnicodeDecodeError: ‘ascii’ codec can’t decode byte 0xe2”
The read from file produced a byte string, so it appears that the magic is occurring on the insert into the database.
When you use
fh.read()returns a unicode. If you take this unicode and use your database driver (such as mysql-python) to insert data into your database, then the driver is responsible for converting the unicode into bytes. The driver is using the encoding set byIf you use
then
fh.read()returns a string of bytes. You are at the mercy of whatever bytes happened to be infname. Fortunately, according to your post, the file is encoded in UTF-8. Since the data is already a string of bytes, the driver does not perform any encoding, and simply communicates the string of bytes as is to the database.Either way, the same string of UTF-8 encoded bytes gets inserted into the database.
Let’s take a look at the source code defining codecs.open:
Notice in particular what happens if no
encodingis set:So
codecs.openis essentially the same as the builtinopenwhen no encoding is set. The builtinopenreturns a file object whosereadmethod returns a str object. It does no decoding at all.In contrast, when you specify an encoding
codecs.openreturns aStreamReaderWriterwithsrw.encodingset toencoding. Now when you call theStreamReaderWriter‘sreadmethod, a unicode object is returned — usually. First the str object must be decoded using the specified encoding.In your example, the
strobject isand if you specify the encoding as
'ascii', then theStreamReaderWritertries to decodecontentusing the'ascii'encoding:That’s not surprising since the
asciiencoding can only decode bytes in the range 0–127, and'\xe2', the first byte incontent, has ordinal value outside that range.For concreteness: When you don’t specify an encoding:
contentis astr.When you specify a valid encoding:
contentis aunicode.When you specify an invalid encoding:
You get a
UnicodeDecodeError.