Skip to content

Fluent Python

yunmingyang edited this page Mar 9, 2020 · 15 revisions

Chapter1

  • 随机抽取方法 random.choice()
  • 若未实现__contains__方法,那么在使用in运算符时候就会进行一次隐式的迭代搜索,实现隐式的迭代搜索可以通过实现__getitem__来实现
  • sorted不改变顺序,并且会调用key所对应的自定方法
  • for实际是调用iter(x),x需要实现__iter__方法
  • tips
    • 通常无需直接使用特殊方法,除非有大量的元编程存在
    • 直接调用特殊方法的次数应远远低于实现它们的次数
    • __init__是一个例外,例如调用超类构造器
  • str与repr
    • 理论上str区别于repr的地方是更易被终端用户所理解,且会被str和print函数调用,但其实应看其具体的实现方式
    • !r/%r会调用repr
    • 若未实现str,解释器会调用repr

Chapter2

  • 列表推导式的一个原则是尽量保持简短,如果一个列表推导式超过两行,那么可以考虑一下用循环重写.但这不是定死的,可以根据情况的不同来衡量
  • python中(),[],{}会省略其中的换行,因此若过长可以省略换行符
  • 生成器表达式和列表推导式语法上的区别是把[]换成() 生成器表达式遵守迭代器协议,逐个产出元素 如果生成器表达式是函数调用的唯一元素,则不需额外括号 生成器表达式能做到的事情,实际上列表推导式也可以做到,但是其优点就是节省内存
  • 若是不是所有元组中的元素都想使用的话,使用*或者_都可以处理那些我们不感兴趣的变量
    a, b = ('a', 'b')
    a, *b = ('a', 'b', 'c')
    
  • python切片和区间操作会忽略最后一个元素,其带来的好处有可以直接使用(stop-start)来计算所取元素的数量,而且stop的值可以等于可迭代序列的长度,不会越界
  • list和tuple和数组类似,但是可以保存不同类型的对象,也是通过索引从0开始访问。list可改变,tuple不可改变。同时可以通过[]得到子集。切片运算之前是什么类型,切片之后也就是什么类型
  • 字典和map类似,但是其键值需要为不可变类型且不能为null
  • 省略(ellipsis)的正确书写方法是三个英语句号(...),其为Ellipsis的别名,Ellipsis是ellipsis的单例
  • 给切片赋值等号右侧一定要是可迭代序列
  • +=会调用__iadd__方法,若没有该方法则会调用__add__
  • 不可变类型使用 += 时候往往消耗很大,因为调用的实际上是__add__方法.str是一个例外,因为它过于常用所以对其有相应的优化,在初始化时候就会有额外的空间预留
  • 增量赋值(+=)不是原子操作
  • list.sort会修改当前list所以返回值为null,而sorted函数会生成一个新的list
  • sorted用的排序算法是稳定的(Timsort)
  • 数组背后存的是机器的机器翻译(array.array) 频繁先入先出,deque会更快一些 频繁检查元素是否存在,set效率更高,因为其对检索做过一定优化

Chapter3

  • 元组只有在内部元素都是可散列的情况下才是可散列的
  • defaultdict在创建时候会需要一个参数,这个参数就是在无法找到key时,添加key的默认value值.无key自动添加key时候其调用的是__missing__这个特殊方法,且该方法只有dict_examplekey时候会被调用,dict_example.get(key)仍然会返回None
  • 自定义映射类型更推荐继承collections.UserDict
  • UserDict并不是dict的子类,但在其中有一个data属性是dict的实例,该实例就是最终数据存储的地方
  • MutableMapping.update,可以直接使用,也被用于__init__函数中,用于使构造方法可以利用传入的各种参数来新建实例。由于该方法使用self[key] = value来添加新值,所以其实使用的是__setitem__方法
  • Mapping.get也是使用__getitem__来进行查找的,而dict自身的get方法可能不是
  • MappingProxyType存在于type中,其通过传入一个dict返回一个该dict的不可变视图(可查看不可修改)
  • frozenset可散列
  • 合集 a | b 交集 a & b 差集 a - b
  • 创建空集只能使用set(),且集合字符串表现形式为{...}
  • set_example = {1, 2, 3}会比set([1, 2, 3])这种创建方式更快一些,因为后者python必须先查询构造方法,再创建一个新list,最后再把这个列表传入到构造方法里;而前者会使用一个BUILD_SET这个字节码来创建集合
  • 散列表中的单元被称为表元(bucket),在dict的散列表中,每个键值对都占用一个表元.因为所有表元的大小一致,所以可以通过偏移量来读取某个表元
  • Python 会设法保证大概还有三分之一的表元是空的,所以在快要达到这个阈值的时候,原有的散列表会被复制到一个更大的空间里面。
  • Python 首先会调用hash(search_key) 来计算 search_key 的散列值,把这个值最低的几位数字当作偏移量,在散列表里查找表元(具体取几位,得看当前散列表的大小)
  • 为了解决散列冲突,算法会在散列值中另外再取几位,然后用特殊的方法处理一下,把新得到的数字再当作索引来寻找表元.若这次找到的表元是空的,则同样抛出KeyError;若非空,或者键匹配,则返回这个值;或者又发现了散列冲突,则重复以上的步骤
  • 在插入新值时,Python 可能会按照散列表的拥挤程度来决定是否要重新分配内存为它扩容。如果增加了散列表的大小,那散列值所占的位数和用作索引的位数都会随之增加,这样做的目的是为了减少发生散列冲突的概率.
  • 一个可散列的对象必须满足以下要求。
    • 支持hash()函数,并且通过__hash__()方法所得到的散列值是不变的
    • 支持通过__eq__()方法来检测相等性。
    • 若a == b为真,则hash(a) == hash(b)也为真。 所有由用户自定义的对象默认都是可散列的,因为它们的散列值由id()来获取,而且它们都是不相等的。
  • 在用户自定义的类型中,__slots__属性可以改变实例属性的存储方式,由 dict 变成 tuple
  • 当在对一个字典进行迭代操作时候,最好不要对其同时进行修改操作,因为当在向字典添加元素时候,可能会由于当前散列表的大小已经快满了而使该散列表被扩容,此过程中由于需要重新进行散列操作,其中顺序可能会发生变化,从而造成未被迭代元素被跳过的情况出现

Chapter4

  • str编码后就会变为bytes类型(unicode),而bytes解码后则会还原回str类型
  • Python3的str类型基本相当于Python2的unicode类型,只不过换了个名称.Python3的bytes类型缺不是把str类型换了个名称那么简单.
  • Python2.6的bytes类型是str的别名,而Python3中的不同
  • Python内置的两种二进制序列类型:bytes和bytearray
  • bytes和bytearray对象的各个元素是介于0~255(含)之间的整数,而不像python2的str对象那样是单个字符.然而二进制序列的切片始终是同一类型的二进制序列,包括长度为1的序列
  • struct可以根据传入的一个format参数,从一个二进制数据中提取结构化信息(eg:如从一个gif的memoryview中提取其格式版本等信息)
  • python自带了超过100中编解码器
  • utf_8别名utf8、utf-8以及U8
  • open()、str.encode()以及bytes.decode()都存在一个encoding参数
  • encode函数可以带一个参数error,可选value为'ignore'、'replace'以及'xmlcharrefreplace',用于在出现编码错误时采取的方法,而不是抛出异常.也可以通过codecs.register_error注册自己的编码错误解决方式
  • python3默认是utf-8编码,python2默认是ASCII,当打开的.py文件中包含UTF-8之外的编码格式时候(以python3为例),就会报一个SyntaxError,此时就可以使用coding: cp1252(Microsoft字符集,是latin1的超集)这种方式解决
  • Chardet(库)/chardetect(工具)可以通过尝试在bytes序列中找到相应的编码格式
  • UTF-16在小字符序设备上在字符最前端会被添加b'\xff\xfe'(BOM/ZERO WIDTH NO-BREAK SPACE/字节序标记),因此在传输这种序列格式时候,是可以识别的,而且UTF-16解码器可以过滤它.
  • UTF-8若存在BOM,可能是b'\xef\xbb\xbf'.
  • 解决unicode标准等价物的方法是使用unicodedata.nomalize函数提供的Unicode规范化

Chapter5

  • 函数 == 对象
    • 在运行时创建
    • 能赋值给变量或数据结构中的元素
    • 能作为参数传递
    • 能作为函数的返回结果
  • 接受函数作为入参或者把函数作为结果返回的函数是高阶函数
  • lambda == 匿名函数
    • 在匿名函数中不能赋值也不能使用while和try等python语句
  • python中可调用对象可以用callable来检查.7种可调用对象
    • 用户定义的函数 def lambda
    • 内置方法(C语言实现) len time.strftime
    • Native方法(C语言实现)
    • 类中方法
    • 类(创建实例时)
    • 类实例(需要定义__call__)
    • 生成器函数
  • 函数__dict__属性存储赋予它的用户属性.eg:
      def hw():
          print('hello')
      hw.short_description = 'a'
      print(hw.__dict__)
    
    若没赋任何用户属性给它__dict__会输出{}

Others:

  • python5大标准数据类型
    • Numbers
    • String
    • List
    • Tuple
    • Dictionary
  • python中未指定返回值的函数会自动返回None,等价于NULL
  • print()和c的printf()很相思同样可以使用%s,%d等字符串格式操作符结合使用,实现字符串替换的功能,但是需要一个%来间隔。
  • int()可将字符串转换为int
  • python单行注释'#',多行注释'""" """"'/"''' '''"
  • 文档字符串,在模块、类或者函数的起始添加一个字符串,起到在线文档的功能
  • / 浮点除, // 地板除法
  • python开头支持_、大写字母和小写字母。变量整体大小写敏感
  • python 不支持++ -- 操作符,但是支持+=、-=等增量操作符
  • python数字类型包括: 有符号整形(长整型、布尔值、浮点数以及复数),且长整形和C语言中的长整形不同,类似于JAVA中的BigInteger类型
  • true会被当作1,false会被当作0。且decimal类型可以用于精确表示浮点型,但是不属于基本类型,因为需要导入decimal包

PYTHONPATH

  • PYTHONPATH是Python搜索路径,默认我们import的模块都会从PYTHONPATH里面寻找。 启动后,Python先寻找PYTHONSTARTUP环境变量,然后执行此变量指定的文件中的代码。 加入PYTHONCASEOK的环境变量, 就会使python导入模块的时候不区分大小写. 另一种模块搜索路径。它通常内嵌于的PYTHONSTARTUP或PYTHONPATH目录中,使得两个模块库更容易切换。

编码

  • 默认情况下 python3使用utf-8编码
  • 若要为源码文件制定不同的编码,需要使用'# * coding: cp-1252 *',同样适用于python2, 但只解决输出中文乱码问题, Python2需要手动设置默认编码格式

多行语句

  1. 一行写不完可以使用''来进行连接,类似于shell
  2. 在[],{}或者()中不需要使用''

标准数据类型

  • number,string,list,tuple,dictionary,set
  • 可变: list,set,dictionary
  • 不可变: number,string,tuple
  • number int,float,bool,complex
    • tips Python2中还没有布尔类型,0是False,1是True,在Python3中则有了True和False,但是他们的值还是1,可以和数字相加 在混合计算时,Python会把整型转换成为浮点数 数值的除法包含两个运算符:/ 返回一个浮点数,// 正常返回一个整数,但是若分子或者分母中有一个是浮点数,那么返回的就是浮点数
  • 字符串 单引号和双引号完全相同 使用三引号(单双均可)可以指定一个多行字符串。 反斜杠是转义,但是在字符串前面加r可以使转义不生效 使用"+"连接,使用"*"重复 访问方式类似于数组,从右往左是-1开始的 字符串不可变 截取方法 变量[头:尾:步长] Python 没有单独的字符类型,一个字符就是长度为1的字符串。 与 C 字符串不同的是,Python 字符串不能被改变。向一个索引位置赋值,比如word[0] = 'm'会导致错误

同行显示多条语句,但是需要用";"来分割

多变量赋值

a=b=c=1 或者 a,b,c=1,2,3

位运算符

&,|,~,^

逻辑运算符

and,or,not

身份运算符

is,not is。主要比较是否引用相同对象。

成员运算符

in,not in

import

  • 在 python 用 import 或者 from...import 来导入相应的模块。
  • 将整个模块(somemodule)导入,格式为: import somemodule。
  • 从某个模块中导入某个函数,格式为: from somemodule import somefunction
  • 从某个模块中导入多个函数,格式为: from somemodule import firstfunc, secondfunc, thirdfunc。
  • 将某个模块中的全部函数导入,格式为: from somemodule import *。

  • 目录只有包含一个叫做__init__.py的文件才会被认作是一个包
  • all,为了确保一些大小写不区分的系统,如windows,import *时候出现问题,所以可以在这个变量中,由作者填入包名,来进行导入

tips

  • python中只有模块、类以及函数才会引入新的作用域,其它代码块不会引入新作用域
  • 内部作用域想要修改外部作用域变量时候,要使用global关键字
  • 修改嵌套作用域的变量就要使用nonlocal关键字