资讯专栏INFORMATION COLUMN

unicode和utf8 —— 从一个遍历文件名的脚本,谈谈对Python2和Python3中字符编

宠来也 / 1323人阅读

摘要:如果传一个中文,下和下编码分别是和,可以自己用打印看看文件中写死,本来理解是跟这个文件本身编码有关,但文件编码同样是的情况下,下打印了的超集,下仍然是。

对编码问题一直一知半解,之前也是得过且过,正好有个同事要我帮忙写个脚本,涉及这方面的问题,借这个契机研究了一下.

先贴几篇比较好的:

1.阮老师的上古文章(07年…),虽然古老但对理解帮助很大,从最基础讲起,逻辑清晰易理解. (ps: 阮老师的博客都有此特点, 在这里推荐一波, 从js到linux, 精通前后端, 是可以当文档看的博客): http://www.ruanyifeng.com/blo...

2.最好看了上一篇再看这篇(解释了py2中为什么不能用 setdefaultencoding): https://blog.ernest.me/post/p...

3.关于UnicodeDecodeError: https://stackoverflow.com/que...

以及Python3的官方文档:https://docs.python.org/relea...

=============================================================================
建议以上几篇理解的差不多后再看正文:

简单说一下:
2.x中的编码概念是不够清晰的,str类型的对象会被赋予默认编码,且既可以对其编码又可以对其解码(单这一点就足够造成很多混乱…),而我们在代码中常直接使用带编码的str进行os库相关的操作,就容易导致很多问题。对于python内部来说,解释器处理操作系统的文件目录相关的东西时,必须使用unicode。新手如果要读取文件名并进行一些处理时,经常遇到乱码,以及windows和linux下效果不同的问题。另外一个主要场景就是stream,流处理,这个就是写文件或者前后端通信之类,这个相对前面问题来说其实还算好处理的。然后还有字符串拼接。

3.x去掉了 unicode类型 和 unicode()函数,(也就没有u"xxx"这种写法了),区分出str类型和bytes类型,而且str不再同时有encodedecode方法,bytes只有decodestr只有encode
3.x中,没有了unicode这个类型,可以理解为str成为了unicode类型,"All text is Unicode"。而带编码的字符串则由bytes类型来处理。但也不能简单地理解为3.x的str和bytes分别对应2.x的unicode和str。

所以2.x处理字符串原则其实也很简单,就是把str当成bytes,内部只用unicode,外部进的和出的都编码成str。

这里可能有个疑问就是,按之前的理解(假设已经读了第1篇)unicode是编码规则,但不是存储方式,uft8才是它的实现,才能用来存储,那么如果python内部是用unicode方式处理文本,在内存中python解释器如何正确读取字符呢?这里要理解清楚所谓实现,其实多的就是一个字节数的信息,unicode和utf8本质上都是一串0和1,只是缺一个字节数量的区分,即,从信息量上来说: unicode + 自身长度 = utf8。这样,在python解释器的处理过程中,python自然有办法用自己的标记来正确读写“自身长度”这个信息,因为这里不需要和外界交互,不需要类似utf8这样的约定规则,自己内部能正确获取信息即可。utf8是为了省硬盘空间,内存中不太需要这样的东西。(这段属于个人想当然的理解,仅供参考)

重点,重点,重点,贴一下py2中处理编码的原则(摘自上面第3篇),也就是我上面那句总结的完整版,如果你理解了为什么有这个原则说明差不多理解了py2的编码:

·所有 text string 都应该是 unicode 类型,而不是 str,如果你在操作 text,而类型却是 str,那就是在制造 bug。
·在需要转换的时候,显式转换。从字节解码成文本,用 var.decode(encoding),从文本编码成字节,用 var.encode(encoding)。
·从外部读取数据时,默认它是字节,然后 decode 成需要的文本;同样的,当需要向外部发送文本时,encode 成字节再发送。

除了上面几篇,百度还有无数其他的讲解,本篇就不再赘述原理之类的,上脚本讲下实际应用,脚本功能是递归遍历目录下所有文件:

#-*- coding:utf-8 -*-
"""
Description :  
递归遍历目录下所有文件(排除目录),并逐行写入到指定文件中。
可以分别用py2或py3来执行,结果相同。
可以不带参数,或者 python xxxx  

主要干两件事:
第一步,把文件路径解码成unicode,传给os用来遍历 (仅py2)
第二步,把文件名编码后写入文件
这样正好覆盖了上面提到的两个主要场景。
"""

""" 
Python2: str -> (decode) -> unicode -> (encode) -> str
Python3: bytes -> (decode) -> str(unicode) -> (encode) -> bytes 
"""

import sys
import os

try:
    PATH = sys.argv[1]
except IndexError:
    # 在这里写一个你能找到的名字最乱,里面文件名最杂的文件夹
    PATH = r"./"  # raw string, 表示不进行转义, 如果复制一个带反斜杠后面带数字或字母的路径, 不加上这个r就会出错
    
try:
    WRITE_PATH = sys.argv[2]
except IndexError:
    WRITE_PATH = "abc"   # 指定要写入的文件名


PY2 = sys.version.startswith("2")


if PY2:
    # 不理解编码的人经常用这个当做万能药,这个确实也有用,但严重不推荐使用,见第3篇
    # import sys
    # reload(sys)  
    # sys.setdefaultencoding("utf8")
    # PATH = PATH.decode()  # 这样就默认以utf8解码,由于上面的代码导致传进来的PATH会被默认编码为utf8


    # 记住原则,在python内处理文本字符串,永远保证是unicode类型,所以这里要进行解码。关于"ignore"参数见第4篇
    # 这里PATH不带中文时,无论哪种都会默认为ascii编码,带其他非ascii文字时,根据来源如果是:
    # 1. sys.argv传入,那么PATH的编码跟操作系统有关。如果传一个中文,windows下和linux下编码分别是ISO-8859-1和utf8,可以自己用chardet打印看看
    # 2. 文件中写死,本来理解是跟这个文件本身编码有关,但文件编码同样是utf8的情况下,windows下打印了Windows-1252(ISO-8859-1的超集),linux下仍然是utf8。所以还是跟操作系统有关
    # 这里默认在linux系统下执行,所以直接用utf8解了,如果要兼容,可以用chardet获取编码类型后指定进行解码
    PATH = PATH.decode("utf8", "ignore")

# if PY3,无论传入还是写死PATH都将会是```str```类型,当然也就不需要也不能进行解码啦


def getf(path):
    l = []
    res = os.listdir(path)
    for each in res:
        subpath = os.path.join(path, each)
        if os.path.isdir(subpath):
            l.extend(getf(subpath))
        else:
            l.append(each)

    return l

res = getf(PATH)



if PY2:
    # Python2, 由于py2中概念的模糊, 可以直接用"w"打开去写,而不需要"wb"
    # 不过不编码成utf8的话也是会抛UnicodeDecodeError的,写文件需要编码这个原则py2还是有的。可以检查一下 "%s
" % each 的类型毫无疑问是unicode
    with open(WRITE_PATH, "w") as f:
        for each in res:
            f.write(("%s
" % each).encode("utf8"))

else:
    # Python3, 可以用w打开然后不编码直接写string(即unicode),也是可以成功写的,不过那样结果是非ascii都乱码。
    # 而编了码就转为了bytes类型,所以Python3想正确实现就必须用二进制方式打开 (wb)
    # 如果打开方式和写入类型不对应,会抛TypeError,很明确
    with open(WRITE_PATH, "wb") as f:
        for each in res:
            f.write(("%s
" % each).encode("utf8"))

总结下代码,首先可以看到py3是没毛病的,对编码的操作概念清晰,没有任何困扰。

py2这块确实有硬伤,虽然很多时候也无所谓,但在要严谨或者专门处理编码的代码中,一定要记住开头贴的原则。

关于setdefaultencoding,除非实在不重要的场景,又需要临时简单解决,可以凑合一下,普通场景不建议用.
原因第3篇解释得很清楚。另附官方文档的说明如下:
set the current default string encoding used by the Unicode implementation. If name does not match any available encoding, LookupError is raised. This function is only intended to be used by the site module implementation and, where needed, by sitecustomize. Once used by the site module, it is removed from the sys module’s namespace.

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/41384.html

相关文章

  • 聊聊Python 3 字符串:str bytes 区别

    摘要:的字符串有两种和,的字符串也有两种和。不同字符的不同表现,让的和显得扑朔迷离。在中,严格区分了和,不同类型之间操作就会抛出的异常。和之间的转换一图胜千言和的相互转换指的是具体的编码规则的名称,对于中文来说,它可以是这些值等等。 Python2的字符串有两种:str 和 unicode,Python3的字符串也有两种:str 和 bytes。Python2 的 str 相当于 Pytho...

    AWang 评论0 收藏0
  • Python学习之路23-文本字节序列

    摘要:字符编码表,码位码元将编码字符集中的码位转换成有限比特长度的整型值的序列。字符编码方案,码元序列化也称为常说的序列化。每个字节里的二进制数就是字节序列。另一个情况则是压缩字节序列的值,如或进程长度编码等无损压缩技术。 《流畅的Python》笔记。本篇主要讲述不同编码之间的转换问题,比较繁杂,如果平时处理文本不多,或者语言比较单一,没有多语言文本处理的需求,则可以略过此篇。 1. 前言 ...

    anRui 评论0 收藏0
  • 关于python解码(decode, encode)

    摘要:,,等属于不同的字符集,转换编码就是在它们中的任意两者间进行。一般个人用的电脑上控制台基本上都是编码的,但运维的机器上基本全是,中文的时候就会有酸爽的问题。 总结总结,本文仅适用于python2.x 默认编码与开头声明 首先是开头的地方声明编码 # coding: utf8 这个东西的用处是声明文件编码为utf8(要写在前两行内),不然文件里如果有中文,比如 a = 美丽 b = u美...

    shusen 评论0 收藏0
  • Python入门-基本语法1

    摘要:根据有效范围作用域分为全局变量和局部变量。类型以开头标识类型以开头标识类型以进制的字节码表示,实际上是一个字节串,回应了它的另一个名字。 < 返回索引页 基本语法 Hello World 代码注释 关键字 数据类型 变量、常量 变量 变量赋值 变量命名 变量的作用域 常量 字符串与编码 字符转义 字符编码 字符串操作 运算符与表达式 运算符 表达式 ...

    ingood 评论0 收藏0
  • Python知识点整理(day1)

    摘要:上一步中执行时,明确的指出脚本由解释器来执行。为了表示更多的中文汉字有了,但是,中华文化博大精深,发现不够用,因此有了对的扩展即。但是,用编码对于英文只占一个字节,,一个中文汉字在却占三个字节可能是中国人有钱啊,我大天朝。 简介: Python是一种解释型语言,需要解释器来执行。可以通过在IDLE下执行,也可以在文本文件里写入代码,然后将该文件命名为xx.py 然后在Windows下可...

    Luosunce 评论0 收藏0

发表评论

0条评论

宠来也

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<