惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LINUX DO - 最新话题
A
Arctic Wolf
I
Intezer
V
Vulnerabilities – Threatpost
C
Cisco Blogs
MyScale Blog
MyScale Blog
NISL@THU
NISL@THU
Y
Y Combinator Blog
C
CERT Recently Published Vulnerability Notes
P
Privacy International News Feed
H
Hackread – Cybersecurity News, Data Breaches, AI and More
酷 壳 – CoolShell
酷 壳 – CoolShell
Recorded Future
Recorded Future
云风的 BLOG
云风的 BLOG
S
SegmentFault 最新的问题
Microsoft Security Blog
Microsoft Security Blog
L
LangChain Blog
博客园 - 聂微东
博客园 - 叶小钗
F
Fortinet All Blogs
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Recent Announcements
Recent Announcements
C
Cyber Attacks, Cyber Crime and Cyber Security
Latest news
Latest news
Simon Willison's Weblog
Simon Willison's Weblog
P
Palo Alto Networks Blog
S
Schneier on Security
C
Cybersecurity and Infrastructure Security Agency CISA
V
V2EX
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The Hacker News
The Hacker News
博客园 - 司徒正美
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
L
LINUX DO - 热门话题
罗磊的独立博客
K
Kaspersky official blog
Last Week in AI
Last Week in AI
Know Your Adversary
Know Your Adversary
小众软件
小众软件
Stack Overflow Blog
Stack Overflow Blog
T
Threat Research - Cisco Blogs
D
DataBreaches.Net
Scott Helme
Scott Helme
P
Proofpoint News Feed
P
Privacy & Cybersecurity Law Blog
P
Proofpoint News Feed
博客园 - 三生石上(FineUI控件)
Hugging Face - Blog
Hugging Face - Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
F
Full Disclosure

hsfzxjy 的博客

解决 VSCode + CMake + MSVC 编译器信息乱码的问题 使用 3090 部署 1.58bit 动态量化版 DeepSeek R1 671b 如何在 VS Code DevContainer 中配置 HTTP 代理 如何在跳板机背后的服务器上使用 VS Code Remote - Containers Cohesive Digests for Ints and Floats Rust 中的隐匿概念 —— Place(位置) 美术馆 一尺之槌,日取其半,1075日而竭 老生常谈:使用 Cloudflare 自选 IP 加速站点访问 辩义 State、Nation 与 Country 将 Base64 编码的数据快速转换为 Uint8Array 折腾 NPU·第1章 —— 搭建 Level Zero 开发环境 折腾 NPU·第0章 —— Intel NPU 概述与 Level-Zero 新增域名 monad.run CSS 中为特定字符设置不同字体 Arbitary Lifetime Transmutation via Rust Unsoundness Dijkstra 算法的延伸 Manacher 回文计数算法 硬卧 Go Fact: Zero-sized Field at the Rear of a Struct Has Non-zero Size Display *big.Rat Losslessly and Smartly in Golang 代码的仪式 Building Electron From Scratch 中式亲属称谓研究之一:构建半群 Some Notes on Kotlin Coroutines Git sparse-checkout and partial clones for Mega-Repos 辩义“封建” Diving from the CUDA Error 804 into a bug of libnvidia-container Modern Cryptography, GPG and Integration with Git(hub) Move the Root Partition of Ubuntu A New Programmer Kicks a Roadblock Git-based Dependencies in Dart and Go Reversy Naming 人类一败涂地 Invalid Golang Pointers Can Bite You Even If You Don't Dereference Side Project(副业) A Flaw of Promoting Complex Trait Bounds in Rust Initialize Process Pool Worker with Individual Value Rust - Python FFI From Scratch [Extending Hexo For My Site] Part 1 [Extending Hexo For My Site] Part 0 Debug a 'torch.tensor(1).cuda()' hanging 不自由的互联网 Retrieve Contents over HTTP without curl or wget [Unravelling mocona] Part 1 - Verbosity or Anti-Pattern [Unravelling mocona] Part 0 - Preface Understanding pickle in Python Rough Notes on Deploying Vaultwarden & NextCloud Bookmarks 语言狂热者与实用主义者 Demystify the randomness in CUDA kernels Performant Bulk Mutations in IndexedDB Auto Rebuild .pyx Files with pyximport Cython and Threads Obtain a Random Available TCP Port with Bash Information Theory: KL Divergence Information Theory: Entropy and Mutual Information 铁板烧 西郊线 Proof of the Gumbel Max Trick Option::as_ref Rc, RefCell and Interior Mutability Visualizing Correlation 三月十日杂感 三月一日杂感 二月十一日杂感 一月二十六日杂感 SS Configuration 一月七日杂感 四月·病 Haskell 笔记:State Monad Haskell 笔记:Monad 引论 Haskell 笔记:Applicative Haskell 笔记:Category Theory and Functor Haskell 笔记:data, type, newtype Haskell 笔记:folds 使用 Aria2 在 Ubuntu 中下载百度云资源 从伪并行的 Python 多线程说起 一个 Reentrant Error 引发的对 Python 信号机制的探索和思考 Linux 文件权限 HSFZMUN 4.0 部署小记 午后雨·科大 最后的雨夜·广州 揭秘·变态的平方根倒数算法 神坑·Python 装饰类无限递归 Ubuntu 重新映射键盘布局 为什么我要翻墙 Python“黑魔法”之 Generator Coroutines 数学美 之 判断线段相交的最简方法 除夕杂感 17 行代码实现的简易 Javascript 字符串模板 Python“黑魔法”之 Meta Classes 诗集 生活,需要被“发现” 家书·十八岁成人礼 炫技?还是需求? 【译】响应式图片的现状 【译】“为什么有这么多的编程语言?” Wisecity 商赛总结——也谈前端自动化测试 记一次 DoS 诈骗网站的经历 那一年,我们望向星空
Python“黑魔法”之 Encoding & Decoding
2016-07-21 · via hsfzxjy 的博客

写在前面

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

引子

先看一段代码:

example.py

# -*- conding=yi -*-

从 math 导入 sin, pi

打印 'sin(pi) =', sin(pi)

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

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

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

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

古老的 PEP 263

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

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

它却给你当头一棒:

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

【一脸懵逼】

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

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

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

In Python 2.1, Unicode literals can only be written using the

Latin-1 based enconding "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 enconding, but are bound to the "unicode-escape" enconding
for Unicode literals.

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

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

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

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

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

with open('example.py', 'r') as f:
content = f.read()
enconding = extract_enconding_info(content)
exec(content.decode(enconding))

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

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

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

codecs

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

打开 /path/to/your/python/lib/encondings/ 目录,你会发现有许多以编码名称命名的 .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 enconding name in all lower case letters, and return a CodecInfo object having the following attributes:

  • name: The name of the enconding;
  • encode: The stateless enconding function;
  • decode: The stateless deconding 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/encondings/rot_13.py,该文件实现了一个简单的密码系统。

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

所谓的“Yython”

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

yi.py



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(enconding):
return codecs.CodecInfo(
name='yi',
encode=Codec().encode,
decode=Codec().decode,
incrementalencoder=IncrementalEncoder,
incrementaldecoder=IncrementalDecoder,
streamwriter=StreamWriter,
streamreader=StreamReader
) if enconding == 'yi' else None

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

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

结语

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

References


作者:hsfzxjy
链接:
许可:CC BY-NC-ND 4.0.
著作权归作者所有。本文不允许被用作商业用途,非商业转载请注明出处。