├── Python 3 实现 markdown 文本解析器-2.md ├── README.md ├── 第1章-python数据模型-1.md ├── 第1章-python数据模型-2.md ├── 第1章-python数据模型-3.md ├── 第1章-python数据模型-4.md ├── 第1章-python数据模型-5.md ├── 第2章-序列数组-1.md ├── 第2章-序列数组-2.md ├── 第3章-字典和集合-1.md ├── 第3章-字典和集合-2.md ├── 第3章-字典和集合-3.md ├── 第3章-字典和集合-4.md ├── 第3章-字典和集合-5.md └── 第4章-文本🆚字节-1.md /Python 3 实现 markdown 文本解析器-2.md: -------------------------------------------------------------------------------- 1 | Python 3 实现 markdown 文本解析器 2 | 一、课程描述 3 | 4 | 本次课程我们将使用 Python 3 打造 Markdown 文本解析器,并且程序支持输出 HTML 格式与 PDF 格式的文件。 5 | 6 | 1.1 课程知识点 7 | 8 | 通过本次课程的学习,我们将接触到以下知识点: 9 | 10 | 正则表达式 11 | docopt 构建命令行解析器 12 | 简单的 HTML 语法 13 | 1.2 试验流程 14 | 15 | 实验的流程为: 16 | 17 | 附加依赖安装 18 | 编程实现 19 | 运行程序 20 | 1.3 实验截图 21 | 22 | 转换效果如下: 23 | 24 | 此处输入图片的描述 25 | 26 | 此处输入图片的描述 27 | 28 | 此处输入图片的描述 29 | 30 | 二、附加依赖安装 31 | 32 | 在安装附加依赖软件包之前,先更新已安装的软件包,避免在安装的过程中出现问题。 33 | 34 | $ sudo apt-get update 35 | 2.1 安装 wkhtmltopdf 36 | 37 | wkhtmltopdf 是一款能将 HTML 文件转化为 PDF 文件的工具,支持 UNIX 平台与Windows 平台。 38 | 39 | $ sudo apt-get install wkhtmltopdf 40 | 更多参考: 41 | 42 | wkhtmltopdf 官方网站 43 | 2.2 安装 docopt 44 | 45 | docopt 是 Python 的一个第三方参数解析库,可以根据使用者提供的文档描述自动生成解析器。因此使用者可以用它来定义参数与解析参数。 46 | 47 | $ sudo pip3 install docopt 48 | 更多参考: 49 | 50 | docopt 官网 51 | 三、编程实现 52 | 53 | 先来考虑一下该程序需要实现哪些功能。 54 | 55 | 能够解析命令行参数,根据参数进行相应的处理,比如将目标文件转换为 HTML 文件或者转换为 PDF 文件。 56 | 解析目标文件,输出 HTML 文件。 57 | 根据需要将 HTML 文件转化为 PDF 文件。 58 | 对于第一点我们可以使用 docopt 库来构建命令行解析器,而第三点 HTML 转换可以使用 wkhtmltopdf 工具来进行转换。 59 | 60 | 至于第二点,Markdown 文本解析实际上就是文件翻译工作,即将 Markdown 中规定的特殊字符根据语法规则转换成相应的 HTML 标签,从而实现解析工作。 61 | 62 | 进入 Code 文件夹创建 shiyanlou_cs708 文件夹作为项目目录,之后该项目的所有文件都位于该文件夹中。 63 | 64 | 3.1 构建命令行解析器 65 | 66 | 进入文件夹shiyanlou_cs708, 创建 md2pdf.py 文件,可以使用 gedit 或 vim 进行编辑编写文档,或者使用 web IDE 。 67 | 68 | 在下方的代码中,在程序段首我们编写了一段程序帮助文档,该文档默认由 __doc__ 变量引用,通过将该变量作为参数传递给 docopt 方法可以快速构建起命令行解析器。然后我们还导入了所有需要用到的模块。 69 | 70 | """md2pdf 71 | 72 | translates markdown file into html or pdf, and support picture insertion. 73 | 74 | Usage: 75 | md2pdf [options] 76 | 77 | Options: 78 | -h --help show help document. 79 | -v --version show version information. 80 | -o --output translate sourcefile into html file. 81 | -p --print translate sourcefile into pdf file and html file respectively. 82 | -P --Print translate sourcefile into pdf file only. 83 | """ 84 | 85 | import os,re 86 | import sys,getopt 87 | from enum import Enum 88 | from subprocess import call 89 | from functools import reduce 90 | 91 | from docopt import docopt 92 | 93 | __version__ = '1.0' 94 | 95 | # 主函数 96 | def main(): 97 | # 定义输出的 HTML 文件默认文件名 98 | dest_file = "translation_result.html" 99 | # 定义输出的 PDF 文件的默认文件名 100 | dest_pdf_file = "translation_result.pdf" 101 | # 该选项决定了是否保留作为转化的 HTML 临时文件 102 | only_pdf = False 103 | 104 | # 使用帮助文档构建命令行解析器 105 | args = docopt(__doc__, version=__version__) 106 | 107 | dest_file = args[''] if args['--output'] else dest_file 108 | 109 | dest_pdf_file = args[''] if args['--print'] or args['--Print'] else "" 110 | # 进行解析 111 | run(args[''], dest_file, dest_pdf_file, args['--Print']) 112 | 113 | 114 | if __name__=="__main__": 115 | main() 116 | 3.1.1 docopt 使用介绍 117 | 118 | 这里为了方便大家理解 docopt 的使用,我们先偏离一下主题单独讲解 docopt 模块。首先为了获得最直观的感受,我们不妨在以上代码中添加一句 print(args) 来打印解析器参数解析结果。 119 | 120 | 此处输入图片的描述 121 | 122 | 测试结果如上图,我们发现 docopt 库返回了一个字典,字典中的键由我们给出的 Usage 与 Options 中的选项构成。 123 | 124 | 所以 docopt(__doc__, version=__version__) 函数能自动根据三个双引号中的文档内容(存储于 __doc__ 中)生成命令行解析器,并将解析结果以字典对象返回。因此使用 docopt 进行命令行解析,我们唯一需要做好的事情就是编写好帮助文档,然后其它一切解析的问题都可以甩手给 docopt 自动解决。 125 | 126 | 补充:关于帮助文档的编写格式以及关于 docopt 的更多信息请自行前往 docopt 官网查阅文档 127 | 3.2 Markdown 语法规则 128 | 129 | 在正式编写解析程序之前,我们需要先规定相应的语法规则,这里我没有完全遵照标准的 Markdown 语法规则来设定。 130 | 131 | 标题支持 132 | 133 | # ---------h1 134 | ##---------h2 135 | ###--------h3 136 | ####-------h4 137 | #####------h5 138 | ######-----h6 139 | 分割线 140 | 141 | --------(至少三个 - ) 142 | 列表 143 | 144 | # 有序 145 | 1. 146 | 2. 147 | 148 | # 无序 149 | - 150 | + 151 | 链接 152 | 153 | \[text](url) # 超链接 154 | ![image](url) # 图像 155 | 公式 156 | 157 | $行内公式$ 158 | 159 | $$行间公式$$ 160 | 3.3 run 程序编写 161 | 162 | run 函数负责解析 Markdown 文本,是项目程序的精华所在。 163 | 164 | ... 165 | # 定义三个枚举类 166 | # 定义表状态 167 | class TABLE(Enum): 168 | Init = 1 169 | Format = 2 170 | Table = 3 171 | 172 | # 有序序列状态 173 | class ORDERLIST(Enum): 174 | Init = 1 175 | List = 2 176 | 177 | # 块状态 178 | class BLOCK(Enum): 179 | Init = 1 180 | Block = 2 181 | CodeBlock = 3 182 | 183 | # 定义全局状态,并初始化状态 184 | table_state = TABLE.Init 185 | orderList_state = ORDERLIST.Init 186 | block_state = BLOCK.Init 187 | is_code = False 188 | is_normal = True 189 | 190 | temp_table_first_line = [] 191 | temp_table_first_line_str = "" 192 | 193 | need_mathjax = False 194 | 195 | ... 196 | 197 | def run(source_file, dest_file, dest_pdf_file, only_pdf): 198 | # 获取文件名 199 | file_name = source_file 200 | # 转换后的 HTML 文件名 201 | dest_name = dest_file 202 | # 转换后的 PDF 文件名 203 | dest_pdf_name = dest_pdf_file 204 | 205 | # 获取文件后缀 206 | _, suffix = os.path.splitext(file_name) 207 | if suffix not in [".md",".markdown",".mdown","mkd"]: 208 | print('Error: the file should be in markdown format') 209 | sys.exit(1) 210 | 211 | if only_pdf: 212 | dest_name = ".~temp~.html" 213 | 214 | 215 | f = open(file_name, "r") 216 | f_r = open(dest_name, "w") 217 | 218 | # 往文件中填写 HTML 的一些属性 219 | f_r.write("""
""") 224 | f_r.write("""""") 225 | 226 | # 逐行解析 markdwon 文件 227 | for eachline in f: 228 | result = parse(eachline) 229 | if result != "": 230 | f_r.write(result) 231 | 232 | f_r.write("""

""") 233 | 234 | # 公式支持 235 | global need_mathjax 236 | if need_mathjax: 237 | f_r.write("""""") 241 | # 文件操作完成之后记得关闭!!! 242 | f_r.close() 243 | f.close() 244 | 245 | # 调用扩展 wkhtmltopdf 将 HTML 文件转换成 PDF 246 | if dest_pdf_name != "" or only_pdf: 247 | call(["wkhtmltopdf", dest_name, dest_pdf_name]) 248 | # 如果有必要,删除中间过程生成的 HTML 文件 249 | if only_pdf: 250 | call(["rm", dest_name]) 251 | ... 252 | 3.4 编写 parse() 253 | 254 | ... 255 | def parse(input): 256 | global block_state, is_normal 257 | is_normal = True 258 | result = input 259 | 260 | # 检测当前 input 解析状态 261 | result = test_state(input) 262 | 263 | if block_state == BLOCK.Block: 264 | return result 265 | 266 | # 分析标题标记 # 267 | title_rank = 0 268 | for i in range(6, 0, -1): 269 | if input[:i] == '#'*i: 270 | title_rank = i 271 | break 272 | if title_rank != 0: 273 | # 处理标题,转化为相应的 HTML 文本 274 | result = handleTitle(input, title_rank) 275 | return result 276 | 277 | # 分析分割线标记 -- 278 | if len(input) > 2 and all_same(input[:-1], '-') and input[-1] == '\n': 279 | result = "
" 280 | return result 281 | 282 | # 解析无序列表 283 | unorderd = ['+', '-'] 284 | if result != "" and result[0] in unorderd : 285 | result = handleUnorderd(result) 286 | is_normal = False 287 | 288 | f = input[0] 289 | count = 0 290 | sys_q = False 291 | while f == '>': 292 | count += 1 293 | f = input[count] 294 | sys_q = True 295 | if sys_q: 296 | result = "
"*count + "" + input[count:] + "" + "
"*count 297 | is_normal = False 298 | 299 | # 处理特殊标记,比如 ***, ~~~ 300 | result = tokenHandler(result) 301 | # END 302 | 303 | # 解析图像链接 304 | result = link_image(result) 305 | pa = re.compile(r'^(\s)*$') 306 | a = pa.match(input) 307 | if input[-1] == "\n" and is_normal == True and not a : 308 | result+="
" 309 | 310 | return result 311 | ... 312 | 3.5 编写 test_state() 313 | 314 | ... 315 | def test_state(input): 316 | global table_state, orderList_state, block_state, is_code, temp_table_first_line, temp_table_first_line_str 317 | Code_List = ["python\n", "c++\n", "c\n"] 318 | 319 | result = input 320 | 321 | # 构建正则表达式规则 322 | # 匹配块标识 323 | pattern = re.compile(r'```(\s)*\n') 324 | a = pattern.match(input) 325 | 326 | # 普通块 327 | if a and block_state == BLOCK.Init: 328 | result = "
" 329 | block_state = BLOCK.Block 330 | is_normal = False 331 | # 特殊代码块 332 | elif len(input) > 4 and input[0:3] == '```' and (input[3:9] == "python" or input[3:6] == "c++" or input[3:4]== "c") and block_state == BLOCK.Init: 333 | block_state = BLOCK.Block 334 | result = "
" 335 | is_code = True 336 | is_normal = False 337 | # 块结束 338 | elif block_state == BLOCK.Block and input == '```\n': 339 | if is_code: 340 | result = "
" 341 | else: 342 | result = "
" 343 | block_state = BLOCK.Init 344 | is_code = False 345 | is_normal = False 346 | elif block_state == BLOCK.Block: 347 | pattern = re.compile(r'[\n\r\v\f\ ]') 348 | result = pattern.sub(" ", result) 349 | pattern = re.compile(r'\t') 350 | result = pattern.sub(" " * 4, result) 351 | result = "" + result + "
" 352 | is_normal = False 353 | 354 | # 解析有序序列 355 | if len(input) > 2 and input[0].isdigit() and input[1] == '.' and orderList_state == ORDERLIST.Init: 356 | orderList_state = ORDERLIST.List 357 | result = "
  1. " + input[2:] + "
  2. " 358 | is_normal = False 359 | elif len(input) > 2 and input[0].isdigit() and input[1] == '.' and orderList_state == ORDERLIST.List: 360 | result = "
  3. " + input[2:] + "
  4. " 361 | is_normal = False 362 | elif orderList_state == ORDERLIST.List and (len(input) <= 2 or input[0].isdigit() == False or input[1] != '.'): 363 | result = "
" + input 364 | orderList_state = ORDERLIST.Init 365 | 366 | # 解析表格 367 | pattern = re.compile(r'^((.+)\|)+((.+))$') 368 | match = pattern.match(input) 369 | if match: 370 | l = input.split('|') 371 | l[-1] = l[-1][:-1] 372 | # 将空字符弹出列表 373 | if l[0] == '': 374 | l.pop(0) 375 | if l[-1] == '': 376 | l.pop(-1) 377 | if table_state == TABLE.Init: 378 | table_state = TABLE.Format 379 | temp_table_first_line = l 380 | temp_table_first_line_str = input 381 | result = "" 382 | elif table_state == TABLE.Format: 383 | # 如果是表头与表格主题的分割线 384 | if reduce(lambda a, b: a and b, [all_same(i,'-') for i in l], True): 385 | table_state = TABLE.Table 386 | result = "" 387 | is_normal = False 388 | 389 | # 添加表头 390 | for i in temp_table_first_line: 391 | result += "" 392 | result += "" 393 | result += "" 394 | is_normal = False 395 | else: 396 | result = temp_table_first_line_str + "
" + input 397 | table_state = TABLE.Init 398 | 399 | elif table_state == TABLE.Table: 400 | result = "" 401 | for i in l: 402 | result += "" 403 | result += "" 404 | 405 | elif table_state == TABLE.Table: 406 | table_state = TABLE.Init 407 | result = "
" + i + "
" + i + "
" + result 408 | elif table_state == TABLE.Format: 409 | pass 410 | 411 | return result 412 | ... 413 | 3.6 几个处理函数 414 | 415 | ... 416 | # 判断 lst 是否全由字符 sym 构成  417 | def all_same(lst, sym): 418 | return not lst or sym * len(lst) == lst 419 | 420 | # 处理标题 421 | def handleTitle(s, n): 422 | temp = "" + s[n:] + "" 423 | return temp 424 | 425 | # 处理无序列表 426 | def handleUnorderd(s): 427 | s = "
  • " + s[1:] 428 | s += "
" 429 | return s 430 | 431 | 432 | def tokenTemplate(s, match): 433 | pattern = "" 434 | if match == '*': 435 | pattern = "\*([^\*]*)\*" 436 | if match == '~~': 437 | pattern = "\~\~([^\~\~]*)\~\~" 438 | if match == '**': 439 | pattern = "\*\*([^\*\*]*)\*\*" 440 | return pattern 441 | 442 | # 处理特殊标识,比如 **, *, ~~ 443 | def tokenHandler(s): 444 | l = ['b', 'i', 'S'] 445 | j = 0 446 | for i in ['**', '*', '~~']: 447 | pattern = re.compile(tokenTemplate(s,i)) 448 | match = pattern.finditer(s) 449 | k = 0 450 | for a in match: 451 | if a: 452 | content = a.group(1) 453 | x,y = a.span() 454 | c = 3 455 | if i == '*': 456 | c = 5 457 | s = s[:x+c*k] + "<" + l[j] + ">" + content + "" + s[y+c*k:] 458 | k += 1 459 | pattern = re.compile(r'\$([^\$]*)\$') 460 | a = pattern.search(s) 461 | if a: 462 | global need_mathjax 463 | need_mathjax = True 464 | j += 1 465 | return s 466 | ... 467 | 3.7 编写 link_image() 468 | 469 | ... 470 | # 处理链接 471 | def link_image(s): 472 | # 超链接 473 | pattern = re.compile(r'\\\[(.*)\]\((.*)\)') 474 | match = pattern.finditer(s) 475 | for a in match: 476 | if a: 477 | text, url = a.group(1,2) 478 | x, y = a.span() 479 | s = s[:x] + "" + text + "" + s[y:] 480 | 481 | # 图像链接 482 | pattern = re.compile(r'!\[(.*)\]\((.*)\)') 483 | match = pattern.finditer(s) 484 | for a in match: 485 | if a: 486 | text, url = a.group(1,2) 487 | x, y = a.span() 488 | s = s[:x] + "" + "" + s[y:] 489 | 490 | # 角标 491 | pattern = re.compile(r'(.)\^\[([^\]]*)\]') 492 | match = pattern.finditer(s) 493 | k = 0 494 | for a in match: 495 | if a: 496 | sym,index = a.group(1,2) 497 | x, y = a.span() 498 | s = s[:x+8*k] + sym + "" + index + "" + s[y+8*k:] 499 | k += 1 500 | 501 | return s 502 | ... 503 | 四、运行程序 504 | 505 | 这里我提供了一份模板文件用作测试使用,可以使用 wget 从以下地址下载。 506 | 507 | http://labfile.oss.aliyuncs.com/courses/708/doc_template.md 508 | 509 | # 这是本次实验的完整源码 510 | http://labfile.oss.aliyuncs.com/courses/708/md2pdf.py 511 | 执行以下命令执行程序: 512 | 513 | $ python3 md2pdf.py doc_template.md -p template.pdf 514 | 此处输入图片的描述 515 | 516 | 执行完毕后,输入以下命令用firefox浏览器查看解析效果: 517 | 518 | $ firefox template.pdf 519 | 五、总结 520 | 521 | 本次课程我们使用 Python 3 打造了 Markdown 文本解析器,并且借助 wkhtmltopdf 这一工具的帮助将 HTML 文本转化成 PDF 。 522 | 523 | 但是,这里我仅仅只是提供了 Markdown 解析的一种方案,还有许多更优更快的方法可以用来解析 Markdown 。 524 | 525 | 另外该程序目前仅支持在 HTML 文件中显示 LaTex 公式,在从 HTML 文件转换为 PDF 的过程中使用了 wkhtmltopdf 插件,所以最终为何没能正确解析 LaTex 公式未能找出原因。 526 | 527 | 六、课后习题 528 | 529 | Q1:使用 wkhtmltopdf 这个工具将在线的网页转化成 PDF 打印出来。 530 | 531 | 课后习题参考链接:https://www.shiyanlou.com/questions/7392 532 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fluent-python-书籍翻译 2 | 计划花一个月的时间翻译出demo版本,然后欢迎大家来润色,已发邮件给本书作者,希望看到的各位可以添砖加瓦。 3 | 4 | 5 | 6 | 1.http://www.crifan.com/files/doc/docbook/python_topic_re/release/ 7 | python正则模块re的2个最重要的函数search和findall的基本介绍 8 | 9 | 10 | 2.http://zhangxiaoyang.me/index.html 11 | 敏捷开发的一些概念:人月,人件等等等等 12 | -------------------------------------------------------------------------------- /第1章-python数据模型-1.md: -------------------------------------------------------------------------------- 1 | #第一部分 开端 2 | #1. python数据模型对象 3 | Guido的语言设计的审美是惊人的。我见过很多好的语言设计者可以建立理论上美丽的语言,但是没有人会使用,但Guido是一个罕见的可以建立一个,只是在理论上略低于完美但可在写程序感到快乐的编程语言的人。 4 |

— Jim Hugunin Jython创造者, AspectJ合伙创建人, .Net DLR架构师

5 | Python最好的一个品质是它的一致性。使用Python工作一阵子后,您可以开始对新的特性做出正确的猜测。 6 | 7 | 然而,如果您学Python之前学了另一种面向对象的语言,您会发现它使用len(collection)而不是collection.len()可能会感到奇怪。这种奇特的现象是冰山的一角,当正确的理解,是一切我们称之为Pythonic的关键。冰山就相当于Python的数据模型,您可以使用它描述的API来拿最符合习惯的语言特点来构建您自己的对象。 8 | 9 | 您可以认为数据模型是作为一个框架来描述Python。它形式化语言本身的基本构件的接口,如序列,迭代器,函数,类,上下文管理器等。 10 | 11 | 在使用任何框架进行编码时,您花了很多时间来实现由框架调用的方法。当您利用Python数据模型时同样如此。Python解释器调用特殊的方法进行基本的对象操作‐情况,往往通过特殊的语法触发。特殊方法的名字总是拼双下划线作为开头和结尾,例如`__getitem__`。好比说,`obj[key]`是由`__getitem__`方法来支持的。要使用`my_collection[key]`,解释器会调用`my_collection.__getitem__(key)`。 12 | 13 | 特殊的方法名称允许您的对象实现,支持和与基本的语言结构交互作用,如: 14 | 15 | * 迭代器; 16 | * 集合; 17 | * 属性访问; 18 | * 操作符重载; 19 | * 函数和方法调用; 20 | * 对象的创建和销毁; 21 | * 字符串表示和格式; 22 | * 管理上下文等 (i.e. with 块); 23 | 24 | ###魔术和dunder 25 | 魔术方法的术语是特殊用法的俚语,但当谈论关于具体方法如`__getitem__`,一些Python开发人员‐走捷径说“under-under-getitem”这是模棱两可,由于语法`__x`还有另一个特殊的意义。但准确发音“under-under-getitem-under-under”是无聊的,所以我效仿作者,Steve Holden老师说“dunder-getitem”。因此,特殊的方法也被称为dunder方法。 26 | 27 | ##一个python风格的纸牌游戏 28 | 下面是一个非常简单的例子,但它只是说明使用`__getitem__`和`__len__`两种方法的威力。 29 | 例1-1 使用一个类来表示一副扑克牌: 30 | ``` 31 | import collections 32 | 33 | Card = collections.namedtuple('Card', ['rank', 'suit']) 34 | 35 | class FrenchDeck: 36 | ranks = [str(n) for n in range(2, 11)] + list('JQKA') 37 | suits = 'spades diamonds clubs hearts'.split() 38 | 39 | def __init__(self): 40 | self._cards = [Card(rank, suit) for suit in self.suits 41 | for rank in self.ranks] 42 | 43 | def __len__(self): 44 | return len(self._cards) 45 | 46 | def __getitem__(self, position): 47 | return self._cards[position] 48 | ``` 49 | 首先要注意的是使用`collections.namedtuple`构建一个简单的类来表示个人卡。因为Python 2.6中namedtuple可以用来构建类,这些类是只是捆绑了属性而没有自定义方法的对象类,如数据库的记录。在这个例子中,我们使用它提供了具有一个很好的代表性的卡,如显示在控制台会话那样: 50 | ``` 51 | >>> beer_card = Card('7', 'diamonds') 52 | >>> beer_card 53 | Card(rank='7', suit='diamonds') 54 | ``` 55 | 但这个例子的关键点是frenchdeck类。它很短,但它包括了一个重点。首先,像任何标准的Python集合,一副牌对len()函数返回卡的数量。 56 | ``` 57 | >>> deck = FrenchDeck() 58 | >>> len(deck) 59 | 52 60 | ``` 61 | 从一副牌中读取特定的卡片,好比第一个或最后一个,应该是和deck[0]或deck[-1]一样容易,这就是`__getitem__`方法提供的访问。 62 | ``` 63 | >>> deck[0] 64 | Card(rank='2', suit='spades') 65 | >>> deck[-1] 66 | Card(rank='A', suit='hearts') 67 | ``` 68 | 我们应该创建一个方法来选择一个随机卡吗?不需要!Python已经有一个函数可以从一个序列得到一个随机的元素:random.choice。我们可以使用它在一副牌中获得一张卡: 69 | ``` 70 | >>> from random import choice 71 | >>> choice(deck) 72 | Card(rank='3', suit='hearts') 73 | >>> choice(deck) 74 | Card(rank='K', suit='spades') 75 | >>> choice(deck) 76 | Card(rank='2', suit='clubs') 77 | ``` 78 | -------------------------------------------------------------------------------- /第1章-python数据模型-2.md: -------------------------------------------------------------------------------- 1 | 我们已经看到使用特定方法利用Python数据模型的两个优点: 2 | 3 | 1.您的类的使用者不必记住任意的标准操作的方法名称(“如何获取物件的数量?”它是.size().length()还是什么?) 4 | 5 | 2.从丰富的Python标准库获益和避免重新发明轮子这是很容易的,例如random.choice函数。 6 | 7 | 但是还有更好的。 8 | 9 | 因为我们的`__getitem__`代表着`self._cards的[]`操作符,我们的一副牌自动支持切片。 10 | 这是我们如何从全新的一副牌看顶上的三张牌,然后通过在12开始,一次跳13个卡片拿起A纸牌: 11 | ``` 12 | >>> deck[:3] 13 | [Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')] 14 | >>> deck[12::13] 15 | [Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'), Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')] 16 | ``` 17 | 仅仅需要实现`__getitem__`方法,我们的一副牌也是可以进行遍历的: 18 | ``` 19 | >>> for card in deck: # doctest: +ELLIPSIS 20 | ... print(card) 21 | Card(rank='2', suit='spades') 22 | Card(rank='3', suit='spades') 23 | Card(rank='4', suit='spades') 24 | ... 25 | ``` 26 | 也可以反向遍历这副牌: 27 | 28 | ``` 29 | >>> for card in reversed(deck): # doctest: +ELLIPSIS 30 | ... print(card) 31 | Card(rank='A', suit='hearts') 32 | Card(rank='K', suit='hearts') 33 | Card(rank='Q', suit='hearts') 34 | ... 35 | ``` 36 | ###tips:doctests中的省略 37 | 只要有可能,在这本书的编写确保精度的在Python控制台列表中提取。 38 | 当输出太长,省略部分用省略号标示…像在上面的最后一行。 39 | 在这种情况下,我们使用的# doctest:+ELLIPSIS省略指令使doctest通过。 40 | 如果你在控制台交互,在这些例子中,你可以一起省略doctest指令。 41 | 42 | 迭代往往是隐式的。如果一个集合没有`__contains__`方法,那么in操作做顺序扫描。 43 | 案例:in操作于我们的frenchdeck类,因为它是可迭代的。过来验证一下: 44 | ``` 45 | >>> Card('Q', 'hearts') in deck 46 | True 47 | >>> Card('7', 'beasts') in deck 48 | False 49 | ``` 50 | 排序怎么样? 51 | 一个常见的排序卡系统是通过等级(设置A为最高),然后以花色排名:黑桃(最高),然后红心、方块和梅花(最低)。 52 | 这里是一个根据上面规则制定的牌规则函数,梅花2返回0和黑桃A返回51: 53 | ``` 54 | suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0) 55 | def spades_high(card): 56 | rank_value = FrenchDeck.ranks.index(card.rank) 57 | return rank_value * len(suit_values) + suit_values[card.suit] 58 | ``` 59 | 有了spades_high函数,我们现在可以在我们的一副牌中增加列表排序: 60 | ``` 61 | >>> for card in sorted(deck, key=spades_high): # doctest: +ELLIPSIS 62 | ... print(card) 63 | Card(rank='2', suit='clubs') 64 | Card(rank='2', suit='diamonds') 65 | Card(rank='2', suit='hearts') 66 | ... (46 cards ommitted) 67 | Card(rank='A', suit='diamonds') 68 | Card(rank='A', suit='hearts') 69 | Card(rank='A', suit='spades') 70 | ``` 71 | 虽然frenchdeck隐式继承自object(在python2中你需要直接写出来FrenchDeck(object),但在python3中就默认继承),它的功能是不是遗传的,而是来自于利用数据模型和composition。 72 | 通过实现特殊的方法`__len__`和`__getitem__`。我们frenchdeck表现得像一个标准的Python序列,允许它从核心语言功能获益,好比迭代和切片,同时从标准库获益,就像例子中所使用的dom.choice, reversed和sorted。 73 | 多亏了composition,`__len__` 和 `__getitem__` 的方法可以把所有工作交给一个列表对象,`self._cards` 74 | ###tips 如何洗牌 75 | 到现在,一个FrenchDeck卡牌对象都不能被洗牌,因为它是不可改变的,除非违反封装和直接处理`_cards`属性。在11章中,这些bug将被一个一行的添加下划线的`__setitem__`方法修正。 76 | -------------------------------------------------------------------------------- /第1章-python数据模型-3.md: -------------------------------------------------------------------------------- 1 | 2 | 如何使用特殊的方法 3 | 特殊方法需要了解的第一件事是,他们注定要被Python解释器调用,而不是被您调用。您不写`my_object.__len__()`。您应该写`len(my_object)`,如果my_object是用户定义的类的一个实例,然后Python调用您实现的`__len__`实例方法。 4 | 5 | 但对于内置类型如表,字符串,字节数组等,解释器采用了一个便捷方法:CPython实现`len()`方法实际上返回的是在`PyVarObject` C结构体中`ob_size`字段的值,表示内存中的任何变量大小的内置对象。这比调用一个方法要快得多。 6 | 7 | 更多的并不是如此,特殊方法的调用往往是隐式的。好比说,`for i in x`这句话实际上会调用`iter(x)`这反过来又可能会调用`x.__iter__()`,如果它是可用的。 8 | 9 | 通常情况下,您的代码不应该有许多直接调用特殊方法的调用。除非您是做了很多的元编程,您应该实现特殊的方法而不是显式调用它们。唯一用户代码经常直接调用的方法是是`__init__`,在您自己的`__init__`中实现调用父类的初始化。 10 | 11 | 如果您需要调用一个特殊的方法,通常更好的是调用相关的内置功能,如`len, iter, str`等。 12 | 13 | 这些内置功能调用相应的特殊方法,但往往提供其他服务,同时,内置类型速度比的方法调用的快。 14 | 避免使用`__foo__`语法创建任意自定义属性,因为这样的名字可能在未来获得特殊的意义,即使它们现在是未使用的。 15 | 16 | 模拟数值类型 17 | 一些特殊的方法允许用户对象响应算子如 + 。我们将在第13章更详细的介绍,但在这里,我们的目标是通过另一个简单的例子进一步说明特殊方法的用法。 18 | 我们将实现一个类来表示2维向量,即如在数学和物理中使用的欧氏向量(见下图) 19 | 20 |
21 | 图1 2D向量相加的例子 22 |
23 | ### tips: 24 | 内置的complex类型可以用来表示二维向量,但是我们类可以推广到表示n维向量。在第14章中,我们将这样做。 25 | 26 | 27 | 我们将开始设计API通过为一个类写一个模拟控制台会话,后面我们可以用作doctest。下面的代码片段测试向量加法的照片(见上图) 28 | ``` 29 | >>> v1 = Vector(2, 4) 30 | >>> v2 = Vector(2, 1) 31 | >>> v1+v2 32 | Vector(4, 5) 33 | ``` 34 | 注意+操作符如何产生一个向量结果并以一个友好的方式在控制台显示 35 | 内置函数`abs`返回的整数和浮点数的绝对值,和复数的模,所以为保持一致,我们的API也使用了`abs`来计算一个向量的大小 36 | ``` 37 | >>> v = Vector(3, 4) 38 | >>> abs(v) 39 | 5.0 40 | ``` 41 | 我们还可以实现*操作符来执行向量乘法,即由一个数字乘以一个向量,以产生一个与原向量相同的方向和乘以原向量模的新的向量: 42 | ``` 43 | >>>v*3 44 | Vector(9, 12) 45 | >>> abs(v * 3) 46 | 15.0 47 | ``` 48 | 例1-2是一个向量类,通过特殊的方法`__repr__,__abs__,__add__和__mul__`的使用,实现所描述的操作符 49 | ``` 50 | from math import hypot 51 | 52 | 53 | class Vector: 54 | 55 | def __init__(self, x=0, y=0): 56 | self.x = x 57 | self.y = y 58 | 59 | def __repr__(self): 60 | return 'Vector(%r, %r)' % (self.x, self.y) 61 | 62 | def __abs__(self): 63 | return hypot(self.x, self.y) 64 | 65 | def __bool__(self): 66 | return bool(abs(self)) 67 | 68 | def __add__(self, other): 69 | x = self.x + other.x 70 | y = self.y + other.y 71 | return Vector(x, y) 72 | 73 | def __mul__(self, scalar): 74 | return Vector(self.x * scalar, self.y * scalar) 75 | ``` 76 | 请注意,虽然我们实施了四个特殊的方法(除了`__init__`),它们中没有一个是类直接调用、或是在控制台列表中说明的类的典型用法中直接调用的。如前所述,Python解释器是调用特殊方法唯一的常客。在下一节中,我们讨论了每种特殊方法的代码。 77 | -------------------------------------------------------------------------------- /第1章-python数据模型-4.md: -------------------------------------------------------------------------------- 1 | ##字符串表示形式 2 | `__repr__`特殊方法被内置为得到对象的字符串表示形式的repr调用。如果我们没有实现`__repr__`,向量实例在控制台显示就会如 ``形式。 3 | 4 | 表达式求值的结果,交互式控制台和调试器会调用repr,正如`% r`占位符在典型情况下的格式是%运算符,`!r `转换符在新的[格式字符串语法](https://docs.python.org/3/library/string.html#format-string-syntax)用于str.format方法。 5 | 6 | 请注意,在我们的`__repr__`实现中我们使用%r获得属性的标准表示来显示。这是很好的做法,因为它显示矢量`Vector(1, 2)`和矢量`Vector("1"、"2")`间的关键差异--后者不会在这个例子中工作,因为构造函数的参数必须是数字,而不是字符串 7 | 8 | 通过`__repr__`返回的字符串应该是明确的,如果可能的话,匹配所必需的源代码,以重新创建所代表的对象。这就是为什么我们选择的代表看起来像调用类的构造函数:例如Vector(3, 4)。 9 | 10 | 11 | 对比`__repr__`与`__str__`,`__str__`是通过`str()`构造函数隐式由打印功能使用的。`__str__`应该返回一个适合的字符串显示给用户。 12 | 13 | 如果你只能实现其中的一个特殊方法,选择`__repr__`,因为当没有自定义`__str__`可用时,Python调用`__repr__`作为备用方案。 14 | 15 | ###tips 16 | [`__repr__`与`__str__`之间的区别](http://stackoverflow.com/questions/1436703/difference-between-str-and-repr-in-python):是由Pythonistas Alex Martelli和Martijn Pieters优秀贡献的StackOverflow问题 17 | ##算数操作符 18 | 例1-2实现了两个操作符:+和*,展现`__add__`和`__mul__`的基本用法。请注意,在这两种情况下,该方法创建并返回一个新的实例Vector,不修改任何一个操作数-本身或其他操作数仅仅是读取。这是中缀运算符的预期行为:创建新的对象并没有碰到其操作数。在第13章中,我将有更多的要说。 19 | 20 | ###tips 21 | 22 | 作为实现,例1-2允许数乘以一个向量的,但不允许由一个向量乘以一个数,它违反了乘法的交换属性。我们将在13章,用特殊方法`__rmul__`修补这个问题。 23 | 24 | ##自定义类型的布尔值 25 | 虽然Python有一个bool类型,它却接受一个布尔上下文中的任何对象,如表达控制的if或者while语句,或作为操作数来进行与、或、否操作。Python应用bool(x)确定一个x值是真的或是假的,它总是返回true或false。 26 | 27 | 默认情况下,除非`__bool__`或`__len__`被实现,用户定义的类的实例被默认认为是真的。基本上,bool(x)调用`x.__bool__()`和使用其结果。如果`__bool__`不实现,Python试图调用`x.__len__()`,如果`x.__len__()`返回零,则返回false。否则,返回true。 28 | 29 | 我们的实现`__bool__`的思路很简单:如果向量的大小为零,它返回false,否则,返回true。我们使用`bool(abs(self))` 将大小转换为布尔因为`__bool__`预计将返回一个布尔值。 30 | 31 | 注意特殊方法`__bool__`是如何允许你的对象是与在建在Python标准库的文档类型定义真值章检验规则一致的。 32 | 33 | ##特殊方法概述 34 | 35 | Python语言参考的数据模型列表列出83特殊的方法名,其中47个用于实现算术,位和比较运算符,作为什么是可用的概述,见下表一和下表二 36 | ![表一](http://ww1.sinaimg.cn/large/006y8lVagw1f8f373qjsbj31660jkae5.jpg) 37 | ![表2](http://ww3.sinaimg.cn/large/006y8lVagw1f8f363vjgrj31620l6dlc.jpg) 38 | 39 | ##为何len不是一个方法 40 | 41 | 这个问题2013年我问核心开发者Raymond Hettinger,他的答案的关键可以用Python之禅的一句话来概括,在纯粹性和实用性之间是倾向实用性的。 42 | 43 | 在“特殊的方法”第8页描述了为何当x是一个内置类型的一个实例时len(x)跑得很快。没有方法由CPython的内置对象调用:长度仅仅是从C结构体的一个域读取。获取集合中的项的数量是一种常见的操作,必须有效地为字符串,列表,memoryview等基本和多样类型工作。 44 | 45 | 换句话说,len是不能称其为方法,因为它是作为Python数据模型的一部分,就像abs得到特殊待遇的。但多亏了特殊的方法`__len__`你也可以用你自己的自定义对象让len工作。这是在内置对象需要高效和语言的一致性之间的公平折衷。再从Python之禅引入一句话来概括:“特殊情况不足以打破规则。” 46 | ##本章小结 47 | 通过实现特殊的方法,你的对象可以像内置类型一样动作,使得大家公认为是Pythonic的有表现力的编码风格。 48 | 49 | 对于Python对象的基本要求是提供可用字符串表示自身,一是用于调试和记录,另外是用于呈现给最终用户。这就是为什么特殊方法`__repr__`和`__str__`在数据模型中存在。 50 | -------------------------------------------------------------------------------- /第1章-python数据模型-5.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /第2章-序列数组-1.md: -------------------------------------------------------------------------------- 1 | #第二部分 数据结构 2 | 你可能已经注意到,有几个提到的操作对文本,列表和表格产生的效果相同。文本,列表和表格一起被称为队列。[...] for命令一般在队列上也适用 3 |

— Geurts, Meertens and Pemberton 4 | ABC程序员手册

5 | 在创造python之前,Guido曾是一个ABC语言-一个10年的为新手创建一个编程环境的研究项目-的创造贡献者。ABC引入了许多想法,我们现在称之为的“Pythonic”:序列的通用操作,内置的元组和映射类型,结构由缩进产生,强类型没有变量声明等。这不是偶然的,Python是用户友好的。 6 | 7 | Python从ABC序列继承统一处理。字符串,列表,字节序列,数组,XML元素和数据库结果共享一组丰富的常用操作:包括迭代、切片、排序和级联。 8 | 9 | 了解各种可用的Python序列中可以从重新发明轮子中拯救我们,同时它们通用的接口激励我们创造API适当支持,并充分利用现有和将来的序列类型。 10 | 本章的大部分讨论适用于包括从熟悉的列表到Python3中的新的字串和字节类型在内的一般序列。 11 | 12 | 具体的关于列表、元组、数组和队列的话题也包括在这里,但对Unicode字符串和字节序列的介绍是延后至第4章。此外,这里的想法是介绍已能够使用的序列类型。创建你自己的序列类型是第10章的主题。 13 | 14 | ##内置序列概述 15 | 标准库提供了用C语言实现的序列类型的丰富的选择: 16 | 容器序列 17 | 列表,元组和collections.deque可以容纳不同类型的项目。 18 | 扁平序列 19 | str, bytes, bytearray, memoryview和array.array只能容纳相同类型的项目。 20 | 容器序列保持引用它们包含的对象的引用,这可能是任何类型的,而扁平的序列是将每个项目的值物理存储在其自己的内存空间中的,而不是作为不同的对象。因此,平面序列更为紧凑,但它们仅限于保持原始值,如字符、字节和数字。 21 | 22 | 另一种方式分组序列类型的方法是可变性: 23 | 可变序列 24 | 列表,字节数组,array.array,collections.deque和memoryview 25 | 不可变序列 26 | 元组,str和bytes 27 | ![](http://ww4.sinaimg.cn/large/006y8lVagw1f8f6lx2tlkj30v20aujt1.jpg) 28 | 29 | 30 |

图2-1 从collections.abc一些类绘制的UML类图。超类都在左侧;继承箭头从子类指向到超类。斜体名称是抽象类和抽象方法

31 | 32 | 图2-1有助于形象化可变序列和那些不可变序列的不同,可变序列同时还继承了它们的几种方法。需要注意的是内置的具体序列类型实际不是Sequence和MutableSequence ABCs(抽象基类) 中所描绘的子类,但抽象基类仍然是作为从一个形式化的序列类型期待一个全特性的序列类型功能有用的。 33 | 牢记这些共同的特点 - 可变VS不变;容器VS扁平 - 有助于你推断一个序列类型其他序列类型不同在哪里。 34 | 35 | 最根本的序列类型是列表 -分可变和混合型。 36 | 我相信你是轻松的处理它们,所以我们会跳转到列表推导式,一种强大的方式构建列表因为语法可能不熟悉没有充分利用。 37 | 38 | 掌握列表解析打开生成器表达式的大门- 生成器表达式其他用途之间 - 能够产生元素来填充任何类型的序列。 39 | 这两个都是下一个部分的主题。 40 | 41 | ##列表解析和生成器表达式 42 | 一个快速的方法来建立一个序列是使用一个列表的解析(如果目标是一个列表)或一个生成器表达式(所有其他类型的序列)。如果你不是每天都在使用这些语法形式,我打赌你错过了写更具可读性,并在同一时间往往更快代码的机会。 43 | 44 | 如果你怀疑我的说法,这些结构是“更可读的”,请继续阅读。我会试图说服你。 45 | 46 | tips:为简洁起见,许多Python程序员称列表推导式为listcomps,称生成器表达式为genexps。我也会用这些词 47 | 48 | 49 | ###列表解析和可读性 50 | 这是一个测试:你觉得例2-1和例2-2哪一个更容易阅读 51 | 52 | 示例2-1 用一个字符串的Unicode码建立一个列表 53 | ``` 54 | >>> symbols = '$¢£¥€¤' 55 | >>> codes = [] 56 | >>> for symbol in symbols: 57 | ... codes.append(ord(symbol)) ... 58 | >>> codes 59 | [36, 162, 163, 165, 8364, 164] 60 | ``` 61 | 示例2-2 用一个字符串的Unicode码建立一个列表程序2 62 | ``` 63 | >>> symbols = '$¢£¥€¤' 64 | >>> codes = [ord(symbol) for symbol in symbols] 65 | >>> codes 66 | [36, 162, 163, 165, 8364, 164] 67 | ``` 68 | 知道一点点的Python知识可以阅读例子2-1。然而,在学习listcomps我发现例2-2的可读性更好,因为它的意图是明确的。 69 | 70 | 一个循环可以用来做很多不同的事情:扫描一个序列来计数或选择其中的项,计算总量(金额,平均数),或处理任何其他数量。在例子2-1中该代码是建立一个列表。2-2相反,一个listcomp意味着只做一件事:建立一个新的列表。 71 | 72 | 当然,有可能滥用列表推导式写真的难以理解的代码。我见过使用listcomps的Python代码只是用重复的代码块来为其副作用埋单。如果你不做与产生的列表有关的事情,请不要使用该语法。此外,尽量保持简短。如果列表解析超过两行以上,最好的打破它或重写为一个普通的旧的循环。使用您的最佳判断:Python作为英语,对明确的写作没有硬性规定。 73 | 74 | tips:在Python代码中,[],{}或()中对换行是忽略的。所以你可以不用丑陋的\行建立多行列表,listcomps,genexps,词典等。 75 | 76 | ####listcomps不再泄漏自己的变量 77 | 78 | 看下面的Python 2.7的控制台会话: 79 | ``` 80 | Python 2.7.6 (default, Mar 22 2014, 22:59:38) 81 | [GCC 4.8.2] on linux2 82 | Type "help", "copyright", "credits" or "license" for more information. 83 | >>> x = 'my precious' 84 | >>> dummy = [x for x in 'ABC'] 85 | >>> x 86 | 'C' 87 | ``` 88 | 你可以看到,x的初始值被截断。这不再发生在Python 3。 89 | 90 | Python 3: 91 | ``` 92 | >>> x = 'ABC' 93 | >>> dummy = [ord(x) for x in x] 94 | >>> x 95 | 'ABC' 96 | >>> dummy [65, 66, 67] 97 | >>> 98 | ``` 99 | ###listcomps对比map和filter 100 | 101 | listcomp做的所有map和filter功能也能在不扭曲Python lambda表达式功能不改变的情况下完成。考虑例子2-3。 102 | 103 | 例子2-3 通过listcomp和map/filter进行同样的列表构建 104 | ``` 105 | >>> symbols = '$¢£¥€¤' 106 | >>> beyond_ascii = [ord(s) for s in symbols if ord(s) > 127] 107 | >>> beyond_ascii 108 | [162, 163, 165, 8364, 164] 109 | >>> beyond_ascii = list(filter(lambda c: c > 127, map(ord, symbols))) 110 | >>> beyond_ascii 111 | [162, 163, 165, 8364, 164] 112 | ``` 113 | 我过去认为,map和filter比等效的listcomps更快,但Alex Martelli指出,并非如此,至少在以上的例子不是这样。 114 | 115 | 在第5章中,我将有更多的关于map和filter的说明。现在我们用listcomps计算笛卡尔积:从两个或两个以上列出的所有项中建立一个包含元组的列表。 116 | ###笛卡尔积 117 | listcomps可以从两个或两个以上的可迭代对象的笛卡尔积生成列表。组成的笛卡尔积的项是由每一个输入的元组元素构成。结果列表有一个长度相等于乘的输入可迭代对象的长度。见图2-2 118 | 119 | ![](http://ww3.sinaimg.cn/large/006y8lVagw1f8fz05mw68j30uu0fqwfc.jpg) 120 | 例如,想象你需要制作一个两种颜色和三种尺寸的T恤衫列表。例2-4显示了如何使用listcomp生成列表。结果有6个项目。 121 | 122 | 例2-4 使用列表解析的笛卡尔乘积。 123 | ``` 124 | >>> colors = ['black', 'white'] 125 | >>> sizes = ['S', 'M', 'L'] 126 | >>> tshirts = [(color, size) for color in colors for size in sizes] 127 | >>> tshirts 128 | [('black', 'S'), ('black', 'M'), ('black', 'L'), ('white', 'S'), 129 | ('white', 'M'), ('white', 'L')] >>> for color in colors: 130 | ... for size in sizes: 131 | ... print((color, size)) ... 132 | ('black', 'S') 133 | ('black', 'M') 134 | ('black', 'L') 135 | ('white', 'S') 136 | ('white', 'M') 137 | ('white', 'L') 138 | >>> tshirts = [(color, size) for size in sizes ... for color in colors] 139 | >>> tshirts 140 | [('black', 'S'), ('white', 'S'), ('black', 'M'), ('white', 'M'), 141 | ('black', 'L'), ('white', 'L')] 142 | 143 | -------------------------------------------------------------------------------- /第2章-序列数组-2.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /第3章-字典和集合-1.md: -------------------------------------------------------------------------------- 1 | 2 | 任何运行的Python程序,同时活跃着许多字典,即使用户的程序代码,没有明确使用字典。 3 | 4 |

— A. M. Kuchling 5 | 6 |

7 |

8 | 漂亮的代码,第18章,Python的字典的实现 9 |

10 | 11 | 字典dict类型不仅广泛应用于我们的程序,也是Python实现的一个基本组成部分。模块命名空间、类和实例的属性和函数的关键字参数的一些基本结构是字典使用的地方。dict内置的功能位于`__builtins__ .__dict__`。 12 | 13 | 14 | 由于其关键作用,Python的字典高度优化。哈希表是在Python字典后的高性能的发动机。 15 | 16 | 17 | 我们在本章中还包括集合sets是因为它们也是哈希表实现的。了解一个哈希表的工作方式是如何使用大多数字典和集合 18 | 19 | 这里是本章的简要概述: 20 | 通用词典的方法 21 | 丢失键的特殊处理 22 | dict在标准库的变化 23 | 集合和冻结集类型 24 | 哈希表如何工作 25 | 哈希表的影响:关键类型的限制,不可预知的顺序等 26 | 27 | ##泛型类型的映射 28 | collections.abc模块提供Mapping和MutableMapping ABCs格式化字典的接口和类似的类型。见图3.1 29 | ![](https://ww1.sinaimg.cn/large/006y8lVagw1fbd9rek4eaj30w00c040g.jpg) 30 | 专门的映射的实现往往扩展dict或collections.UserDict,而不是这些ABCs。ABCs的价值主要在于记录和正规化最小接口的映射,并为isinstance在代码中测试需要支持映射在一个广义的场景中提供标准。 31 | ``` 32 | >>> my_dict = {} 33 | >>> isinstance(my_dict, abc.Mapping) 34 | True 35 | ``` 36 | 37 | 使用insinstace比确定一个方法的参数是否是dict类型更好,因为接着一个可供替代的映射类型就可以使用了。 38 | 39 | 标准库里所有的映射类型在其实现中使用了基本的dict,因此限制所有的键必须是哈希化的。 40 | 41 | 42 | 给定了这些基本规则,你可以通过几种方法构建字典。库参考中的内建方法页面有不同方法构造字典的示例: 43 | ``` 44 | >>> a = dict(one=1, two=2, three=3) 45 | >>> b = {'one': 1, 'two': 2, 'three': 3} 46 | >>> c = dict(zip(['one', 'two', 'three'], [1, 2, 3])) 47 | >>> d = dict([('two', 2), ('one', 1), ('three', 3)]) 48 | >>> e = dict({'three': 3, 'one': 1, 'two': 2}) 49 | >>>a==b==c==d==e 50 | True 51 | 52 | ``` 53 | 54 | 除了字面的语法形式和复杂的dict构造函数,我们可以使用字典推导式来构建字典。见下一小节。 55 | 56 | ##字典推导式 57 | 58 | 自从Python 2.7列表推导式和生成器表达式的语法被应用在字典推导式(集合推导式也是一样的,我们将很快谈到)。一个字典推导式通过从任何一个迭代对象中生成键:值对来构造一个字典实例。 59 | 例3-1.字典推导式例子 60 | ``` 61 | >>> DIAL_CODES = [ 62 | ... (86, 'China'), 63 | ... (91, 'India'), 64 | ... (1, 'United States'), 65 | ... (62, 'Indonesia'), 66 | ... (55, 'Brazil'), 67 | ... (92, 'Pakistan'), 68 | ... (880, 'Bangladesh'), 69 | ... (234, 'Nigeria'), 70 | ... (7, 'Russia'), 71 | ... (81, 'Japan'), 72 | ... ] 73 | >>> country_code = {country: code for code, country in DIAL_CODES} 74 | >>> country_code 75 | {'China': 86, 'India': 91, 'Bangladesh': 880, 'United States': 1, 'Pakistan': 92, 'Japan': 81, 'Russia': 7, 'Brazil': 55, 'Nigeria': 234, 'Indonesia': 62} 76 | >>> {code: country.upper() for country, code in country_code.items() ... if code < 66} 77 | {1: 'UNITED STATES', 55: 'BRAZIL', 62: 'INDONESIA', 7: 'RUSSIA'} 78 | 79 | ``` 80 | 如果你习惯liscomps,dictcomps是顺理成章。如果不是,listcomp语法的传播意味着熟悉它现在比以往任何时候都更有利可图。 81 | 82 | 我们现在转去看看mapping的API的全貌 83 | ##公共mapping方法的总览 84 | 映射方法的基本的API是非常丰富的,表3-1显示了这些方法通过字典和它的两个最有用的变化来实现:defaultdict和OrderedDict,defaultdict和OrderedDict都定义在集合模块。 85 | ![](https://ww2.sinaimg.cn/large/006y8lVagw1fbddxb4phvj30q20y7tei.jpg) 86 | 87 | ```几种 88 | ```方法 89 | ```撒上 90 | ``` 91 | -------------------------------------------------------------------------------- /第3章-字典和集合-2.md: -------------------------------------------------------------------------------- 1 | ##使用setdefault处理缺失的键 2 | 3 | 当k不存在时,访问d[k]会产生错误。每一个Python高手都知道 d.get(k, default)在d[k]值默认时是一个比处理KeyError更加方便的选择。 4 | 5 | 然而,当更新后值被发现(如果是可变的),使用__getitem__或是get是笨拙和低效的。 6 | 考虑例3-2,一个局部最优脚本的目的只是为了证明一个情况下,dict.get不是处理一个缺失的键值的最佳方式。 7 | 8 | 例3-2改编自一个Alex Martelli的例子,产生一个指数,像示例3-3一样 9 | 10 | ``` 11 | """Build an index mapping word -> list of occurrences""" 12 | import sys 13 | import re 14 | WORD_RE = re.compile('\w+') 15 | index = {} 16 | with open(sys.argv[1], encoding='utf-8') as fp: 17 | for line_no, line in enumerate(fp, 1): 18 | for match in WORD_RE.finditer(line): 19 | word = match.group() 20 | column_no = match.start()+1 21 | location = (line_no, column_no) 22 | # this is ugly; coded like this to make a point 23 | occurrences = index.get(word, []) 24 | occurrences.append(location) 25 | index[word] = occurrences 26 | # print in alphabetical order 27 | for word in sorted(index, key=str.upper): 28 | print(word, index[word]) 29 | ``` 30 | 例3-3:部分输出来自于示例3-2处理Python的禅宗。每行显示一个字和一个出现的位置组成的列表编码为对:(行数,列数)。 31 | ``` 32 | $ python3 index0.py ../../data/zen.txt 33 | a [(19, 48), (20, 53)] 34 | Although [(11, 1), (16, 1), (18, 1)] 35 | ambiguity [(14, 16)] 36 | and [(15, 23)] 37 | are [(21, 12)] 38 | aren [(10, 15)] 39 | at [(16, 38)] 40 | bad [(19, 50)] 41 | be [(15, 14), (16, 27), (20, 50)] 42 | beats [(11, 23)] 43 | Beautiful [(3, 1)] 44 | better [(3, 14), (4, 13), (5, 11), (6, 12), (7, 9), (8, 11), 45 | (17, 8), (18, 25)] 46 | ... 47 | ``` 48 | 例3-4:比例3-2更好的一种处理方法: 49 | ``` 50 | """Build an index mapping word -> list of occurrences""" 51 | import sys 52 | import re 53 | WORD_RE = re.compile('\w+') 54 | index = {} 55 | with open(sys.argv[1], encoding='utf-8') as fp: 56 | for line_no, line in enumerate(fp, 1): 57 | for match in WORD_RE.finditer(line): 58 | word = match.group() 59 | column_no = match.start()+1 60 | location = (line_no, column_no) 61 | index.setdefault(word, []).append(location) 62 | # print in alphabetical order 63 | for word in sorted(index, key=str.upper): 64 | print(word, index[word]) 65 | ``` 66 | 换句话说, 67 | ``` 68 | my_dict.setdefault(key, []).append(new_value) 69 | ``` 70 | 这一行的结果等同于运行 71 | ``` 72 | if key not in my_dict: 73 | my_dict[key] = [] 74 | my_dict[key].append(new_value) 75 | 76 | ``` 77 | 78 | 但后者的代码搜索键执行至少两次--如果没有发现就三次--而`setdefault`在一个单一的查找就完成了。 79 | 80 | 81 | ##具有灵活键查找的映射 82 | 83 | ###defaultdict:另一种处理缺失的键值的方法 84 | 例3-5使用了collections.defaultdict提供了另一种简洁的方法来解决例3-4的问题,当搜索出一个缺失的键值后一个defaultdict被配置用来创建需要的项。 85 | 这里是defaultdict它这里是如何工作:当实例化一个defaultdict,你要提供一个回调对象当传递一个不存在的键参数给`__getitem__`时被用来产生一个默认的值。 86 | 例如,通过`dd=defaultdict(list)`创建给定一个空的defaultdict,如果'new-key'不在dd中dd['new-key']表达式做以下步骤: 87 | 1.调用`list()`创建一个新的list 88 | 2.将新创建的list使用'new-key'作为键值插入dd中 89 | 3.返回此list的引用 90 | 可调用对象产生的默认值是存在一个实例的属性称为default_factory 91 | 如果没有提供default_factory,通常会产生KeyError。 92 | 93 | ``` 94 | """Build an index mapping word -> list of occurrences""" 95 | import sys 96 | import re 97 | import collections 98 | WORD_RE = re.compile('\w+') 99 | index = collections.defaultdict(list) 100 | with open(sys.argv[1], encoding='utf-8') as fp: 101 | for line_no, line in enumerate(fp, 1): 102 | for match in WORD_RE.finditer(line): 103 | word = match.group() 104 | column_no = match.start()+1 105 | location = (line_no, column_no) 106 | index[word].append(location) 107 | # print in alphabetical order 108 | for word in sorted(index, key=str.upper): 109 | print(word, index[word]) 110 | 111 | ``` 112 | 113 | 让defaultdict通过调用default_factory工作机制实际上是__missing__特殊方法,__missing__功能支持所有标准的映射类型,我们接下来讨论 114 | ###__missing__方法 115 | 116 | 例3-6 117 | ``` 118 | Tests for item retrieval using `d[key]` notation:: 119 | >>> d = StrKeyDict0([('2', 'two'), ('4', 'four')]) 120 | >>> d['2'] 121 | 'two' 122 | >>> d[4] 123 | 'four' 124 | >>> d[1] 125 | Traceback (most recent call last): 126 | ... 127 | KeyError: '1' 128 | Tests for item retrieval using `d.get(key)` notation:: 129 | >>> d.get('2') 130 | 'two' 131 | >>> d.get(4) 132 | 'four' 133 | >>> d.get(1, 'N/A') 134 | 'N/A' 135 | Tests for the `in` operator:: 136 | >>>2 in d 137 | True 138 | >>>1 in d 139 | False 140 | ``` 141 | 142 | 例3-例7例例:例 143 | 144 | ``` 145 | class StrKeyDict0(dict): 146 | def __missing__(self, key): 147 | if isinstance(key, str): 148 | raise KeyError(key) 149 | return self[str(key)] 150 | def get(self, key, default=None): 151 | try: 152 | return self[key] 153 | except KeyError: 154 | return default 155 | def __contains__(self, key): 156 | return key in self.keys() or str(key) in self.keys() 157 | 158 | 159 | ``` 160 | -------------------------------------------------------------------------------- /第3章-字典和集合-3.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /第3章-字典和集合-4.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /第3章-字典和集合-5.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /第4章-文本🆚字节-1.md: -------------------------------------------------------------------------------- 1 | 2 | --------------------------------------------------------------------------------