[Tutor] UTF-8 filenames encountered in os.walk
Kent Johnson
kent37 at tds.net
Wed Jul 4 20:47:45 CEST 2007
Terry Carroll wrote:
> I'm pretty iffy on this stuff myself, but as I see it, you basically have
> three kinds of things here.
>
> First, an ascii string:
>
> s = 'abc'
>
> In hex, this is 616263; 61 for 'a'; 62 for 'b', 63 for 'c'.
>
> Second, a unicode string:
>
> u = u'abc'
>
> I can't say what this is "in hex" because that's not meaningful. A
> Unicode character is a code point, which can be represented in a variety
> of ways, depending on the encoding used. So, moving on....
>
> Finally, you can have a sequence of bytes, which are stored in a string as
> a buffer, that shows the particular encoding of a particular string:
>
> e8 = s.encode("UTF-8")
> e16 = s.encode("UTF-16")
>
> Now, e8 and e16 are each strings (of bytes), the content of which tells
> you how the string of characters that was encoded is represented in that
> particular encoding.
I would say that there are two kinds of strings, byte strings and
unicode strings. Byte strings have an implicit encoding. If the contents
of the byte string are all ascii characters, you can generally get away
with ignoring that they are in an encoding, because most of the common
8-bit character encodings include plain ascii as a subset (all the
latin-x encodings, all the Windows cp12xx encodings, and utf-8 all have
ascii as a subset), so an ascii string can be interpreted as any of
those encodings without error. As soon as you get away from ascii, you
have to be aware of the encoding of the string.
encode() really wants a unicode string not a byte string. If you call
encode() on a byte string, the string is first converted to unicode
using the default encoding (usually ascii), then converted with the
given encoding.
>
> In hex, these look like this.
>
> e8: 616263 (61 for 'a'; 62 for 'b', 63 for 'c')
> e16: FFFE6100 62006300
> (FFEE for the BOM, 6100 for 'a', 6200 for 'b', 6300 for 'c')
>
> Now, superficially, s and e8 are equal, because for plain old ascii
> characters (which is all I've used in this example), UTF-8 is equivalent
> to ascii. And they compare the same:
>
>>>> s == e8
> True
They are equal in every sense, I don't know why you consider this
superficial. And if your original string was not ascii the encode()
would fail with a UnicodeDecodeError.
>
> But that's not true of the UTF-16:
>
>>>> s == e16
> False
>>>> e8 == e16
> False
>
> So (and I'm open to correction on this), I think of the encode() method as
> returning a string of bytes that represents the particular encoding of a
> string value -- and it can't be used as the string value itself.
The idea that there is somehow some kind of string value that doesn't
have an encoding will bring you a world of hurt as soon as you venture
out of the realm of pure ascii. Every string is a particular encoding of
character values. It's not any different from "the string value itself".
>
> But you can get that string value back (assuming all the characters map
> to ascii):
>
>>>> s8 = e8.decode("UTF-8")
>>>> s16 = e16.decode("UTF-16")
>>>> s == s8 == s16
> True
You can get back to the ascii-encoded representation of the string.
Though here you are hiding something - s8 and s16 are unicode strings
while s is a byte string.
In [13]: s = 'abc'
In [14]: e8 = s.encode("UTF-8")
In [15]: e16 = s.encode("UTF-16")
In [16]: s8 = e8.decode("UTF-8")
In [17]: s16 = e16.decode("UTF-16")
In [18]: s8
Out[18]: u'abc'
In [19]: s16
Out[19]: u'abc'
In [20]: s
Out[20]: 'abc'
In [21]: type(s8) == type(s)
Out[21]: False
The way I think of it is, unicode is the "pure" representation of the
string. (This is nonsense, I know, but I find it a convenient mnemonic.)
encode() converts from the "pure" representation to an encoded
representation. The encoding can be ascii, latin-1, utf-8... decode()
converts from the coded representation back to the "pure" one.
Kent
More information about the Tutor
mailing list