Adakah gotcha yang menggunakan unicode_literals dengan Python 2.6?

101

Kami sudah menjalankan basis kode kami di bawah Python 2.6. Untuk mempersiapkan Python 3.0, kami telah mulai menambahkan:

dari __future__ import unicode_literals

ke dalam .pyfile kami (saat kami memodifikasinya). Saya bertanya-tanya apakah ada orang lain yang telah melakukan ini dan mengalami masalah yang tidak jelas (mungkin setelah menghabiskan banyak waktu untuk debugging).

Jacob Gabrielson
sumber

Jawaban:

101

Sumber utama masalah yang saya alami saat bekerja dengan string unicode adalah saat Anda mencampur string yang dikodekan utf-8 dengan yang unicode.

Misalnya, perhatikan skrip berikut.

two.py

# encoding: utf-8
name = 'helló wörld from two'

one.py

# encoding: utf-8
from __future__ import unicode_literals
import two
name = 'helló wörld from one'
print name + two.name

Output dari menjalankan python one.pyadalah:

Traceback (most recent call last):
  File "one.py", line 5, in <module>
    print name + two.name
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 4: ordinal not in range(128)

Dalam contoh ini, two.nameadalah string yang dienkode utf-8 (bukan unicode) karena tidak diimpor unicode_literals, dan one.namemerupakan string unicode. Saat Anda mencampur keduanya, python mencoba mendekode string yang dikodekan (dengan asumsi itu ascii) dan mengubahnya menjadi unicode dan gagal. Ini akan berhasil jika Anda melakukannya print name + two.name.decode('utf-8').

Hal yang sama dapat terjadi jika Anda menyandikan string dan mencoba mencampurnya nanti. Misalnya, ini berfungsi:

# encoding: utf-8
html = '<html><body>helló wörld</body></html>'
if isinstance(html, unicode):
    html = html.encode('utf-8')
print 'DEBUG: %s' % html

Keluaran:

DEBUG: <html><body>helló wörld</body></html>

Tetapi setelah menambahkan import unicode_literalsitu TIDAK:

# encoding: utf-8
from __future__ import unicode_literals
html = '<html><body>helló wörld</body></html>'
if isinstance(html, unicode):
    html = html.encode('utf-8')
print 'DEBUG: %s' % html

Keluaran:

Traceback (most recent call last):
  File "test.py", line 6, in <module>
    print 'DEBUG: %s' % html
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 16: ordinal not in range(128)

Gagal karena 'DEBUG: %s'merupakan string unicode dan oleh karena itu python mencoba memecahkan kode html. Beberapa cara untuk memperbaiki hasil cetak adalah melakukan print str('DEBUG: %s') % htmlatau print 'DEBUG: %s' % html.decode('utf-8').

Saya harap ini membantu Anda memahami potensi gotcha saat menggunakan string unicode.

Koba
sumber
11
Saya akan menyarankan untuk menggunakan decode()solusi daripada solusi str()atau encode(): semakin sering Anda menggunakan objek Unicode, semakin jelas kodenya, karena yang Anda inginkan adalah memanipulasi string karakter, bukan array byte dengan pengkodean yang tersirat secara eksternal.
Eric O Lebigot
8
Harap perbaiki terminologi Anda. when you mix utf-8 encoded strings with unicode onesUTF-8 dan Unicode bukan merupakan 2 pengkodean yang berbeda; Unicode adalah standar dan UTF-8 adalah salah satu pengkodean yang didefinisikannya.
Kos
11
@Kos: Saya pikir dia berarti mencampur "utf-8 encoded string" objek dengan unicode (maka decode) objek . Yang pertama adalah tipe str, yang terakhir adalah tipe unicode. Karena objek yang berbeda, masalah dapat muncul jika Anda mencoba untuk menjumlahkan / menggabungkan / menginterpolasi mereka
MestreLion
Apakah ini berlaku untuk python>=2.6atau python==2.6?
joar
16

Juga di 2.6 (sebelum python 2.6.5 RC1 +) literal unicode tidak cocok dengan argumen kata kunci ( issue4978 ):

Kode berikut ini misalnya berfungsi tanpa unicode_literals, tetapi gagal dengan TypeError: keywords must be stringjika unicode_literals digunakan.

  >>> def foo(a=None): pass
  ...
  >>> foo(**{'a':1})
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
      TypeError: foo() keywords must be strings
mfazekas.dll
sumber
17
FYI saja, python 2.6.5 RC1 + telah memperbaikinya.
Mahmoud Abdelkader
13

Saya menemukan bahwa jika Anda menambahkan unicode_literalsdirektif, Anda juga harus menambahkan sesuatu seperti:

 # -*- coding: utf-8

ke baris pertama atau kedua file .py Anda. Jika tidak, garis seperti:

 foo = "barré"

mengakibatkan kesalahan seperti:

SyntaxError: Karakter non-ASCII '\ xc3' dalam file mumble.py pada baris 198,
 tetapi tidak ada pengkodean yang dideklarasikan; lihat http://www.python.org/peps/pep-0263.html
 untuk detailnya
Jacob Gabrielson
sumber
5
@IanMackinnon: Python 3 mengasumsikan bahwa file UTF8 secara default
endolith
3
@endolith: Tetapi Python 2 tidak, dan itu akan memberikan kesalahan sintaks jika Anda menggunakan karakter non-ascii bahkan dalam komentar ! Jadi IMHO # -*- coding: utf-8adalah pernyataan yang hampir wajib terlepas dari apakah Anda menggunakan unicode_literalsatau tidak
MestreLion
Tidak -*-diperlukan; jika Anda memilih cara yang kompatibel dengan emacs, saya pikir Anda akan membutuhkannya -*- encoding: utf-8 -*-(lihat juga -*-di bagian akhir). Yang Anda butuhkan hanyalah coding: utf-8(atau bahkan =bukan : ).
Chris Morgan
2
Anda mendapatkan kesalahan ini apakah Anda atau tidak from __future__ import unicode_literals.
Flimm
3
Kompatibilitas Emacs membutuhkan # -*- coding: utf-8 -*- "pengkodean" (bukan "pengkodean" atau "pengkodean file" atau yang lainnya - Python hanya mencari "pengkodean" terlepas dari awalan apapun).
Alex Dupuy
7

Juga pertimbangkan bahwa unicode_literalakan mempengaruhi eval()tetapi tidak repr()(perilaku asimetris yang imho adalah bug), yaitu eval(repr(b'\xa4'))tidak akan sama dengan b'\xa4'(seperti pada Python 3).

Idealnya, kode berikut akan menjadi invarian, yang seharusnya selalu berfungsi, untuk semua kombinasi unicode_literalsdan penggunaan Python {2.7, 3.x}:

from __future__ import unicode_literals

bstr = b'\xa4'
assert eval(repr(bstr)) == bstr # fails in Python 2.7, holds in 3.1+

ustr = '\xa4'
assert eval(repr(ustr)) == ustr # holds in Python 2.7 and 3.1+

Pernyataan kedua berhasil, karena repr('\xa4')dievaluasi dengan u'\xa4'Python 2.7.

hvr
sumber
2
Saya merasa masalah yang lebih besar di sini adalah Anda menggunakan repruntuk meregenerasi sebuah objek. The reprdokumentasi jelas menyatakan bahwa ini bukan keharusan. Menurut pendapat saya, ini mengacu reprpada sesuatu yang hanya berguna untuk debugging.
jpmc26
5

Masih ada lagi.

Ada pustaka dan bawaan yang mengharapkan string yang tidak mentolerir unicode.

Dua contoh:

builtin:

myenum = type('Enum', (), enum)

(sedikit esotik) tidak berfungsi dengan unicode_literals: type () mengharapkan string.

Perpustakaan:

from wx.lib.pubsub import pub
pub.sendMessage("LOG MESSAGE", msg="no go for unicode literals")

tidak berfungsi: pustaka wx pubsub mengharapkan tipe pesan string.

Yang pertama bersifat esoterik dan mudah diperbaiki

myenum = type(b'Enum', (), enum)

tetapi yang terakhir ini menghancurkan jika kode Anda penuh dengan panggilan ke pub.sendMessage () (yang milik saya).

Sial, eh?!?

GreenAsJade
sumber
3
Dan jenis barang juga bocor ke metaclass - jadi di Django string apa pun yang Anda nyatakan class Meta:seharusnyab'field_name'
Hamish Downer
2
Ya ... dalam kasus saya, saya menyadari bahwa itu sepadan dengan usaha untuk mencari dan mengganti semua string sendMessage dengan versi b '. Jika Anda ingin menghindari pengecualian "decode" yang ditakuti, tidak ada yang seperti menggunakan unicode secara ketat dalam program Anda, mengubah input dan output seperlunya ("sandwich unicode" mengacu pada beberapa makalah yang saya baca tentang topik tersebut). Secara keseluruhan, unicode_literals telah menjadi kemenangan besar bagi saya ...
GreenAsJade