热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Python“黑魔法”之Encoding&Decoding

首发于我的博客,转载请注明出处写在前面本文为科普文本文中的例子在Ubuntu14.04Python2.7.11下运行成功,Python3+的接口有些许不同,需要读者自行转换引子先看

首发于我的博客,转载请注明出处

写在前面

  • 本文为科普文
  • 本文中的例子在 Ubuntu 14.04 / Python 2.7.11 下运行成功,Python 3+ 的接口有些许不同,需要读者自行转换

引子

先看一段代码:

example.py

# -*- coding=yi -*-
从 math 导入 sin, pi
打印 'sin(pi) =', sin(pi)

这是什么?!是 Python 吗?可以运行吗?——想必你会问。

我可以明确告诉你:这不是 Python,但它可以用 Python 解释器运行。当然,如果你愿意,可以叫它 “Yython” (易语言 + Python)。

《Python “黑魔法” 之 Encoding & Decoding》

怎么做到的?也许你已经注意到第一行的奇怪注释——没错,秘密全在这里。

这种黑魔法,还要从 PEP 263 说起。

古老的 PEP 263

我相信 99% 的中国 Python 开发者都曾经为一个问题而头疼——字符编码。那是每个初学者的梦靥。

还记得那天吗?当你试图用代码向它示好:

print '你好'

它却给你当头一棒:

SyntaxError: Non-ASCII character '\xe4' in file chi.py on line 1, but no encoding declared

【一脸懵逼】

于是,你上网查找解决方案。很快,你便有了答案:

# -*- coding=utf-8 -*-
print '你好'

其中第一行的注释用于指定解析该文件的编码。

这个特新来自 2001 年的 PEP 263 — Defining Python Source Code Encodings,它的出现是为了解决一个反响广泛的问题:

In Python 2.1, Unicode literals can only be written using the Latin-1 based encoding “unicode-escape”. This makes the programming environment rather unfriendly to Python users who live and work in non-Latin-1 locales such as many of the Asian countries. Programmers can write their 8-bit strings using the favorite encoding, but are bound to the “unicode-escape” encoding for Unicode literals.

Python 默认用 ASCII 编码解析文件,给 15 年前的非英文世界开发者造成了不小的困扰——看来 Guido 老爹有些个人主义,设计时只考虑到了英文世界。

提案者设想:使用一种特殊的文件首注释,用于指定代码的编码。这个注释的正则原型是这样的:

^[ \t\v]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)

也就是说 # -*- coding=utf-8 -*- 并不是唯一的写法,只是 Emacs 推荐写法而已。诸如 # coding=utf-8# encoding: utf-8 都是合法的——因此你不必惊讶于他人编码声明与你不同。

正则的捕获组 ([-_.a-zA-Z0-9]+) 将会被用作查找编码的名称,查找到的编码信息会被用于解码文件。也就是说,import example 背后其实相当于有如下转换过程:

with open('example.py', 'r') as f:
cOntent= f.read()
encoding = extract_encoding_info(content) # 解析首注释
exec(content.decode(encoding))

问题其实又回到我们常用的 str.encodestr.decode 上来了。

可 Python 怎么这么强大?!几乎所有编码它都认得!这是怎么做到的?是标准库?还是内置于解释器中?

一切,都是 codecs 模块在起作用。

codecs

codecs 算是较为冷门的一个模块,更为常用的是 strencode/decode 的方法——但它们本质都是对 codecs 的调用。

打开 /path/to/your/python/lib/encodings/ 目录,你会发现有许多以编码名称命名的 .py 文件,如 utf_8.pylatin_1.py。这些都是系统预定义的编码系统,实现了应对各种编码的逻辑——也就是说:编码系统其实也是普通的模块。

除了内置的编码,用户也可以 自行定义编码系统codecs 暴露了一个 register 函数,用于注册自定义编码。register 签名如下:

codecs.register(search_function)
Register a codec search function. Search functions are expected to take one argument, the encoding name in all lower case letters, and return a CodecInfo object having the following attributes:

  • name: The name of the encoding;
  • encode: The stateless encoding function;
  • decode: The stateless decoding function;
  • incrementalencoder: An incremental encoder class or factory function;
  • incrementaldecoder: An incremental decoder class or factory function;
  • streamwriter: A stream writer class or factory function;
  • streamreader: A stream reader class or factory function.

encodedecode 是无状态的编码/解码的函数,简单说就是:前一个被编解码的字符串与后一个没有关联。如果你想用 codecs 系统进行语法树解析,解析逻辑最好不要写在这里,因为代码的连续性无法被保证;incremental* 则是有状态的解析类,能弥补 encodedecode 的不足;stream* 是流相关的解析类,行为通常与 encode/decode 相同。

关于这六个对象的具体写法,可以参考 /path/to/your/python/lib/encodings/rot_13.py,该文件实现了一个简单的密码系统。

那么,是时候揭开真相了。

所谓的 “Yython”

黑魔法其实并不神秘,照猫画虎定义好相应的接口即可。作为例子,这里只处理用到的关键字:

yi.py

# encoding=utf8
import codecs
yi_map = {
u'从': 'from',
u'导入': 'import',
u'打印': 'print'
}
def encode(input):
for key, value in yi_map.items():
input = input.replace(value, key)
return input.encode('utf8')
def decode(input):
input = input.decode('utf8')
for key, value in yi_map.items():
input = input.replace(key, value)
return input
class Codec(codecs.Codec):
def encode(self, input, errors="strict"):
input = encode(input)
return (input, len(input))
def decode(self, input, errors="strict"):
input = decode(input)
return (input, len(input))
class IncrementalEncoder(codecs.IncrementalEncoder):
def encode(self, input, final=False):
return encode(input)
class IncrementalDecoder(codecs.IncrementalDecoder):
def decode(self, input, final=False):
return decode(input)
class StreamWriter(Codec, codecs.StreamWriter):
pass
class StreamReader(Codec, codecs.StreamReader):
pass
def register_entry(encoding):
return codecs.CodecInfo(
name='yi',
encode=Codec().encode,
decode=Codec().decode,
incrementalencoder=IncrementalEncoder,
incrementaldecoder=IncrementalDecoder,
streamwriter=StreamWriter,
streamreader=StreamReader
) if encoding == 'yi' else None

在命令行里注册一下,就可以看到激动人心的结果了:

>>> import codecs, yi
>>> codecs.register(yi.register_entry)
>>> import example
sin(pi) = 1.22464679915e-16

结语

有时,对习以为常的东西深入了解一下,说不定会有惊人的发现。

References

  • codecs – Codec registry and base classes

推荐阅读
  • 主要用了2个类来实现的,话不多说,直接看运行结果,然后在奉上源代码1.Index.javaimportjava.awt.Color;im ... [详细]
  • Java 中 Writer flush()方法,示例 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 本文探讨了 Objective-C 中的一些重要语法特性,包括 goto 语句、块(block)的使用、访问修饰符以及属性管理等。通过实例代码和详细解释,帮助开发者更好地理解和应用这些特性。 ... [详细]
  • Docker的安全基准
    nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd ... [详细]
  • 本文介绍如何使用 Python 将一个字符串按照指定的行和元素分隔符进行两次拆分,最终将字符串转换为矩阵形式。通过两种不同的方法实现这一功能:一种是使用循环与 split() 方法,另一种是利用列表推导式。 ... [详细]
  • 本文详细探讨了KMP算法中next数组的构建及其应用,重点分析了未改良和改良后的next数组在字符串匹配中的作用。通过具体实例和代码实现,帮助读者更好地理解KMP算法的核心原理。 ... [详细]
  • 本文详细介绍了Java中org.neo4j.helpers.collection.Iterators.single()方法的功能、使用场景及代码示例,帮助开发者更好地理解和应用该方法。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • PyCharm中配置Pylint静态代码分析工具
    本文详细介绍如何在PyCharm中配置和使用Pylint,帮助开发者进行静态代码检查,确保代码符合PEP8规范,提高代码质量。 ... [详细]
  • Explore how Matterverse is redefining the metaverse experience, creating immersive and meaningful virtual environments that foster genuine connections and economic opportunities. ... [详细]
  • 本文介绍如何使用Objective-C结合dispatch库进行并发编程,以提高素数计数任务的效率。通过对比纯C代码与引入并发机制后的代码,展示dispatch库的强大功能。 ... [详细]
  • 技术分享:从动态网站提取站点密钥的解决方案
    本文探讨了如何从动态网站中提取站点密钥,特别是针对验证码(reCAPTCHA)的处理方法。通过结合Selenium和requests库,提供了详细的代码示例和优化建议。 ... [详细]
  • 题目描述:给定n个半开区间[a, b),要求使用两个互不重叠的记录器,求最多可以记录多少个区间。解决方案采用贪心算法,通过排序和遍历实现最优解。 ... [详细]
  • 前言--页数多了以后需要指定到某一页(只做了功能,样式没有细调)html ... [详细]
author-avatar
温尧乔761975
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有