搜尋
首頁後端開發Python教學关于你不想知道的所有Python3 unicode特性

我的读者知道我是一个喜欢痛骂Python3 unicode的人。这次也不例外。我将会告诉你用unicode有多痛苦和为什么我不能闭嘴。我花了两周时间研究Python3,我需要发泄我的失望。在这些责骂中,仍然有有用的信息,因为它教我们如何来处理Python3。如果没有被我烦到,就读一读吧。

这次吐槽的内容会不一样。不会关联到WSGI或者HTTP及与其相关的东西。通常,我被告知我应该停止抱怨Python3 Unicode系统,因为我不写别人经常写的代码(HTTP库之类的东西),所以我这次准备写点别的东西:一个命令行应用程序。我写了一个很方便的库叫click来让编写它更加简单。

注意,我做的是每一个新手Python程序员做的事情:写一个命令行应用程序。Hello World程序。但是不同以往,我想要确保应用程序是稳定的并且对于Python2和Python3的Unicode都是支持的,还能够进行单元测试。所以接下来的就是如何来实现它。

我们想做什么

在Python3我们作为开发者需要好好使用Unicode。显然,我觉得这意味着所有的文本数据都是Unicode,所有非文本数据都是字节。在这么美妙的世界里所有的东西只有黑与白,Hello World的例子非常直截了当。所以让我们来写一些shell工具吧。

这是用Python2形式实现的应用程序:

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。

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。到此结束。

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
Python vs. C:了解關鍵差異Python vs. C:了解關鍵差異Apr 21, 2025 am 12:18 AM

Python和C 各有優勢,選擇應基於項目需求。 1)Python適合快速開發和數據處理,因其簡潔語法和動態類型。 2)C 適用於高性能和系統編程,因其靜態類型和手動內存管理。

Python vs.C:您的項目選擇哪種語言?Python vs.C:您的項目選擇哪種語言?Apr 21, 2025 am 12:17 AM

選擇Python還是C 取決於項目需求:1)如果需要快速開發、數據處理和原型設計,選擇Python;2)如果需要高性能、低延遲和接近硬件的控制,選擇C 。

達到python目標:每天2小時的力量達到python目標:每天2小時的力量Apr 20, 2025 am 12:21 AM

通過每天投入2小時的Python學習,可以有效提升編程技能。 1.學習新知識:閱讀文檔或觀看教程。 2.實踐:編寫代碼和完成練習。 3.複習:鞏固所學內容。 4.項目實踐:應用所學於實際項目中。這樣的結構化學習計劃能幫助你係統掌握Python並實現職業目標。

最大化2小時:有效的Python學習策略最大化2小時:有效的Python學習策略Apr 20, 2025 am 12:20 AM

在兩小時內高效學習Python的方法包括:1.回顧基礎知識,確保熟悉Python的安裝和基本語法;2.理解Python的核心概念,如變量、列表、函數等;3.通過使用示例掌握基本和高級用法;4.學習常見錯誤與調試技巧;5.應用性能優化與最佳實踐,如使用列表推導式和遵循PEP8風格指南。

在Python和C之間進行選擇:適合您的語言在Python和C之間進行選擇:適合您的語言Apr 20, 2025 am 12:20 AM

Python適合初學者和數據科學,C 適用於系統編程和遊戲開發。 1.Python簡潔易用,適用於數據科學和Web開發。 2.C 提供高性能和控制力,適用於遊戲開發和系統編程。選擇應基於項目需求和個人興趣。

Python與C:編程語言的比較分析Python與C:編程語言的比較分析Apr 20, 2025 am 12:14 AM

Python更適合數據科學和快速開發,C 更適合高性能和系統編程。 1.Python語法簡潔,易於學習,適用於數據處理和科學計算。 2.C 語法複雜,但性能優越,常用於遊戲開發和系統編程。

每天2小時:Python學習的潛力每天2小時:Python學習的潛力Apr 20, 2025 am 12:14 AM

每天投入兩小時學習Python是可行的。 1.學習新知識:用一小時學習新概念,如列表和字典。 2.實踐和練習:用一小時進行編程練習,如編寫小程序。通過合理規劃和堅持不懈,你可以在短時間內掌握Python的核心概念。

Python與C:學習曲線和易用性Python與C:學習曲線和易用性Apr 19, 2025 am 12:20 AM

Python更易學且易用,C 則更強大但複雜。 1.Python語法簡潔,適合初學者,動態類型和自動內存管理使其易用,但可能導致運行時錯誤。 2.C 提供低級控制和高級特性,適合高性能應用,但學習門檻高,需手動管理內存和類型安全。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境