Винсент: Знаешь, что самое забавное в Европе?
Джулс: Что?
Винсент: Такие маленькие отличия. Там вроде все то же самое, что и здесь, но чуть-чуть отличается.
"Криминальное чтиво"
Как известно, ключевым отличием Python 3.x от Python 2.x является переориентация языка и стандартных библиотек со строк байтов на строки символов Unicode. Когда я решил повнимательнее посмотреть на это различие, пришлось копнуть вглубь и вширь, а результаты моих раскопок я оформил в статью, которую предлагаю вашему вниманию.
В Python 2 | В Python 3 |
---|---|
строковый литерал получает тип str |
строковый литерал получает тип str |
тип str представляет собой строку байтов |
тип str представляет собой строку символов Unicode |
для представления строки символов Unicode используется тип unicode |
для представления строки байтов используется тип bytes |
для представления строки unicode в нужной кодировке используется метод unicode.encode(кодировка) , возвращающий строку байтов str |
для представления строки str в нужной кодировке используется метод str.encode(кодировка) , возвращающий строку байтов bytes |
для преобразования строки байтов str в строку unicode используется метод str.decode(кодировка) , возвращающий строку unicode |
для преобразования строки байтов bytes в строку str используется метод bytes.decode(кодировка) , возвращающий строку str |
системная кодировка по умолчанию, она же кодировка исходных файлов по умолчанию, ASCII | системная кодировка по умолчанию, она же кодировка исходных файлов по умолчанию, UTF-8 |
при записи в файл строки по умолчанию преобразуются в системную кодировку по умолчанию | при записи в файл строки по умолчанию преобразуются в кодировку, определяемую пользовательскими настройками |
идентификаторы в программе содержат только символы ASCII, не могут использовать никаких других букв, кроме латинских | идентификаторы в программе содержат символы Unicode, могут использовать буквы других алфавитов, кроме латинского |
Мои эксперименты я ставлю под ОС Windows 7 в стандартной консоли.
Для разминки, несколько манипуляций со строковыми литералами и переменными в Python 2:
>>> s = 'Hello'
>>> s
'Hello'
>>> type(s)
<type 'str'>
>>> u = s.decode('ascii')
>>> u
u'Hello'
>>> type(u)
<type 'unicode'>
И в Python 3:
>>> s = 'Hello'
>>> s
'Hello'
>>> type(s)
<class 'str'>
>>> b = s.encode('ascii')
>>> b
b'Hello'
>>> type(b)
<class 'bytes'>
Теперь посмотрим на кодировки, используемые в Python 2 и Python 3 по умолчанию:
- Системная кодировка по умолчанию (system default encoding), возвращается функцией
sys.getdefaultencoding()
. Это кодировка исходных текстов Python по умолчанию, она же используется по умолчанию для кодирования и декодирования строкunicode
. - Кодировки стандартных потоков ввода, вывода и ошибок - файловых объектов, атрибуты
sys.stdin.encoding
,sys.stdout.encoding
иsys.stderr.encoding
, соответственно. Строки символов Unicode, посылаемые в канал вывода, преобразуются в соответствующую кодировку (или в системную кодировку по умолчанию, если атрибутencoding is None
). - Кодировка имен файлов в файловой системе, возвращается функцией
sys.getfilesystemencoding()
. Имена файлов, представленные в программе на Python как строки символов Unicode, преобразуются в эту кодировку для взаимодействия с файловой системой (или в системную кодировку по умолчанию, еслиsys.getfilesystemencoding()
возвращаетNone
). - Кодировка для текстовых данных, заданная пользовательскими настройками, возвращается функцией
locale.getpreferredencoding(False)
.
Кодировки в Python 2:
>>> import sys, locale
>>> sys.getdefaultencoding()
'ascii'
>>> sys.stdin.encoding
'cp866'
>>> sys.stdout.encoding
'cp866'
>>> sys.stderr.encoding
'cp866'
>>> sys.getfilesystemencoding()
'mbcs'
>>> locale.getpreferredencoding(False)
'cp1251'
Кодировки в Python 3:
>>> import sys, locale
>>> sys.getdefaultencoding()
'utf-8'
>>> sys.stdin.encoding
'cp866'
>>> sys.stdout.encoding
'cp866'
>>> sys.stderr.encoding
'cp866'
>>> sys.getfilesystemencoding()
'mbcs'
>>> locale.getpreferredencoding(False)
'cp1251'
Как видим, системной кодировкой по умолчанию для Python 2 является ascii
, а для Python 3 - utf-8
. И это единственное обнаруженное различие.
Нам, русским, очень "повезло" с обилием кириллических кодировок в Windows. Работая в консоли Windows, по умолчанию мы имеем дело с кириллической кодировкой cp866
. Работая с текстовым файлом в Блокноте, по умолчанию мы работаем в кириллической кодировке cp1251
. Имена файлов, использующие русские символы, в файловой системе Windows представлены в кодировке mbcs
(multi-byte character set), - это двухбайтовая кодировка, которая позволяет представить подмножество символов Unicode (UTF-16?).
Вооружившись знанием об используемых по умолчанию кодировках, попробуем в интерактивном режиме Pyhton вводить и выводить строки, включающие нелатинские символы.
Фрагмент интерактивного сеанса Python 2:
>>> u = u'Привет world'
>>> type(u)
<type 'unicode'>
>>> u
u'\u041f\u0440\u0438\u0432\u0435\u0442 world'
>>> print u, u.encode('cp866')
Привет world Привет world
Что я только что сделал?
Команды вводятся через стандартный входной поток, использующий кодировку cp866
(sys.stdin.encoding
). Таким образом, в первом предложении присваивания литерал u'Привет world'
преобразуется в строку unicode
из кодировки cp866
и полученная строка unicode
присваивается переменной u
. Далее я проверил тип и значение переменной u
и увидел, что русские буквы представлены двухбайтовыми кодами Unicode. Наконец, предложение print
посылает в стандартный выходной поток данную строку unicode
и строку str
, полученную преобразованием строки u
в кодировку cp866
. Результат вывода обеих строк одинаков, поскольку строка unicode
неявно преобразуется при выводе в стандартый выходной поток в кодировку cp866
(sys.stdout.encoding
).
В Python 3 получим такой результат (предлагаю интерпретировать его самостоятельно):
>>> s = 'Привет world'
>>> type(s)
<class 'str'>
>>> s
'Привет world'
>>> print(s, s.encode('cp866'))
Привет world b'\x8f\xe0\xa8\xa2\xa5\xe2 world'
Идем дальше.
В Python 2 для использования в скриптах не-ASCII символов нужно явно указывать кодировку исходного файла, поскольку системная кодировка по умолчанию, ascii
, подразумевает использование в файле только символов ASCII. А в Python 3 исходный файл по умолчанию содержит символы Unicode в кодировке utf-8
, что позволяет использовать в нем практически любые символы без явного указания кодировки.
Файл hello.py в кодировке UTF-8 для Python 2:
# -*- coding: utf-8 -*-
s = u'Привет world!'
print type(s), s, s.encode('cp866')
Выполню его в консоли Windows:
C:\_sandbox> c:\Python27\python.exe hello.py
<type 'unicode'> Привет world! Привет world!
Файл hello3.py в кодировке UTF-8 для Python 3:
# нас устраивает кодировка по умолчанию utf-8
s = 'Привет world!'
print(type(s), s, s.encode('cp866'))
Выполню его в консоли Windows:
C:\_sandbox> c:\Python33\python.exe hello3.py
<class 'str'> Привет world! b'\x8f\xe0\xa8\xa2\xa5\xe2 world!'
Разница с работой скрипта hello.py
в том, что теперь к кодировке стандартного потока вывода приводится значение типа str
, и строка байтов bytes
не интерпретируется как строка читабельных символов.
Следующий скрипт helloname.py
демонстрирует ввод и вывод кириллических символов в Python 2, используя для их хранения строки unicode
:
# -*- coding: utf-8 -*-
import sys
def uraw_input(prompt):
return unicode(raw_input(prompt.encode(sys.stdout.encoding)), sys.stdin.encoding)
name = uraw_input(u'Привет! Ваше имя? ')
print u'Привет, %s!' % name
Выполню скрипт в консоли Windows:
C:\_dev\GOLD>c:\Python27\python.exe helloname.py
Привет! Ваше имя? Андрей
Привет, Андрей!
Посмотрим теперь, что происходит при записи строк символов Unicode в файл. Если в Python 2 явно не преобразовывать выводимые в файл строки unicode
в строки str
с нужной кодировкой, то получим ошибку.
# -*- coding: UTF-8 -*-
with open('hello.txt', 'w') as f:
print 'File encoding:', f.encoding
f.write(u'Привет world!')
Выполню скрипт в консоли Windows:
C:\_sandbox> c:\Python27\python.exe hellofile.py
File encoding: None
Traceback (most recent call last):
File "hellofile.py", line 5, in
f.write(u'╨Я╤А╨╕╨▓╨╡╤В world!')
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-5: ordinal not in range(128)
При записи в файл Python 2 пытается преобразовать строку unicode
в системную кодировку по умолчанию, ascii
, поскольку атрибут encoding
открытого нами файла имеет значение None
. В кодировке ascii
нельзя представить кириллические символы, из-за чего и возникает ошибка.
Во избежание ошибок, при записи строк unicode
в файл нужно явно приводить их к желаемой кодировке:
# -*- coding: UTF-8 -*-
with open('hello.txt', 'w') as f:
print 'File encoding:', f.encoding
f.write(u'Привет world!'.encode('cp866'))
Выполню исправленный скрипт в консоли Windows:
C:\_sandbox> c:\Python27\python.exe hellofile.py
File encoding: None
C:\_sandbox> type hello.txt
Привет world!
Аналогичный эксперимент с Python 3 показывает, что строки пишутся в файл в кодировке, определяемой локалью пользователя!
with open('hello3.txt', 'w') as f:
print('File encoding:', f.encoding)
f.write('Привет world!')
Выполняю скрипт в консоли Windows:
C:\_sandbox> c:\Python33\python.exe hellofile3.py
File encoding: cp1251
Выше мы видели, что на русифицированной Windows функция locale.getpreferredencoding(False)
возвращает 'cp1251'
. Именно с этой кодировкой открывается новый файл по умолчанию, и, как следствие, к ней приводятся строки, записываемые в этот файл.
С кодировкой файла cp1251
запись в него смешанной латино-кириллической строки проходит на ура, а вот попытка записи кандзи вместе с кириллицей приводит к уже знакомой нам ошибке:
with open('hello31.txt', 'w') as f:
print('File encoding:', f.encoding)
f.write('Привет 世界!')
Выполняю скрипт в консоли Windows:
C:\_sandbox> c:\Python33\python.exe hellofile31.py
File encoding: cp1251
Traceback (most recent call last):
File "hellofile31.py", line 3, in
f.write('Привет \u4e16\u754c!')
File "c:\Python33\lib\encodings\cp1251.py", line 19, in encode
return codecs.charmap_encode(input,self.errors,encoding_table)[0]
UnicodeEncodeError: 'charmap' codec can't encode characters in position 7-8: character maps to
Кодировка cp1251
не кодирует кандзи!
Хорошая новость в том, что в Python 3, в отличие от Python 2, при открытии файла можно явно указать кодировку файла. В эту кодировку и будут преобразовываться строки str
при записи в файл; из этой кодировки будут преобразовываться в str
читаемые из файла строки байтов.
Укажу явно кодировку открываемых файлов в скрипте hello32.py
:
with open('hello31.txt', 'w', encoding='utf-8') as f:
print('File encoding:', f.encoding)
f.write('Привет world!')
with open('hello32.txt', 'w', encoding='utf-8') as f:
print('File encoding:', f.encoding)
f.write('Привет 世界!')
with open('hello31.txt', encoding='utf-8') as f:
print('File encoding:', f.encoding)
print(f.read())
Выполняю скрипт в консоли Windows:
C:\_sandbox> c:\Python33\python.exe hellofile32.py
File encoding: utf-8
File encoding: utf-8
File encoding: utf-8
Привет world!
Как видим, скрипт пишет в файл и читает из файла строки в кодировке utf-8
.
В заключение, экзотический пример кода. Вследствие того, что в Python 3 системной кодировкой по умолчанию является utf-8
, в Python 3 можно использовать в идентификаторах не только латиницу, но и другие символы Unicode:
>>> def привет_5_раз(имя):
... for i in range(5):
... print('Привет,', имя)
...
>>> привет_5_раз('Медвет')
Привет, Медвет
Привет, Медвет
Привет, Медвет
Привет, Медвет
Привет, Медвет
Сопровождать такой код и вносить в него изменения интернациональной команде разработчиков будет проблематично!
Проделанные сравнительные эксперименты не дали мне достаточно оснований, чтобы решительно встать на одну из сторон в священной войне между защитниками Python 2 и энтузиастами Python 3 :). Хотя Unicode-ориентированность Python 3 и то, как это сказывается на прикладном программировании, мне нравится.
Этот текст мне очень помог, спасибо.
ОтветитьУдалитьСпасибо! Приятно читать человека понимающего о чем он пишет.
ОтветитьУдалить