Python学习笔记

目录

第一部分  Python基础

1 变量、注释、运算符

2 数据类型:数字、布尔、字符串、列表、元组、字典、集合

3 控制流:if、for、while

4 函数(function)

5 模块(module)和包(package)

第二部分  高级特性:切片、迭代、列表解析、生成器、迭代器

第三部分  函数式编程 (functional programming)

1 高阶函数 (Higher-order function)(functools模块)

2 返回函数(闭包closure)

3 匿名函数(anonymous function)

4 装饰器(decorator)

5 偏函数(partial function)

第四部分  面向对象编程(Object-Oriented Programming, OOP)

1 类和实例

2 访问限制

3 继承和多态

4 获取对象信息

第五部分 面向对象高级编程

1 使用__slots__限制属性

2 使用@property把方法变成属性

3 定制类:__str__, __repr__

4 使用枚举类(enum模块)

5 使用type()创建类,元类

第六部分  错误、调试和测试

1 错误处理

2 调试

3 单元测试(unittest模块)

4 文档测试

第七部分  IO编程

1 文件读写

2 StringIO和BytesIO(io模块)

3 操作文件和目录(os模块)

4 序列化(pickle模块,json模块)

第八部分 进程和线程

1 多进程(os模块,multiprocessing模块,)

2 多线程(模块_thread,threading,Lock)

3 ThreadLocal

4 分布式进程(mutiprocessing.managers模块,queue模块)

第九部分  正则表达式

1 如何用字符串描述字符

2 re模块

一些PythonError

一些代码编写规范

一些python标准库模块

一些内建函数:


第一部分  Python基础

1 变量、注释、运算符

变量

-- 变量名:只能包含字母、数字和下划线,但不能以数字开头。命名应具有描述性

-- 声明全局变量:global,此时可在任何作用域改变全局变量的值

 

-- 常量:不可变的量、空值None

注释

-- 单行注释:使用#表示

-- 块注释:使用三引号,''' 或 """

-- 文档字符串:第一行以某一大写字母开头,以句号结束,第二行为空行,第三行后面是更详细的说明,使用__doc__属性获取,内建函数help()就是获取函数的__doc__属性

运算符及优先级

(expressions...), [expressions...], {key: value...}, {expressions...} 

表示绑定或元组、表示列表、表示字典、表示集合
x[index], x[index:index], x(arguments...), x.attribute  下标、切片、调用、属性引用
** 求幂
+x、-x、~x 正、负、按位取反
*、/、、//、% 乘、除、整除、取余
+、- 加减
<<、>> 移动
& 按位与,也可用于集合
^ 按位异或,也可用于集合,不同取1相同取0
| 按位或,也可用于集合
in、not in 、is、is not、<、<=、>、>=、!=、== 成员资格测试、身份测试、比较
not x  布尔非
and  布尔与
or  布尔或
if - else  条件表达式
lambda lambda表达式
续行符和转义字符  \,如\',\\,\t,\n

 

2 数据类型:数字、布尔、字符串、列表、元组、字典、集合

 

  数字 布尔 字符串 列表 元组 字典 集合 生成器 迭代器
可变/不可变            
资格测试in/not in        
序列sequence(可使用切片)            

可迭代对象Iterable

(集合数据类型,可作用于for循环)

   

<数字> 整数类型int()、浮点数类型float(),python可以处理任意大小的整数

<布尔> True、False

<字符串>  str(),字符串就是一系列用引号引起来的字符,包括单引号、双引号、三引号。在一个字符串中使用多种引号时要注意内容中的引号与命令引号的区分。字符串前加r/R表示原始字符串。字符串也是一种list,每个元素是一个字符,但不可变

一些字符串方法:

修改大小写 首字母大写captialize(),单词首字母大写title()、全部大写upper()、全部小写lower()、
删除两端空白 lstrip()、rstrip()、strip()
出现次数 count()、不管大小写
查找位置 find(),找不到返回-1
以某字符串开始 startswith()
用字符串a连接x a.join(x), 例如'_'.'I love you', I_love_you
拆分 split(),默认以空格为分隔符拆分字符串,返回一个列表
替换 replace(a, b), 把字符串中的a替换成b,字符串是不可变对象,返回一个新字符串
字符串编码解码 字符串编码为bytes:encode("utf-8"),bytes解码为字符串:decode('utf-8'),小部分错误可传入参数errors='ignore'

注意: 对字符串使用list会将每个字符变成一个列表的元素。对列表使用str则会将列表的原始表达整个变成字符串,包括中括号逗点空格
<列表list> 使用方括号[]表示,由一系列按特定顺序排列的元素组成,并用逗号分隔其中的元素。列表之间赋值只是添加新的地址索引。索引从0开始,也可以使用负索引,表示倒数第几个元素。

一些列表方法:

修改元素 添加append(), 插入insert(index, value)<若index大于列表最后一个索引则在最后一个位置插入>
删除元素

直接删除del list[index], 删除任意位置pop(index), 删除最后一个元素pop(), 根据值删除remove(value)<若要删除的值在列表中出现多次,只删除第一个>

排序

永久性排序sort(), 临时排序可调用函数sorted(),反转列表reverse()

出现次数 count(value),返回value在list中出现的次数
返回索引

a.index(b),b元素在列表a中的索引

<元组tuple> 也是一种list,但不可变,可以改变元组变量,也可以改变元组里的可变元素如list。

<字典dict> 字典是一种动态结构,由一系列键值对组成,用花括号括起来。为了方便,一般先定义一个空字典,然后再分行往里面添加键值对。python不关心字典的存储顺序,只跟踪键值对的关联关系

一些字典方法:

遍历键值对 键值对items(), 键keys(), 值values(), 没有顺序,可使用sorted函数排序
删除键值对 del dict[key] 或者 pop(key),删除最后一对popitem()
清空字典 clear()
查看键是否存在

一是使用in表达式,返回True或False

二是使用get(key, default)方法,存在返回键值,不存在返回default(默认返回None)

清空、复制、更新 clear(),copy(),update(dict),也可以是[(k, v)]格式

<集合set> 集合中不包含任何重复元素,由大括号括起来,创建集合set(),需要提供一个可迭代对象作为参数如list,字符串。set和dict的唯一区别是set中没有存储相应的键值,与dict一样,不可放入可变对象。放入元组可以,但放入包含list的元组不可以。

一些集合方法:

添加 add(value)
删除 remove(value)
清空集合 clear()
取出一个元素 pop()
复制一个新的集合 copy()
是否是另一个集合的超集 issuperset()

3 控制流:if、for、while

 语法:if ... : ... [elif ... : ... elif ... : ... [else: ...]],只有一个判断使用if,两个使用if-else,三个以上使用if-elif-else,或只是用if-elif不使用else使判断逻辑更清楚

 语法: for ... in ... : ...,注意:for循环是一种遍历列表的有效方式,但不应在for循环里修改列表,否则将导致python难以跟踪其中的元素。使用while循环修改

 语法:while expression: ...,使用break退出循环,使用continue结束本次循环,for循环也可以使用

4 函数(function)

处理特定的任务的代码块,使用def定义,可以返回任意类型的值,包括list,tuple和dict等复杂数据结构

<函数参数> 参数类型及定义顺序:位置参数,默认值参数,可变参数,命名关键字参数,可变关键字参数。<若指定某些命名关键字参数时,又没有任意参数,则需要在位置参数后面用*间隔,如 def person(a, b, *, c, d)>

位置参数 要求实参和形参的一一对应,函数形参的类型取决于实参的类型
默认值参数

给形参指定默认值,在没有位置参数后面

可变参数 使用*args,将收到的所有值都封装到这个元组中。若已有list或tuple,可在前面加*变成可变参数传进去
命名关键字参数 在可变参数或*后面,必须通过关键字调用传递实参,也可使用默认值
可变关键字参数 使用**keys,将受到的所有键值对都存储在字典keys中,对于已有字典可加**变成可变参数传进去

<注意>

  • 必要时检查函数参数的类型;
  • 函数默认值必须指向不可变对象;
  • 传递列表是传递原始列表,禁止修改可使用list[:]创建副本,除非有充分理由传递副本,否则还是应该传递原始列表,避免花费额外的时间和内存创建副本。

<递归函数> 逻辑简单清晰,但过深的调用会导致栈溢出

5 模块(module)和包(package)

一个文件即是一个模块

<标准文件模板>

#!/usr/bin/env python3

# -*- coding: utf-8 -*-

"""注释"""

__author__ = 'Bingmous'

这两行是标准注释,第一行可以让这个文件直接在Unix/Linux/Mac上运行,windows忽略这行,第二行表示文件本身使用标准utf-8编码,第三行是一个字符串,表示模块的文档字符串,任何模块代码的第一个字符串都被视为模块的文档注释。第四行是作者

<模块搜索路径>

第一种方法是在sys.path中添加,运行时修改,运行后失效

第二种是设置环境变量PYTHONPATH,该环境变量的内容会被自动添加到搜索路径中,只需添加自己的搜索路径,python自身的搜索路径不受影响

<导入模块>

使用import导入模块,也可以使用from ... import ...导入其中的一些方法或模块,from ... import * 表示导入所有,若导入的函数与程序中现有的冲突,可使用as指定别名

<包package>

为了避免模块名冲突,python引入了包,每一个包目录下都有一个__init__.py文件,否则python把这个目录当成普通目录而不是一个包,包可以由多级目录。自己创建模块时要注意命名,不要和python自带的模块名称冲突


第二部分  高级特性:切片、迭代、列表解析、生成器、迭代器

<切片slice> sequence[beg:end[:step]],不写beg表示从0开始,不写end表示到最后一个结束,写end结果不包含end。复制列表使用[:],序列都可以使用切片,字符串,列表,元组

<迭代Iterable> 只要是可迭代对象都可以迭代,可以使用collections里的Iterable和内置函数isinstance判断一个对象是否可迭代。序列都是可迭代对象,字典和集合也是

<列表解析list comprehensions> list_name = [expr for var in range(args) if expr else expr] 可以使用多个循环,也可以使用{}生成字典,也可以使用tuple()、set()生成元组、字典

<生成器generator> 通过列表解析可以生成一个列表,但受于内存限制,如果元素可以按照某种算法推算出来,那么就不用创建完整的列表,这种一边循环一边计算的机制称为生成器。

创建方法一:只需将列表解析[]改成()就创建了一个generator。通过函数next(generator)计算下一个值,知道计算结束抛出StopIteration异常。通常使用for循环遍历,因为generator也是可迭代对象。

创建方法二:不能通过列表解析创建生成器时可使用函数创建,只要函数中包含yield关键字,它就不是一个普通函数而是一个generator。

执行逻辑:函数是顺序执行,遇到return或执行到最后一行就返回。而变成generator的函数,在每次调用next时遇到yield就返回,再次执行时从上次返回的yield语句处继续执行。

<迭代器iterator> 可直接作用于for循环的数据类型有:一类是集合数据类型str、list、tuple、dict、set等,一类是generator和带yield的generator函数,这些可直接作用于for循环的统称为可迭代对象。而generator不仅可以直接作用于for循环还可以被next()函数不断调用并返回下一个值,直到抛出StopIterations异常。可以被next()函数调用并不断返回下一个值的对象称为迭代器。

为什么str、list、dict、set等数据类型不是迭代器?因为python的Iterator对象表示的是一个数据流,可以把这个数据流看出一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算时惰性的,只有在需要返回下一个数据时它才会计算。

把iterable对象变成iterator可以使用iter()函数

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是不可能存储全体自然数的。


第三部分  函数式编程 (functional programming)

函数式编程是一种抽象程度很高的编程范式,一个特点就是允许把函数本身作为参数传入另一个函数,还允许返回一个参数

1 高阶函数 (Higher-order function)(functools模块)

变量可以指向函数,函数名也是变量,那么函数可以传入函数作为参数,编写高阶函数就是让函数的参数可以接受函数

  • map()函数,接收一个函数和一个序列iterable,返回一个Iterator。把函数对象作用到每一个Iterable的元素上面
  • reduce()函数,把一个函数作用在一个序列上,这个函数必须接受两个参数,把结果和下一个元素作为参数继续计算
  • filter()函数,接受一个函数和一个序列,把函数依次作用于每个元素根据返回值是True还是False选择留下还是丢弃该元素
  • sorted()函数,也是一个高阶函数,排序函数可以使用关键字key传入,反转可以使用reverse传入

2 返回函数(闭包closure)

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。此时不需要计算出返回值的值,到后面需要的时候在调用计算。这样相关的参数和变量都保存在返回的函数中,称为闭包。<注意> 返回闭包时的返回函数不要包含任何循环变量,或者后续会继续发生变化的变量。

3 匿名函数(anonymous function)

关键字lambda表示匿名函数,冒号前面表示函数参数,后面跟一个表达式,返回值就是该表达式的结果。好处是函数没有名字,不必担心函数名字冲突,缺点是只能有一个表达式。匿名函数也是一个函数对象。

4 装饰器(decorator)

增强函数的功能,本质上decorator就是一个返回函数的高阶函数,只不过在返回原函数时增加额外的功能。在函数定义处使用语法@,装饰器返回wrapper,wrapper函数返回原函数的调用,所以使用装饰器,在调用原函数时相当于调用wrapper函数,在其中增加功能,同时返回值再调用原函数,保留原函数。@log相当于func = log(func)。

如果decorator本身需要传入参数,那就需要在最外层编写一个返回decorator的高阶函数,在调用时@log('text')相当于func = log('text')(func),调用log并传入所需参数,返回装饰器,再调用装饰器返回wrapper,所以最终还是返回了wrapper,在调用原函数时相当于调用了wrapper函数

在装饰器里面,wrapper函数外面写@functools.wraps(func),解决使用装饰器后的函数的__name__属性还是原来的属性

5 偏函数(partial function)

偏函数的作用就是固定原函数的一些参数,返回一个新的函数,方便调用,使用functools.partial创建偏函数


第四部分  面向对象编程(Object-Oriented Programming, OOP)

面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行,为了简化程序,把函数继续分割为子函数,降低系统的复杂度。

而面向对象的程序设计是把计算机程序视为一组对象集合,而每个对象可以接受其他对象发来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。

类(Class)是一种抽象的概念,而实例(Instance)是一个具体的对象。面向对象是抽象出Class,根据Class创建Instance

面向对象的抽象程度比函数要高,因为一个类既包含数据,又包含操作数据的方法。

1 类和实例

<类的定义> 使用关键字class定义类,一般类名首字母大写, 使用__init__(self, args)方法绑定必要属性,python自动把实例变量传递给self,它是指向实例本身的引用,这样实例就可以访问类中的属性和方法了。也可以使用对象动态绑定属性和方法

<数据封装> 实例本身拥有数据,那么就没必要用外面的函数访问数据,可直接在类的内部定义访问数据的函数,这样就把数据封装起来了,这些封装数据的函数是和类关联起来的,称为方法(Method)

<类属性和类方法> 类属性:类本身绑定的属性,类属性和实例属性相同时类属性会被隐藏,可以使用类名访问类属性,也可以使用对象访问类属性,当对象的属性中没有时会去类属性里去找。类方法:使用装饰器@classmethord定义,只能使用类进行访问。可以通过对象的__class__属性找到其所属的类。

<给类属性指定默认值>

  • 在__init__(self)方法中给属性设置默认值,不提供初始化形参,每次创建实例都初始化为这个值
  • 使用默认值参数,在形参列表中给出默认值,初始化时可以修改这个默认值。

2 访问限制

python没有任何机制阻止你干坏事,一切全靠自觉

  • 在类的内部变量前添加两个下划线,变成私有变量,这样从外部就无法访问这个变量了,实际上这个变量被python改成了_Class__var,访问和修改变量可以通过类的方法实现,还可以对参数进行检查,避免传入无效参数
  • 变量以双下划线开头,并以双下划线结尾的可以访问,不是私有变量
  • 以单下划线开头的变量,不是私有变量,但按照约定俗成,当你看到时,意思是“虽然可以访问,但是请把我当成私有变量,不要随意访问”

3 继承和多态

当我们定义一个类时,可以从现有的类继承,新的类称为子类(subclass),被继承的类称为基类(base class)、父类或超类(super class),继承的好处就是子类自动获得了父类的所有功能,同时子类的对象不仅是子类的类型也是父类的类型。python允许多重继承

子类的__init__(self)方法:首先需要给父类的所有属性赋值,在其形参列表中先包含父类的所有参数,然后使用super().__init__()初始化父类属性,形参包含所有的父类属性,不包含self,也可以使用fatherClass.__init__(self)初始化

子类方法与父类方法重名,则子类覆盖父类

多态的意思就是当我们需要子类类型的对象时,我们只需要接受一个父类的类型就行了,因为子类也是父类的类型,所有调用父类类型对象的,调用子类类型对象也可以正常运行,这就是多态。我们只需要知道它是父类类型,具体是什么子类类型不需要知道也可以调用父类的方法,由运行时该类型的确切类型确定,这就是多态的威力,调用方只管用,不管细节。这就是著名的开闭原则:对扩展开放,允许继承类;对修改封闭,不需要修改依赖父类类型的调用方的函数。

python是动态语言,动态语言的“鸭子类型”特点决定了继承不像静态语言那样是必须的,并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看作鸭子。

4 获取对象信息

<查看对象状态>

  • 使用type()返回对象类型class,判断一个对象是不是函数,可使用types里定义的类型常量
  • 使用isinstance()判断,若是类的继承关系使用type就很不方便,isinstance也可以判断某些类型中的一种
  • dir(), 获取对象的所有属性和方法

<操作对象状态>

查看是否有属性hasattr(obj, attr),动态绑定属性setattr(obj, attr),获取属性getattr(obj, attr)


第五部分 面向对象高级编程

1 使用__slots__限制属性

当我们定义了一个class,定义了一个class的实例之后,可以给该实例绑定任何属性和方法。

  • 绑定属性 instance.attr = value
  • 绑定方法 instance.method = MethodType(method, instance),要使用MethodType把method绑定到instance上。绑定到实例的方法只有实例可以使用,其他实例不起作用。
  • 给类绑定一个方法 class.method = method,此时所有实例都可以调用。这样的方法可以直接定义在类中,但动态绑定允许我们在程序运行中动态给class添加功能。

如果我们要限制绑定的属性,可以在类的定义中使用__slots__ = (),元组中列出限制属性,这样定义的实例只能添加__slots__限制的属性,添加其他属性会导致AttributeError,仅对当前的实例起作用,对继承的子类不起作用,若子类也定义了__slots__,那么子类允许定义的属性就是自身的__slots__加上父类的__slots__

2 使用@property把方法变成属性

直接绑定属性没办法检查参数,可以添加方法设置,不过调用方法略复杂,那么可以通过@property把类方法变成属性,这样又可以检查参数,又使用方便

如果只是只读,在函数前加@property,返回函数名前加下划线

如果是读写,再定义另外一个函数前面加@name.setter,函数接受一个value赋值给self._name

@property可以让调用者写出简单的代码,同时保证对参数进行必要的检查

3 定制类:__str__, __repr__

  • __str__是调用类创建新实例时显示给用户的字符串,__repr__是调试时显示的
  • 如果一个类想被用于for循环,也就是变成一个迭代器,必须实现一个__iter__方法,返回一个迭代对象,然后for循环不断调用该迭代对象的__next__方法拿到循环的下一个值,知道遇到StopIteration错误时退出循环
  • 像list那样可以用下标取元素需要实现__getiem__方法,传入参数n为下标,返回相应下标的值,切片功能的设计
  • __getattr__,当调用属性不存在时,python会调用__getattr__(self, attr)来捕获属性,这样我们就有机会返回属性的值,也可以返回函数,只要调用方式为调用函数
  • 对象实例在本身上调用,需要定义__call__方法,还可以定义参数。本质上,对象和函数没有什么区别。判断一个对象是否可调用使用callable()

4 使用枚举类(enum模块)

使用枚举类为枚举类型定义一个class类型,每个常量都是class的唯一实例,使用Enum类实现,例如:from enum import Enum,Month = Enum('Month', (...)),也可以从Enum继承,更精确的控制枚举类型,使用装饰器@unique检查保证没有重复值,from enum import unique。可以使用成员名称引用枚举常量Weekday.Mon, Weekday['Mon'],也可以根据value的值获得枚举常量Weekday(1)

5 使用type()创建类,元类

  • type()既可以返回一个函数的类型,又可以创建新的类型,需要传入三个参数:1,类的名字,2,继承的父类,3,class的方法名称与函数绑定
  • 使用元类动态的创建类,控制类的创建行为

第六部分  错误、调试和测试

1 错误处理

try ... except ... [as ...] else ... finally ...,可以有多个except,也可以多个Error写在一起,else表示try正确处理后执行,finally一定会执行

  • except不仅捕获该类型错误,也捕获其子类型的错误
  • 使用logging.exception(e)记录错误信息
  • 抛出异常raise,不带参数表示把当前错误原样抛出,在except中使用raise一个Error可以把一种类型转换成另外一种类型
  • 继承Exception编写自己的异常

常见的错误类型和继承关系

https://docs.python.org/3/library/exceptions.html#exception-hierarchy

2 调试

  • 使用print(),把可能有问题的变量打印出来,缺点是调试完需要删除print()语句
  • 凡是用print()来辅助查看的地方,都可以用断言代替,assert expression, info,断言失败会抛出AssertionError,也比print()好不到哪去,可以在启动python解释器时使用python -O 来关闭assert
  • logging,logging.info()输出文本信息,可以配置logging.basicConfig(level=logging.INFO)输出不同级别信息,级别由低到高:debug, info, warning, error
  • pdb, 启动python -m pdb  file.py,命令n单步执行代码,p var查看变量,c继续运行,q结束调试。在代码中使用pdb.set_trace()设置断点
  • IDE调试,Visual Studio Code(需要安装python插件),pycharm,Eclipse加上pydev插件也可以

3 单元测试(unittest模块)

单元测试是对一个模块、一个函数或一个类来进行正确性检验的工作

  1. 为了编写单元测试,需要引入unittest模块,并导入需要测试的模块、函数或类
  2. 编写测试的类,需要从unittest.TestCase继承,测试的类的方法都要以test打头,不以test打头的方法不被认为是测试方法
  3. unittest.TestCase提供了很多内置的条件判断,我们只需要调用这些方法就可以断言输出是不是我们所期望的,常用的断言assertEqual(), assertTrue(), 期待某种行为会抛出某种Error使用with self.assertRaise(Error): action
  4. 运行单元测试,1使用if __name__ == '__main__': unittest.main(),把它当成python脚本运行,2是使用python -m unittest file.py,推荐做法,因为这样可以一次批量运行很多单元测试,并且有很多工具可以自动运行这些测试
  5. 在测试类中编写两个特殊的方法setUp(), tearDown(),这两个方法会在调用每一个测试方法的前后分别被执行。比如测试需要启动一个数据库,在setUp中连接数据库,在tearDown中关闭数据库,就不必在每个测试方法中重复相同的代码。比如测试类时,在setUp中添加类的对象作为一个属性self.attr = Cls(),后面再写测试方法时可以通过self.attr来调用这个对象,不用每次都创建一个对象。

4 文档测试

写在注释中的代码,也可自动执行,这样使用文档工具生成的文档里的代码就可以直接粘贴运行。使用doctest模块doctest.testmod(),doctest严格按照python交互式命令行的输入和输出判断测试结果是否正确,只要测试异常的时候可以用...表示中间的一大段烦人的输出。使用if __name__ == '__main__' 使其只有在使用命令行直接运行时才执行doctest.testmod(),在非测试环境下不执行


第七部分  IO编程

1 文件读写

<读文件> f = open(), f.close() 或使用try finally 释放资源避免在读写时出现IOError无法正常关闭文件,或使用with open()等同于前面的try finally。read(),一次读取文件所有内容,readlines()一次读取所有内容并按行返回list,读取一定大小read(size),根据需要决定怎么调用

<二进制文件> 读取二进制文件使用'rb'模式

<字符编码> 若读取非utf-8编码格式,则需要传入encoding参数;open()还接受一个errors参数,表示如果遇到编码错误后如何处理

<写文件> 使用方法write(),需将读写模式改为'w'或'wb'表示写文本文件或写二进制文件,其他模式的定义及含义参考官方文档:

https://docs.python.org/3/library/functions.html#open

注意:with语句内只能读取一次,使用read后再使用readlines时文件以及关闭了

2 StringIO和BytesIO(io模块)

数据读写不一定是文件,也可以在内存中读写数据流,from io import StringIO, BytesIO

  • 在内存中读写str,首先要创建一个StringIO,f = StringIO(),
  • StringIO操作的只能是str,要操作二进制数据就需要使用BytesIO,首先创建一个BytesIO, f = BytesIO()
  • 写入内容:f.write(),写入BytesIO时必须是bytes-like对象,读取内容:f.getvalue()
  • read(), readline(), readlines()只能读取一次,读取多次使用getvalue()

3 操作文件和目录(os模块)

import os

<系统名和环境变量>

  • os.name 查看操作系统的类型,Linux,Unix或Mac OS X返回posix,Windows系统返回nt
  • os.environ 查看在操作系统中定义的所有环境变量
  • os.environ.get('key')返回key下的环境变量,os.environ('x', 'default')定义x不存在时返回值default

<操作文件和目录> 注意操作文件和目录的函数一部分放在os模块中,一部分放在os.path中

  • os.path.abspath('.') 查看当前路径的绝对路径,返回abs_path
  • os.path.abs.path('new_dir') 创建新目录的绝对路径,返回abs_dir\new_dir
  • os.path.join('abs_path', 'new_dir') 创建新目录的绝对路径,使用join可以正确处理不同操作系统的路径分隔符
  • os.path.mkdir('abs_path') 创建目录abs_path
  • os.rmdir('abs_path') 删除目录abs_path
  • os.path.split('abs_path') 把一个路径拆分为两部分,后一部分总是最后一级目录或文件名
  • os.path.splitext('abs_path') 把一个路径拆分为两部分,后一部分为扩展名,文件夹则无扩展名
  • os.rename('old_name', 'new_name') 重命名
  • os.remove('file_name') 删除文件
  • 使用shutil模块的copyfile()可以复制文件
  • os.listdir('.') 列出当前目录下的所有文件和目录
  • os.path.isdir('x') 判断x是不是文件夹

4 序列化(pickle模块,json模块)

把变量从内存中变成可存储或可传输的过程称为序列化,python中叫pickling,在其他语言中也被称之为serialization,marshalling,flattening等等,序列化之后就可以把序列化之后的内容写入磁盘,或通过网络传输到其他机器。反过来,把变量内容从序列化的对象从新读到内存里称为反序列化,即unpickling。python提供了pickle模块实现序列化

序列化:pickle.dump(data, file-like obj) 二进制写模式;反序列化:pickle.load(file-like obj) 二进制读写模式

只能用于python,不同python版本之间可能都不兼容,因此只能保存那些不重要的数据,不能成功的反序列化也没关系

要在不同的编程语言之间传递对象,就必须把对象序列化为标准格式,如xml,最好是json,因为json表示出来就是一个字符串,可以被所有语言读取,也可以方便的存储和网络传输。json不仅是标准格式而且比xml快,可以在web中读取

dumps()方法返回一个str,内容是标准的json,类似的dump()方法直接把json写入一个file-like obj

把json反序列化为一个python对象,使用loads()或对应的load()方法,前者把json的字符串反序列化,后者从file-like obj读取字符串并反序列化

json标准规定json编码是utf-8,所以我们总能正确的在python的str和json的字符串之间转换

序列化自定义类的对象,首先需要编写一个函数告诉json.dumps()如何序列化,传入参数default=,如返回字典等。反序列化也需要编写函数告诉json.loads()如何反序列化,传入参数object_hook=,如通过字典重新创建对象,返回对象

json.dumps可选参数如下

https://docs.python.org/3/library/json.html#json.dumps

也可以偷懒,传入default=lambda obj: obj.__dict__,一般没有定义__slots__的类都有__dict__属性,包含了对象的属性字典


第八部分 进程和线程

1 多进程(os模块,multiprocessing模块,)

Unix/Linux操作系统提供了fork()系统调用,它非常特殊,调用一次返回两次,操作系统自动把当前进程(父进程)复制一份(子进程),然后分别在父进程和子进程内放回,子进程永远返回0,父进程返回子进程ID。调用os.fork(),os.getpid()返回当前进程,os.getppid(),返回父进程

跨平台版本的多进程模块,提供了一个Process类来代表一个进程对象。创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Porcess实例,用start方法启动,这样创建进程比fork()还要简单,join()方法可以等待子进程结束后再继续往下进行,通常用于进程间的同步。

from multiprocessing import Process
import os

def run_proc(args): # 子进程要执行的代码
    pass

if __name__ == '__main__':
    p = Process(target=run_proc, args=(args,)) # 传入一个执行函数和函数的参数
    p.start() # 开始执行进程
    p.join() # 等待子进程结束后再继续往下运行,通常用于进程间的同步

如果要启用大量的子进程,可以用进程池的方式批量创建子进程:

from multiprocess import Pool

def long_time_task(args): # 某个执行时间很长的任务
    pass

if __name__ == '__main__':
    p = Pool(4) # 设置最多可同时执行的进程数,默认是cpu核数
    for i in range(5): # 创建多个进程任务,传入进程函数和函数参数
        p.apply_async(long_time_task, args=(i,))
    p.close() # 调用join()之前必须调用close(),调用close()之后就不能添加进程了 
    p.join() # 等待所有子进程执行完毕

<子进程> 很多时候,子进程并不是自身,而是一个外部进程,我们创建一个子进程后还要控制子进程的输入和输出

执行一个子进程

import subprocess

r = subprocess.call([nslookup, 'www.python.org']) # 相当于在命令行执行 nslookup www.python.org
print('Exit code:', r) # r为进程的返回值

如果子进程还需要输入,则可以通过communicate()方法输入

import subprocess

p = subprocess.Popen(['nslookup'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# 上面一行代码相当于在命令行执行nslookup
output, err = p.communicate(b'set q=mx\npython.org\nexit\n') # 输入
print(output.decode('utf-8', errors='ignore'))
print('Exit code:', p.returncode)

# 上面的代码相当于在命令行执行nslookup,然后输入
# set q=mx
# python.org
# exit

<进程间通信> Process之间肯定是需要通信的,操作系统提供了很多机制实现进程间的通信,python的multiprocessing模块包装了低层的机制,提供了Queue、Pipes等多种方式交换数据。以Queue为例,一个往Queue里写数据,一个读数据

from multiprocessing import Process, Queue

# 写数据进程执行的代码
def write(q):
    for value in ['A', 'B']:
        q.put(value)

# 读数据进程执行的代码
def read(q):
    while True:
        value = q.get(True) # True没有数据时等待

if __name__ == '__main__':
    q = Queue # 父进程创建一个Queue,并传给各个子进程
    pw.start() # 启动子进程pw,写入
    pr.start() # 启动子进程pr,读取,有数据就取
    pw.join() # 等待pw结束
    pr.terminate() # pr进程是死循环,无法等待其结束,强制终止

注意:父进程所有python对象都必须通过pickle序列化再传到子进程去,所以,若multiprocessing在Windows下调用失败,要先考虑是不是pickle失败了

2 多线程(模块_thread,threading,Lock)

Python的标准库提供了两个模块:_thread和threading,_thread是低级模块,threading是高级模块,对_thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。

<启动一个线程> 创建一个thread实例并传入一个函数和其参数,然后调用start()开始执行

import threading

# 新线程执行的代码
def loop():
    print('thread {} is running...'.formate(threading.current_thread().name)) # 显示子线程

print('thread {} is running...'.formate(threading.current_thread().name)) # 显示主线程
t = threading.Thread(target=loop, name='LoopThread') # 创建子线程,传入函数,命名子线程
t.start() # 子进程开始 
t.join() # 等待子进程结束
print('thread {} ended.'.formate(threading.current_thread().name)) # 子线程先结束,主线程后结束

由于任何进程都会默认启动一个线程,该线程称为主线程,主线程又可以启动新的线程。threading模块有个current_thread()函数,它永远返回当前线程的实例,主线程的名字叫MainThread,子线程的名字在创建时指定,也可以不起名字

多线程中所有变量由所有线程共享,所以任何一个变量都可以被任何一个线程修改,为了避免同时修改,就需要给修改的函数上一把锁,通过threading.Lock()创建一个锁,在执行lock.acquire()时获得锁,若多个线程同时执行,则只有一个线程获得锁,其他线程等待直到获得锁。释放锁:lock.release()

获得锁的线程用完后一定要释放锁,否则其他线程将永远等下去,成为死线程。锁的好处是确保某一段关键代码只能由一个线程完整的执行到尾,坏处也很多,首先是阻止了多进程的并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大下降,其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,又不能结束,只能由操作系统强制终止

import threading

balance = 0 # 假设是银行存款
lock = threading.Lock() # 创建一个锁

def change_it(n):
    globe balance # 先存后取
    balance = balance + n
    balance = balance - n

def run_thread(n):
    for i in range(10000):
        lock.acquire() # 先要获取锁
        try:
            change_it(n) # 放心改吧
        finally:
            lock.release() # 改完了释放锁

t1 = threading.Thread(target=run_thread, args=(5,)) # 创建线程t1
t2 = threading.Thread(target=run_thread, args=(8,)) # 创建线程t2
t1.start() # 启动线程t1
t2.start() # 启动线程t2
t1.join() # 等待线程t1结束
t2.join() # 等待线程t2结束

虽然python的线程是真正的线程,但解释器执行代码时,有一个GIL锁(Global Interpreter Lock),任何python线程执行前必须先获得GIL锁,然后执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以多线程在python只能交替执行,GIL是python解释器设计的历史遗留问题,通常我们用的解释器是官方实现的的CPython,要真正利用多核,除非重写一个不带GIL的解释器

所以在python中,可以使用多线程,但不要指望能有效利用多核,如果一定要利用可以使用C扩展来实现,不过就是去了python简单易用的特点

不过虽然不能使用多线程实现多核任务,但可以通过多进程实现多核任务,多个python进程有各自独立的GIL锁,互不影响

3 ThreadLocal

解决在一个线程中,函数之间传递参数的问题,创建一个全局ThreadLocal对象,每个线程都可以对它的属性进行读写,但属性在线程之间互不影响,都是线程的局部变量

import threading

local_schlool = threading.local() # 创建全局ThreadLocal对象

def process_student():
    std = local_school.student # 获取当前线程关联的student

def process_thread(name):
    local_school.student = name # student是每个线程的局部变量
    process_student()

t1 = threading.Thread(target=process_thread, args=('Alice',), name='Thread-A')
t2 = threading.Thread(target=process_thread, args=('Bob'), name='Thread-B')
t1.start()
t2.start()
t1.join()
t2.join()

4 分布式进程(mutiprocessing.managers模块,queue模块)

在Thread和Process中优选Process,因为Process更稳定,可以分布到多个机器上,python的multiprocessing模块不但支持多进程,其中managers子模块还支持把多进程分布到多台机器上。一个服务进程作为调度者,将任务分布到其他多个进程中,依靠网络通信。例子:现在已经有一个通过Queue通信的多进程程序在同一机器上运行,现在需要把发布任务的进程和处理任务的进程分布到两台机器上,原有的Queue继续使用,通过managers模块把Queue通过网络暴露出去,让其他机器可访问Queue

服务进程,负责启动Queue,把Queue注册到网上,然后往Queue里写任务:

# task_master.py
import queue
from multiprocessing.managemers import BaseManager

task_queue = queue.Queue() # 发送任务的队列
result_queue = queue.Queue() # 接受结果的队列

class QueueManager(BaseManager): # 从BaseManager继承的QueueManager
    pass

# 把两个Queue都注册到网上,callable参数关联了Queue对象
QueueManager.register('get_task_queue', callable=lambda: task_queue)
QueueManager.register('get_result_queue', callable=lambda: result_queue)

manager = QueueManager(address=('', 5000), authkey=b'abc') # 绑定端口5000,设置验证码'abc'
manager.start() # 启动Queue
task = manager.get_task_queue() # 获得通过网络访问的Queue对象
result = manager.get_result_queue()

for i in range(10): # 放几个任务到task里面,也即是通过get_task_queue访问的task_queue
    task.put(i)

for i in range(10): # 从result队列中读取结果
    r = result.get()
    print(r)

manager.shutdown() # 关闭

当我们在一台机器上写多进程程序时,创建的Queue可以直接拿来用,但是在分布式多进程环境下,添加任务到Queue不可以直接对原始的task_queue进行操作,那样就让过了QueueManager的封装,必须通过manager.get_task_queue()获得Queue的接口,然后添加任务

在另外一台机器上启动任务进程(本机上启动也可以):

# task_worker.py
import queue
from multiprocessing.managers import BaseManager

class QueueManager(BaseManager): # 创建类似的QueueManager
    pass

# 由于这个QueueManager只能从网络上获取Queue,所以注册时只提供名字
QueueManager.register('get_task_queue') 
QueueManager.register('get_result_queue')

# 连接到服务器,也就是运行task_master.py的机器
server_addr = '127.0.0.1'
m = QueueManager(address=(server_addr, 5000), authkey=b'abc') # 端口和验证码与task_master.py设置的完全一致
m.connect() # 从网络连接

task = m.get_task_queue() # 获取Queue的对象
result = m.get_reslut_queue()

for i in range(10): # 从task队列获取任务,并把结果写入result队列
    try:
        n = task.get()
        r = n * n
        result.put(r)
    except Queue.Empty:
        print('task queue is empty.')

第九部分  正则表达式

正则表达式是一种用来匹配字符串的强有力的武器,它的设计思想是用一种描述性语言给字符串定义一个规则,凡是符合规则的字符串,我们就认为它匹配了,否则该字符串就是不合法的。

1 如何用字符串描述字符

在正则表达式中,如果直接给出字符,就是精确匹配。

  • \d可以匹配一个数字,
  • \w可以匹配一个字母或数字,
  • .可以匹配任意字符。

要匹配变长的字符,

  • *表示任意个字符(包括0个),
  • +表示至少一个,
  • 表示1个或0个,
  • {n}表示n个字符,
  • {n, m}表示n-m个字符,
  • \s可以匹配一个空格(也包括Tab等空白符),
  • 特殊字符-需要转义\-

要做更精确的匹配,可以用[]表示范围,用A|B可以匹配A或B,用^表示行开头,用$表示行的结束

2 re模块

  • 匹配:re.match('正则表达式', 'test') 匹配成功返回一个Match对象,否则返回None
  • 切分字符串:re.split('正则表达式', 'test') 切分字符串,返回切分后的数组
  • 分组:在匹配时使用()提取分组,返回Match对象,使用m.group()提取分组,group(0)永远是原始字符串,group(1),group(2)...表示第1、2...个字符串

正则匹配默认是贪婪匹配,加?采用非贪婪匹配

编译:在python中使用正则表达式时,re模块内部会干两件事,首先是编译正则表达式,如果正则表达式字符串本身不合法会报错,然后用编译后的正则表达式去匹配字符串。如果一个正则表达式要重复使用几千次,出于效率的考虑,我们可以预编译该表达式使用p = re.compile('正则表达式')返回一个Pattern对象,然后使用p.match('string').group()返回匹配的字符串,编译后生成Regular Expression对象,该对象自己包含了正则表达式,所以调用对应的方法时不用给出正则字符串。


一些PythonError

https://docs.python.org/3/library/exceptions.html#exception-hierarchy

python Error 可以使用raise抛出可以接受一个参数(参数检查)

  1. TypeError: 'int' object is not callable:是因为在调用某个函数的时候,这个函数被使用成了一个int类型的变量,故int对象不可调用
  2. NameError
  3. FileNotFoundError
  4. ZeroDivisionError
  5. EOFError:发现文件结尾符在不该出现的时候出现了(ctrl + D)
  6. KeyboardInterrupt:发现了取消操作(ctrl + C)
  7. TypeError:类型错误

一些代码编写规范

<编写函数> 

  1. 编写函数时,给函数指定描述性名称,且只使用小写字母和下划线,在函数后面紧跟文档字符串格式的注释
  2. 默认值形参等号两边不要有空格,关键字实参也不要有空格
  3. 代码长度不要超过79字符,可在函数定义中左括号后回车
  4. 函数之间使用两个空行隔开

<编写类> 

  1. 类名应采用驼峰命名法,即在类名中每个单词的首字母都大写,而不使用下划线。实例名和模块名使用小写,并在单词之间使用下划线
  2. 在类中,使用一个空行分隔方法在模块中,使用两个空行分隔类
  3. 需要同时导入标准库中的模块和自己编写的模块时,先导入标准库的模块,再添加一个空行,然后导入自己编写的模块

一些python标准库模块

  1. 模块collections,其中的OrderedDict类可以记录键值对的添加顺序,使用方法Dict_name = collections.OrderedDict()<使用括号是调用类的意思>,建立一个空的字典。Iterable类型,结合isinstance判断对象是否可迭代
  2. sys模块,version 查看python版本,version_info查看版本信息
  3. os模块,和系统交互
  4. platform模块,获取平台操作系统的信息
  5. logging模块,存储日志

一些内建函数:

查看所有内建函数import builtins dir(builtins) https://docs.python.org/3/library/functions.html#

输入输出 input():仅接受一个参数为要向用户显示的提示或说明,返回值为字符串类型

print():字符串的格式化方法:‘{a:b^c.nf}’.format(args),括号的参数会替代大括号,b表示用b补充,长度为c,^<>表示居中居左居右。n为保留小数维数,取消print末尾的换行符使用end=''。接受多个参数时,逗号分隔,逗号会打印出空格

类型转换 int(), float, bool(), str(), list(), tuple(), dict(), set()
查看类型/实例 type()、isinstance(obj, class_or_tuple)
计算长度 len()
range()函数 range(beg, [end, step]),一次只生成一个数字,创建数字列表或元组使用list()或tuple()
最大、最小、求和、绝对值 min(list)、max(list)、sum(list)、abs()
返回由对象定义的名称列表 dir()
查看文档 help()
删除名称 del x
排序

sorted(iterable, key, reverse), 临时排序,返回列表。key是函数,排序依据

如sorted(dict_name.items(), key=operator.itemgetter(1), reverse=True),返回键值对,根据键值从大到小排序。operator.itemgetter(1)表示使用前面每一项的第1个索引排序,也就是字典的值

进制转换 二进制bin(), 八进制oct(), 十六进制hex(), 进制前缀0b, 0o, 0x或0B, 0O, 0X
字符与数字转换 字符转数字ord(), 数字转字符chr()
同时迭代索引和元素 enumerate(sequence), 可以把sequence变成索引元素对,可同时迭代索引和元素
执行迭代器 next(), 可以修改默认返回值而不返回StopIteration异常
将iterable变成iterator iter()
操作对象属性 hasattr(), 返回bool, getattr(), 没有属性则会报错,可设返回值, setattr()设置属性
切片类 slice
是否可调用 callable

你可能感兴趣的:(Python)