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

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

对编码问题一直一知半解,之前也是得过且过,正好有个同事要我帮忙写个脚本,涉及这方面的问题,借这个契机研究了一下.先贴几篇比较好的:1.阮老师的上古文章(07年…),虽然古老但对理解

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

先贴几篇比较好的:

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\n" % each 的类型毫无疑问是unicode
with open(WRITE_PATH, 'w') as f:
for each in res:
f.write(("%s\n" % 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\n" % 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.


推荐阅读
  • 怎么在PHP项目中实现一个HTTP断点续传功能发布时间:2021-01-1916:26:06来源:亿速云阅读:96作者:Le ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 本文介绍了在Mac上搭建php环境后无法使用localhost连接mysql的问题,并通过将localhost替换为127.0.0.1或本机IP解决了该问题。文章解释了localhost和127.0.0.1的区别,指出了使用socket方式连接导致连接失败的原因。此外,还提供了相关链接供读者深入了解。 ... [详细]
  • mac php错误日志配置方法及错误级别修改
    本文介绍了在mac环境下配置php错误日志的方法,包括修改php.ini文件和httpd.conf文件的操作步骤。同时还介绍了如何修改错误级别,以及相应的错误级别参考链接。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • 图像因存在错误而无法显示 ... [详细]
  • 本文介绍了Android中的assets目录和raw目录的共同点和区别,包括获取资源的方法、目录结构的限制以及列出资源的能力。同时,还解释了raw目录中资源文件生成的ID,并说明了这些目录的使用方法。 ... [详细]
  • ***byte(字节)根据长度转成kb(千字节)和mb(兆字节)**parambytes*return*publicstaticStringbytes2kb(longbytes){ ... [详细]
  • AFNetwork框架(零)使用NSURLSession进行网络请求
    本文介绍了AFNetwork框架中使用NSURLSession进行网络请求的方法,包括NSURLSession的配置、请求的创建和执行等步骤。同时还介绍了NSURLSessionDelegate和NSURLSessionConfiguration的相关内容。通过本文可以了解到AFNetwork框架中使用NSURLSession进行网络请求的基本流程和注意事项。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • C语言注释工具及快捷键,删除C语言注释工具的实现思路
    本文介绍了C语言中注释的两种方式以及注释的作用,提供了删除C语言注释的工具实现思路,并分享了C语言中注释的快捷键操作方法。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • HDFS2.x新特性
    一、集群间数据拷贝scp实现两个远程主机之间的文件复制scp-rhello.txtroothadoop103:useratguiguhello.txt推pushscp-rr ... [详细]
author-avatar
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有