又隔了好久好久没有写博客,过完了春节元宵清明五一一大堆节日,今年就没了一半了,所以需要抓紧时间充实一下。立好Flag以后看书一定要随时笔记不然厚厚的书翻起来跟天书一样难找。(顺便吐槽一下CSDN这个奇怪的Markdown编辑器真的真的好难用=o=)

前言

其实这是一篇读书笔记,主要是关于Python的几种基础数据类型,包括顺序结构(List、Tuple等)、哈希结构(Dict、Set等)以及文本和Bytes。这些每天都在打交道的类型其实并不像看上去的简单,简单了解一下背后的理论和一些相关的使用技巧有助于平时编码中提高效率和写出(没)优(人)雅(懂)的代码。
原书里面大概很多文字都没(看)什(不)么(懂)用,所以文章尽可能附上相关代码方便理解。

文本与Bytes

Python3将Python2的万能的str分成了text类型(Unicode)和bytes类型,反人类的拆分背后隐藏着怎样的秘密?

什么是字符串

字符串就是“字符”的“串”,问题在于什么是“字符”。
两个概念:

  • 字符的标识,即码位(code point),是0-1114111的数字构成的,在Unicode中使用4-6个十六进制的数字标示,前缀U+。例如:A-U+0041,€-U+20AC。
  • 代表字符的byte的表示方式取决于具体编码。例如:A-U+0041在UTF-8中编码成单个字节\x41,在UTF-16LE中编码为两个字节\x41\x00。

码位(code points)转字节序(bytes)叫编码,反之叫解码

>>> s = '你好'
>>> b = s.encode('UTF-8') #
>>> b  # 这是个bytes对象
b'\xe4\xbd\xa0\xe5\xa5\xbd'
>>> b.decode('UTF-8')
'你好'
>>> b.decode('UTF-16LE')
'뷤\ue5a0붥'
>>> len(s)
2
>>> len(b)
6
>>>

字节

震惊,下面这串东西的[0]和[0:1]的结果竟然不一样?

>>> my_char = bytes('很好玩的代码', encoding='utf_8')
>>> my_char
b'\xe5\xbe\x88\xe5\xa5\xbd\xe7\x8e\xa9\xe7\x9a\x84\xe4\xbb\xa3\xe7\xa0\x81'
>>> my_char[0]
229
>>> my_char[0:1]
b'\xe5'

好吧本来以为是str类型很好玩,其实Python里面的其他顺序类型也是这么做的,指定某个index的时候返回的是对应的元素,[a:b]切片的时候返回的是同类型的序列,只是str类型看起来像是比较奇怪,只返回了值。

>>> l1 = ['好', '玩', '的', '代', '码']
>>> l1[0]
'好'
>>> l1[0:1]
['好']

二进制序列的表示方法有几种,如果不知道什么叫二进制序列的表示方法,请看:

>>> s = 'cafe咖啡'
>>> s.encode('utf-8')
b'cafe\xe5\x92\x96\xe5\x95\xa1'
>>> s = """
... have
... a
... test
... """
>>> s.encode('utf-8')
b'\nhave\na \ntest\n'

cafe\xe5\x92\x96\xe5\x95\xa1

  • cafe和原文的cafe完全一致,因此二进制序列可以是ASCII字符本身
  • 换行、回车等和\对应的字节,使用\n、\t等表示
  • 其他字节的值,比如咖啡,使用十六进制转义序列

除了少数方法(formatformat_mapcasefoldisnumeric等)以外,str类型的方法同样支持bytesbytearray类型。例如endswithreplace等。re模块中的正则表达式也能处理二进制序列。
二进制序列有个类方法是str没有的,fromhex,用于从十六进制数字对构建二进制序列。(大概这东西也没什么用)

Struct和Memory Views

struct提供了一些把字节序列(\xe5\x96)转换成不同类型字段组成的元组的方法和逆向方法。struct模块可以处理bytesbytearraymemoryview对象。

>>> import struct
>>> fmt = '<3s3sHH'
>>> with open('filter.gif', 'rb') as fp:
...     img = memoryview(fp.read())  # 使用文件创建memoryview对象
...
>>> header = img[:10]  # memoryview切片再创建一个memoryview对象,但是memoryview的切片是共享内存地址的
>>> bytes(header)
b'GIF89a+\x02\xe6\x00'
>>> struct.unpack(fmt, header)  # 按照fmt规则unpack这个memoryview对象,得到一个元组
(b'GIF', b'89a', 555, 230)
>>> del header
>>> del img

虽然搞不懂有什么用不过看起来很厉害就是了。

编码器/解码器

这个小节最核心的点就是不要依赖系统的编码。因为不同系统的编码不一致,依赖系统编码会导致你的Python代码在不同系统上运行结果不一致。手动指定每次的编码器/解码器可以避免这个问题。

很显然不同的编码器对同一段字符串的编码得到的字节序列差异很大。开头已经说过,代表字符的byte的表示方式取决于具体编码:

>>> for codec in ['utf_8', 'utf_16', 'latin_1']:
...     print(codec, 'This is 同样的文字'.encode(codec), sep='\t')
...
utf_8   b'This is \xe5\x90\x8c\xe6\xa0\xb7\xe7\x9a\x84\xe6\x96\x87\xe5\xad\x97'  # UTF-8编码结果
utf_16  b'\xff\xfeT\x00h\x00i\x00s\x00 \x00i\x00s\x00 \x00\x0cT7h\x84v\x87eW['  # UTF-16编码结果
Traceback (most recent call last):File "<stdin>", line 2, in <module>
UnicodeEncodeError: 'latin-1' codec can't encode characters in position 8-12: ordinal not in range(256)  # Lartin-1不支持中文

不支持中文肯定不行,所以有一些处理UnicodeEncodeError的方法,包括将不支持的字符转跳过替换

>>> print(codec, 'This is 同样的文字'.encode('latin_1', errors='ignore'), sep='\t')
latin_1 b'This is '
>>> print(codec, 'This is 同样的文字'.encode('latin_1', errors='replace'), sep='\t')
latin_1 b'This is ?????'
>>> print(codec, 'This is 同样的文字'.encode('latin_1', errors='xmlcharrefreplace'), sep='\t')
latin_1 b'This is 同样的文字'

对应的,如果要将bytes解码,也会有UnicodeDecodeError。

>>> b = b'\xff\xfeT\x00h\x00i\x00s\x00 \x00i\x00s\x00 \x00\x0cT7h\x84v\x87eW['
>>> b.decode('utf-8')
Traceback (most recent call last):File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte  # 解码不了
>>> b.decode('utf-8', errors='replace')
'��T\x00h\x00i\x00s\x00 \x00i\x00s\x00 \x00\x0cT7h�v�eW['  # 代替
>>> b.decode('utf-8', errors='ignore')
'T\x00h\x00i\x00s\x00 \x00i\x00s\x00 \x00\x0cT7hveW['  # 忽略

使用预期之外的编码抛出SyntaxError

文件顶部加上注释

# coding: cp1252

Python3默认使用UTF-8,GUN/Linux和OS X默认都是UTF-8,Windows则不是,所以可能会报这种反人类的错误。因此,和前面说的一样,不要依赖系统编码,全部手动指定可以让你的脚本正常运行于不同系统。

怎样才能知道一段字节序列的编码

不可能。
不过可以通过某种编码的特定模式来猜测(也就是没有100%准确的方案,a.k.a 不可能)对应编码

BOM

>>> u16_en = 'El Niño'.encode('utf_16')
>>> u16_cn = '有鬼'.encode('utf_16')
>>> u16_en
b'\xff\xfeE\x00l\x00 \x00N\x00i\x00\xf1\x00o\x00'
>>> u16_cn
b'\xff\xfe\tg<\x9b'

奇怪,好像两段没有一个字相同的字符,但经过编码后的字节序列都是以\xff\xfe开头的。没错这就是BOM(byte-order mark),指明编码时使用小字节序(little-endian byte ordering)。
小字节序中,字母E的位码是U+0045,在字节便宜的第二位和第三位的编码为69和0;而大字节序中是编码顺序是相反的,E的编码为0和69。
因此需要区分开小字节序系统和大字节序系统。因为按照设计,U+FFFE 字符不存在,在小字节序编码中,字节序列 b’\xff
xfe’ 必定是 ZERO WIDTH NO-BREAK SPACE ,所以编解码器知道该用哪个字节序。

处理文本文件

处理文本的原则遵照“Unicode三明治”,就是尽可能早地把输入的字节序列转为字符串,让逻辑层只处理字符串对象。

>>> open('cafe.txt', 'w', encoding='utf_8').write('café')
4
>>> open('cafe.txt').read()

如果在Windows上,最后的输出可能就不是café了,因为首次打开文件的时候指定了UTF-8编码,而再次打开的时候没有指定编码,则会依照系统的默认编码。Linux上默认均为UTF-8,会给人一种代码没有问题的假象,实际上并不是这样的。
另外,如果在open的参数中声明是在二进制模式中读取文件,将会得到一个BufferedReader对象,而正常情况下会得到一个TextIOWrapper对象。

>>> f = open('cafe.txt','rb')
>>> f
<_io.BufferedReader name='cafe.txt'>
>>> f = open('cafe.txt','r')
>>> f
<_io.TextIOWrapper name='cafe.txt' mode='r' encoding='UTF-8'>

打开文件时没有指定encoding参数,编码会由locale.getpreferredencoding()指定,类似的还有一个用于编解码文件名的方法sys.getfilesystemencoding()

字符串对比

先看一段代码:

>>> s1 = 'café'
>>> s2 = 'cafe\u0301'
>>> s1, s2
('café', 'café')
>>> s1 == s2
False
>>> len(s1), len(s2)
(4, 5)

看到两种表示的café并不相等,因为Python看到的是不同的码位序列。解决办法是将Unicode规范化,使用unicodedata.normalize
normalize有4种参数: NFCNFDNFKCNFKD。前两个分别对应“使用最少的bytes构成等价字符串”和“把字符串分解成基本字符和单独的组合字符”,也就是类似于cafécafe\u0301两种形式;后两个分别是前两个的“兼容分解”模式,K表示“compatibility”,这样做格式会有所损失,例如1⁄2(这实际上是一个字符,1在上2在下)经过“兼容分解”后会变成1/2(这是3个字符)。
接上面的代码,经过规范化后对比返回True

>>> from unicodedata import normalize
>>> s1 == normalize('NFC', s2)
True
>>> s2 == normalize('NFD', s1)
True

大小写折叠(Case Fold)

字符串的casefold方法将文本变为小写,但是对比lower方法更加暴力

>>> 'ß'.casefold()
'ss'  # ß在德语中是“sharp s”
>>> 'ß'.lower()
'ß'

规范化总结

规范化待比较的字符串使用NFC,不区分大小写使用casefold

极端的“规范化”

如何将São Paulo规范化成Sao Paulo,尽管这样做会丢失信息?
作者的骚操作,不再展示,可以参考原书4.6.3节。

Unicode的文本排序

这里有个错误的排序:

>>> fruits = ['caju', 'atemoia', 'cajá', 'açaí', 'acerola']
>>> sorted(fruits)
['acerola', 'atemoia', 'açaí', 'caju', 'cajá']

期望得到的结果是ç按照c排序,á按照a排序:

['açaí', 'acerola', 'atemoia', 'cajá', 'caju']

非ASCII文本的标准排序方式是使用locale.strxfrm函数,这个函数的结果跟当前所在区域有关,通过使用locale.setlocale()改变所在区域以达到按照特定区域的习惯排序的效果。

>>> import locale
>>> locale.setlocale(locale.LC_COLLATE, 'zh_CN.UTF-8')
'zh_CN.UTF-8'
>>> fruits = ['caju', 'atemoia', 'cajá', 'açaí', 'acerola']
>>> sorted(fruits, key=locale.strxfrm)
['açaí', 'acerola', 'atemoia', 'cajá', 'caju']

Unicode 数据库

Unicode标准提供了一个完整的数据库,包括码位和字符名称之间的映射、各字符的元数据、字符之间的关系。

Python数据类型——String相关推荐

  1. python string类型_Python的基本数据类型——String

    Python的基本数据类型--String String是一个Unicode字符序列,是Python中最重要的数据类型之一,可以使用单引号.双引号.三引号创建创建一个字符串 a='1234' b=&q ...

  2. python数据类型-Python语言基本数据类型

    本文主要向大家介绍了Python语言基本数据类型,通过具体的内容向大家展示,希望对大家学习Python语言有所帮助. python标准数据类型 有六个标准的数据类型: 1.Number(数字) 2.S ...

  3. Python 数据类型及其用法

    本文总结一下Python中用到的各种数据类型,以及如何使用可以使得我们的代码变得简洁. 基本结构 我们首先要看的是几乎任何语言都具有的数据类型,包括字符串.整型.浮点型以及布尔类型.这些基本数据类型组 ...

  4. python中表示单一数据的类型被称为_各种Python数据类型的完整列表

    各种Python数据类型的完整列表 如今,Python是最受欢迎的编程语言之一.它允许开发人员将所有精力都放在实现上,而不是将复杂的程序和python中的数据类型用于此目的只是为了简化我们的工作. P ...

  5. python数据类型—字符串

    Python3.6.4的str的源代码 在Python的IDE工具pycharm中可以输入数据类型的名称后,ctrl + B,数据类型的源代码.str的部分源代码如下: str类的方法 capitai ...

  6. [转载] python 字符串(string)

    参考链接: Python字符串String 字符串 什么是字符串? 字符串是Python中最长用的数据类型.我们可以使用引号('或")来创建字符串.事实上,在Python中,加了引号的字符都 ...

  7. python数据类型_Python数据类型

    python数据类型 Python Data Types are used to define the type of a variable. Previously we learned about ...

  8. 【Python百日基础系列】Day03 - Python 数据类型

    文章目录 一.Python中的数据类型 1.1 数据类型系统是编程语言的核心 1.2 Python中的数据类型种类 1.3 数据类型的可变性 1.4 数据类型查看函数 - type() 二.Numbe ...

  9. 【python数据类型】

    python的数据类型 [数值类型] [字符串] [列表] [元组] [集合] [字典] **数据类型的总结** Python数据类型:数值.字符串.列表.元组.集合.字典. [数值类型] 数值类型有 ...

最新文章

  1. easyui 去掉按钮 虚线框
  2. 第九讲 二阶齐次常系数线性ODE
  3. Linux设备驱动开发概述
  4. 浅谈PopupWindow弹出菜单
  5. 深度学习pytorch--softmax回归(二)
  6. C++真的能够王者归来吗?
  7. oracle 查看用户、权限、角色
  8. 最课程学员启示录:一份有诚意的检讨书
  9. 每周荐书:SLAM、Vue2、爬虫(评论送书)
  10. no target device found怎么解决_关于移动端开发 1px 线的一些理解和解决办法
  11. mysql菜鸟手迹1--安装及目录介绍
  12. 易语言程序转c语言,C语言转易语言代码工具下载
  13. 小学生十大计算机专业书排行,小学教辅十大排行榜2018 小学教辅书那些比较好...
  14. RAID环境中增加容量-在线扩容
  15. 命里有时终须有,命里无时莫强求
  16. java怎么调字体_Java怎么设置字体
  17. webp的js插件_网页及CSS使用JS脚本加载webP图片
  18. grammarly word安装失败
  19. V2X主要用到什么技术?
  20. 台式机没声音怎么样才能解决

热门文章

  1. ESP32 开发笔记(三)源码示例 14_WIFI_Scan 附近WIFI信号扫描示例
  2. SwiftUI iOS 精品完成项目之宠物展示与领养App MVVM(教程含源码)
  3. 大二学生JavaScript实训大作业——动漫秦时明月7页 期末网页制作 html css javascript 网页设计实例 企业网站制作
  4. 车规级需要满足哪些规范?
  5. 压力传感器压力变送器如何选型
  6. 大学C语言系统作业,南昌大学作业答疑系统c语言答案
  7. 基于SSM在线服装商城购物网站设计
  8. 凡客登录页面html代码,简洁的凡客购物商城首页模板源码
  9. 神秘代码(链接至steam指南)
  10. python字典合并 同key_Python 合并两个字典(Dictionary)中相同key的value的方法及示例代码...