国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

腳本之家,腳本語言編程技術及教程分享平臺!
分類導航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務器之家 - 腳本之家 - Python - 關于你不想知道的所有Python3 unicode特性

關于你不想知道的所有Python3 unicode特性

2020-05-16 14:26腳本之家 Python

我的讀者知道我是一個喜歡痛罵Python3 unicode的人。這次也不例外。我將會告訴你用unicode有多痛苦和為什么我不能閉嘴。我花了兩周時間研究Python3,我需要發泄我的失望。在這些責罵中,仍然有有用的信息,因為它教我們如何來處理

我的讀者知道我是一個喜歡痛罵Python3 unicode的人。這次也不例外。我將會告訴你用unicode有多痛苦和為什么我不能閉嘴。我花了兩周時間研究Python3,我需要發泄我的失望。在這些責罵中,仍然有有用的信息,因為它教我們如何來處理Python3。如果沒有被我煩到,就讀一讀吧。

這次吐槽的內容會不一樣。不會關聯到WSGI或者HTTP及與其相關的東西。通常,我被告知我應該停止抱怨Python3 Unicode系統,因為我不寫別人經常寫的代碼(HTTP庫之類的東西),所以我這次準備寫點別的東西:一個命令行應用程序。我寫了一個很方便的庫叫click來讓編寫它更加簡單。

注意,我做的是每一個新手Python程序員做的事情:寫一個命令行應用程序。Hello World程序。但是不同以往,我想要確保應用程序是穩定的并且對于Python2和Python3的Unicode都是支持的,還能夠進行單元測試。所以接下來的就是如何來實現它。

我們想做什么

 

在Python3我們作為開發者需要好好使用Unicode。顯然,我覺得這意味著所有的文本數據都是Unicode,所有非文本數據都是字節。在這么美妙的世界里所有的東西只有黑與白,Hello World的例子非常直截了當。所以讓我們來寫一些shell工具吧。

這是用Python2形式實現的應用程序:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
import sys
import shutil
 
for filename in sys.argv[1:]:
  f = sys.stdin
  if filename != '-':
    try:
      f = open(filename, 'rb')
    except IOError as err:
      print >> sys.stderr, 'cat.py: %s: %s' % (filename, err)
      continue
  with f:
    shutil.copyfileobj(f, sys.stdout)

顯然,命令在處理任何命令行選項的時候也不是特別好,不過至少能夠用。所以我們開始碼代碼吧。

UNIX里的UNICODE

 

上面的代碼在Python2是不行的,因為你暗中處理字節。命令行參數是字節,文件名是字節,文件內容也是字節。語言衛道士會指出這是不對的,這樣會引發問題,但如果你開始更多考慮它,你會發現這是個不固定的問題。

UNIX是字節,已經被定義成了這樣,并且一直會是這樣。為了理解為什么你需要觀察數據傳輸的不同場景。

  • 終端
  • 命令行參數
  • 操作系統輸入輸出層
  • 文件系統驅動

順便提一下,這不是數據可能通過的唯一東西,但是我們來了解一下,在多少場景下我們能了解一個編碼。答案是一個也沒有。至少我們需要理解一個編碼是終端輸出區域信息。這個信息可以用來展現轉換,也能夠理解文本信息所擁有的編碼。

舉個例子,如果LC_CTYPE的值為en_US.utf-8告訴應用程序系統使用US English,并且大部分文本數據是utf-8編碼。實際上還有很多別的變量,不過我們假定這是我們唯一需要看的。注意LC_CTYPE并不代表所有的數據都是utf-8編碼的。它代替通知應用程序如何分類文本特性并且什么時候需要應用轉換。

這很重要,原因是因為c locale。c locale是POSIX唯一指定的現場,它說所有ASCII編碼和來自命令行工具的回復會按照POSIX spec里定義的來對待。

在我們上面的cat工具里,如果它是比特,沒有別的方法來對待這些數據。原因是shell里沒有指定這數據是什么。例如你調用cat hello.txt,終端會在對應用程序編碼的時候對hello.txt進行編碼。

但是現在想想這個例子echo *。Shell會把目前目錄的所有文件名傳遞給你的應用程序。那它們是什么編碼?文件名沒有編碼!

UNICODE瘋狂

 

現在一個用Windows的人看到這里會說:弄UNIX的人在搞什么呢。但這還不算悲慘。產生這些工作的原因是一些聰明的人設計得這個系統能夠向后兼容。不像Windows把每個API都定義兩次,在POSIX上,最好的處理方法是為了顯示的目的將其假定為字節,用默認的編碼方式來編碼。

用上面的cat命令來舉例。比如有一個關于文件無法打開的錯誤信息,原始是因為它們不存在或者它們是受保護的,或者其他任何的原因。我們假定文件是用latin1編碼的,因為它是來自1995年外部驅動。終端會獲取標準輸出,它將會試著把它用utf-8編碼,因為這是它認為的編碼。因為字符串是latin1編碼的,因為它無法順利得解碼。但是不怕,不會有什么崩潰,因為你的終端在無法處理它的時候會無視它。

它在圖形界面上怎樣?每種有兩個版本。在一個像Nautilus 這樣的圖形界面上列出所有的文件。它把文件名和圖標關聯起來,能夠雙擊并且試著使文件名能夠顯示出來,因而把它解碼。例如它會嘗試用utf-8解碼,錯誤的地方用問題記號來替代。你的文件名可能不是完全可讀的但那是你仍能打開文件。

UNIX上的unicode只在你強制所有東西用它的時候會很瘋狂。但那不是unicode在UNIX上工作的方式。UNIX沒有區別unicode和字節的API。它們是相同的,使其更容易處理。

C Locale

 

C Locale在這里出現的次數非常多。C Locale是避免POSIX的規格被強行應用到任何地方的一種手段。POSIX服從操作系統需要支持設置LC_CTYPE,來讓一切使用ASCII編碼。

這個locale是在不同的情況下挑選的。你主要發現這個locale為所有從cron啟動的程序,你的初始化程序和子進程提供一個空的環境。C Locale在環境里復原了一個健全的ASCII地帶,否則你無法信任任何東西。

但是ASCII這個詞指出它是7bit編碼。這不是問題,因為操作系統是能處理字節的!任何基于8bit的內容能正常處理,但你與操作系統遵循約定,那么字符處理會限制在前7bit。任何你的工具生成的信息它會用ASCII編碼并且使用英語。

注意POSIX規范沒有說你的應用程序應當死于火焰。

Python3死于火焰

 

Python3在unicode上選擇了與UNIX不同的立場。Python3說:任何東西是Unicode(默認情況下,除非是在某些情況下,除非我們發送重復編碼的數據,可即使如此,有時候它仍然是Unicode,雖然是錯誤的Unicode)。文件名是Unicode,終端是Unicode,stdin和stdout是Unicode,有如此多的Unicode。因為UNIX不是Unicode,Python3現在的立場是它是對的UNIX是錯的,人們也應該修改POSIX的定義來添加Unicode。那么這樣的話,文件名就是Unicode了,終端也是Unicode了,這樣也就不會看到一些由于字節導致的錯誤了。

不是僅僅我這樣說。這些是Python關于Unicode的腦殘想法導致的bug:

  • ASCII是很槽糕的文件名編碼
  • 用surrogateescape作為默認error handler
  • Python3在C locale下拋出Unicode錯誤
  • LC CTYPE=C,pydoc給終端留下一個不能使用的狀態

如果你Google一下,你就能發現如此多的吐槽。看看有多少人安裝pip模塊失敗,原因是changelog里的一些字符,或者是因為home文件夾的原因又,或者是因為SSH session是用ASCII的,或者是因為他們是使用Putty連接的。

Python3 cat

 

現在開始為Python3修復cat。我們如何做?首先,我們需要處理字節,因為有些東西可能會顯示一些不符合shell編碼的東西。所以無論如何,文件內容需要是字節。但我們也需要打開基本輸出來讓它支持字節,而它默認是不支持的。我們也需要分別處理一些情況比如Unicode API失敗,因為編碼是C。那么這就是,Python3特性的cat。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import sys
import shutil
 
def _is_binary_reader(stream, default=False):
  try:
    return isinstance(stream.read(0), bytes)
  except Exception:
    return default
 
def _is_binary_writer(stream, default=False):
  try:
    stream.write(b'')
  except Exception:
    try:
      stream.write('')
      return False
    except Exception:
      pass
    return default
  return True
 
def get_binary_stdin():
  # sys.stdin might or might not be binary in some extra cases. By
  # default it's obviously non binary which is the core of the
  # problem but the docs recomend changing it to binary for such
  # cases so we need to deal with it. Also someone might put
  # StringIO there for testing.
  is_binary = _is_binary_reader(sys.stdin, False)
  if is_binary:
    return sys.stdin
  buf = getattr(sys.stdin, 'buffer', None)
  if buf is not None and _is_binary_reader(buf, True):
    return buf
  raise RuntimeError('Did not manage to get binary stdin')
 
def get_binary_stdout():
  if _is_binary_writer(sys.stdout, False):
    return sys.stdout
  buf = getattr(sys.stdout, 'buffer', None)
  if buf is not None and _is_binary_writer(buf, True):
    return buf
  raise RuntimeError('Did not manage to get binary stdout')
 
def filename_to_ui(value):
  # The bytes branch is unecessary for *this* script but otherwise
  # necessary as python 3 still supports addressing files by bytes
  # through separate APIs.
  if isinstance(value, bytes):
    value = value.decode(sys.getfilesystemencoding(), 'replace')
  else:
    value = value.encode('utf-8', 'surrogateescape') \
      .decode('utf-8', 'replace')
  return value
 
binary_stdout = get_binary_stdout()
for filename in sys.argv[1:]:
  if filename != '-':
    try:
      f = open(filename, 'rb')
    except IOError as err:
      print('cat.py: %s: %s' % (
        filename_to_ui(filename),
        err
      ), file=sys.stderr)
      continue
  else:
    f = get_binary_stdin()
 
  with f:
    shutil.copyfileobj(f, binary_stdout)

這不是最差的版本。不是因為我想讓事情更加復雜,它現在就是有這么復雜。例如在例子里沒有做的是在讀取一個二進制的東西是強制清理文本stdout。在這個例子里沒有必要,是因為這里的print調用去了stderr而不是stdout,但如果你想打印一些stdout,你就必須清理。為什么?因為stdout是別的緩沖區之上的緩沖區,如果你不強制清理它,你的輸出順序可能會出錯。

不僅僅是我,例如看:twisted's compat module ,會發現相同的麻煩。

跳起編碼舞蹈

 

為了理解shell里的命令行參數,順便說一些Python3里最糟糕的情況:

  1. shell把文件名以字節傳給腳本
  2. 字節在命中你的代碼前被Python以預期的解碼方式解碼。因為這是有損好的過程,Python3使用一個特別的錯誤處理器來處理解碼錯誤。
  3. Python代碼處理一個沒有錯誤的文件,并且需要格式化一個錯誤信息。因為我們寫文本流的時候如果它不是非法的unicode,是不會寫替代的。
  4. 將包含替代的unicode串編碼為utf-8,然后告訴它處理替代轉義。
  5. 然后我們從utf-8解碼并告訴他忽略錯誤
  6. 結果字符串回到只有文本的流里
  7. 之后終端會解碼我們的字符串來進行顯示

以下是Python2里的情況:

  1. shell把文件名作為字節傳給腳本
  2. shell解碼字符串來進行顯示

因為Python2版本里的字符串處理只是在出錯的時候進行糾正,因為shell在顯示文件名時能做得更好。

注意這沒有讓腳本更不對。如果你需要對輸入數據進行實際的字符串處理,你就要在2.x和3.x里面切換到unicode處理。但在那種情況,你也想讓你的腳本支持一個—charset參數,那么在2.x和3.x上做的工作差不多。只是在3.x上會更加糟糕,你需要構建在2.x上不需要的二進制標準輸出。

但你是錯誤的

 

很顯然我錯了,我被人告訴這些:

  • 我感到痛苦是因為我不像初學者那樣思考,新的unicode系統會對初學者更友好
  • 我不考慮windows用戶和新的文本模型對windows用戶是多么大的改進
  • 問題不在于Python,問題在POSIX規范
  • Linux發行版需要開始支持C.UTF-8,因為它們被過去一直阻礙著
  • 問題是SSH發送了錯誤的編碼。SSH需要修復這個問題。
  • Python3里一大堆unicode錯誤的真正問題是人們不傳遞明確的編碼而假設Python3作出了正確的決定。
  • 我與分解代碼工作,顯然這在Python3里會更難。
  • 我應該去改進Python3而不是在twitter和博客上抱怨
  • 你在沒有問題的地方制造問題。讓每個人修復他們的環境和對任何東西進行編碼就很好。這是用戶的問題。
  • Java有這個問題好多年了,這對開發者來說沒問題。

你知道嗎?我在做HTTP方面的工作的時候就停止了抱怨,因為我接受了這個主意,就是HTTP/WSGI的一大堆問題對人們來說很平常。但你知道什么?在Hello World這樣的情況下也有相同的問題。可能我應該放棄獲得一個高質量的unicode支持的庫,就這么將就了。

我可以對以上觀點進行反駁,但最終也沒關系了。如果Python3是我唯一使用的Python語言,我會解決所有的問題并且使用它開發。有一個完美的另一個語言叫Python2,它有更大的用戶基礎,并且用戶基礎是很牢固的。這時我是非常沮喪的。

Python3可能足夠強大,會開始讓UNIX走Windows走過的路:在很多地方使用unicode,但我很懷疑這樣的做法。

更可能的事情是人們仍舊使用Python2,并且用Python3做一些很爛的東西。或者他們會用Go。這門語言使用了與Python2很相似的模型:任何東西都是字節串。并且假設其編碼是UTF-8。到此結束。

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 中文字幕一区在线观看视频 | 在线观看日韩av | 免费一级黄色毛片 | 亚洲精品久久久一区二区三区 | 91久久精品国产91久久性色tv | 一区二区在线免费观看 | 久久精品中文字幕一区二区 | 国产日韩精品一区 | 在线观看国产 | 亚色在线 | 免费观看av | 九九综合| 一区二区三区国产 | 国产中文字幕一区 | 91亚洲日本aⅴ精品一区二区 | 日韩欧美国产一区二区 | 亚洲成av人片一区二区梦乃 | 亚洲欧洲精品视频 | 在线a视频网站 | 黄色毛片网站在线观看 | 天堂中文视频在线观看 | 久热免费在线视频 | 国产欧美在线观看 | 中文字幕天堂在线 | 久久中文在线观看 | 黄色影片免费在线观看 | 国产精品久久久久国产a级 国产色 | 亚洲一区二区av | 国产中文| 国产区免费在线观看 | 久久中文字幕一区 | 美女羞羞网站 | 亚洲免费视频观看 | 亚洲电影在线观看 | 欧美日韩精品一区二区三区 | 高清一区二区三区 | 亚洲精品久久久久一区二区三区 | 麻豆91视频 | 中文字幕第一页在线 | 毛片久久久 | 欧美精品久久一区 |