目录
我们想做什么
UNIX里的UNICODE
UNICODE疯狂
C Locale
Python3死于火焰
Python3 cat
跳起编码舞蹈
但你是错误的
首页 后端开发 Python教程 关于你不想知道的所有Python3 unicode特性

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

Jun 10, 2016 pm 03:18 PM
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

热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

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

深入了解PHP:JSON Unicode转中文的实现方法 深入了解PHP:JSON Unicode转中文的实现方法 Mar 05, 2024 pm 02:48 PM

深入了解PHP:JSONUnicode转中文的实现方法在开发中,我们经常会遇到需要处理JSON数据的情况,而JSON中的Unicode编码在一些场景下会给我们带来一些问题,特别是当需要将Unicode编码转换为中文字符时。在PHP中,有一些方法可以帮助我们实现这个转换过程,下面将介绍一种常用的方法,并提供具体的代码示例。首先,让我们先了解一下JSON中Un

unicode怎么转中文 unicode怎么转中文 Dec 14, 2023 am 10:57 AM

Unicode是一种字符编码标准,用于表示各种语言和符号。要将Unicode编码转换为中文字符,可使用Python的内置函数chr()和ord()。

解决Eclipse中文乱码问题的方法试试看 解决Eclipse中文乱码问题的方法试试看 Jan 03, 2024 pm 05:28 PM

Eclipse中文乱码困扰?试试这些解决方案,需要具体代码示例一、背景介绍随着计算机技术的不断发展,中文在软件开发中扮演着越来越重要的角色。然而,很多开发者在使用Eclipse进行中文开发时会遇到乱码问题,影响了工作效率。那么,本文将介绍一些常见的乱码问题,并给出相应的解决方案及代码示例,帮助读者解决Eclipse中文乱码问题。二、常见乱码问题及解决方案文件

PHP教程:如何将JSON Unicode转换为中文字符 PHP教程:如何将JSON Unicode转换为中文字符 Mar 05, 2024 pm 06:36 PM

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式,通常用于Web应用程序之间的数据交换。在处理JSON数据时,我们经常会遇到Unicode编码的中文字符(例如"u4e2du6587"),需要将其转换为可读的中文字符。在PHP中,我们可以通过一些简单的方法来实现这个转换。接下来,我们将详细介绍如何将JSONUnico

解决Java连接MySQL数据库时Unicode字符集编码不一致的问题 解决Java连接MySQL数据库时Unicode字符集编码不一致的问题 Jun 10, 2023 am 11:39 AM

随着大数据、云计算等技术的发展,数据库成为了企业信息化的重要基石之一。在Java开发的应用程序中,连接MySQL数据库已成为常态。然而,在这个过程中,我们常常会遭遇到一个棘手的问题——Unicode字符集编码不一致。这不仅会影响我们的开发效率,还会影响应用程序的性能和稳定性。本文将介绍如何解决这个问题,让Java连接MySQL数据库更顺畅。一、Unicode

unicode和ascii有哪些区别 unicode和ascii有哪些区别 Sep 06, 2023 am 11:56 AM

unicode和ascii的区别包括编码范围不同、存储空间不同和兼容性不同等。详细介绍:1、编码范围不同,ascii的编码范围是0-127,主要用于表示英语字母,而unicode的编码范围广阔得多,可以表示几乎所有的语言字符;2、存储空间不同,ascii通常使用1个字节来存储一个字符,而unicode可能使用2个或更多的字节来存储一个字符;3、兼容性不同等等。

Java中如何使用Unicode代理编程 Java中如何使用Unicode代理编程 May 06, 2023 pm 08:43 PM

顺序访问顺序访问是在Java语言中处理字符串的一个基本操作。在这种方法下,输入字符串中的每个字符从头至尾按顺序访问,或者有时从尾至头访问。本小节讨论使用顺序访问方法从一个字符串创建一个32位码位数组的7个技术示例,并估计它们的处理时间。示例1-1:基准测试(不支持代理对)清单1将16位char类型值直接分配给32位码位值,完全没有考虑代理对:清单1.不支持代理对int[]toCodePointArray(Stringstr){//Example1-1intlen=str.length();//t

PHP编程技巧:高效处理JSON数据中的Unicode转换 PHP编程技巧:高效处理JSON数据中的Unicode转换 Mar 05, 2024 pm 05:03 PM

在PHP开发过程中,处理JSON数据是非常常见的操作。然而,在处理含有Unicode字符的JSON数据时,可能会遇到一些问题,特别是在数据转换和编码转换方面。本文将介绍一些高效处理JSON数据中的Unicode转换的PHP编程技巧,并提供具体的代码示例。在处理含有Unicode字符的JSON数据时,通常会涉及到将Unicode字符进行转换和编码处理。在PHP

See all articles