目录
第一部分 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标准库模块
一些内建函数:
变量 | -- 变量名:只能包含字母、数字和下划线,但不能以数字开头。命名应具有描述性 -- 声明全局变量: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 |
数字 | 布尔 | 字符串 | 列表 | 元组 | 字典 | 集合 | 生成器 | 迭代器 | |
可变/不可变 | √ | √ | √ | ||||||
资格测试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() |
处理特定的任务的代码块,使用def定义,可以返回任意类型的值,包括list,tuple和dict等复杂数据结构
<函数参数> 参数类型及定义顺序:位置参数,默认值参数,可变参数,命名关键字参数,可变关键字参数。<若指定某些命名关键字参数时,又没有任意参数,则需要在位置参数后面用*间隔,如 def person(a, b, *, c, d)>
位置参数 | 要求实参和形参的一一对应,函数形参的类型取决于实参的类型 |
默认值参数 | 给形参指定默认值,在没有位置参数后面 |
可变参数 | 使用*args,将收到的所有值都封装到这个元组中。若已有list或tuple,可在前面加*变成可变参数传进去 |
命名关键字参数 | 在可变参数或*后面,必须通过关键字调用传递实参,也可使用默认值 |
可变关键字参数 | 使用**keys,将受到的所有键值对都存储在字典keys中,对于已有字典可加**变成可变参数传进去 |
<注意>
<递归函数> 逻辑简单清晰,但过深的调用会导致栈溢出
一个文件即是一个模块
<标准文件模板>
#!/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是不可能存储全体自然数的。
函数式编程是一种抽象程度很高的编程范式,一个特点就是允许把函数本身作为参数传入另一个函数,还允许返回一个参数
变量可以指向函数,函数名也是变量,那么函数可以传入函数作为参数,编写高阶函数就是让函数的参数可以接受函数
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。此时不需要计算出返回值的值,到后面需要的时候在调用计算。这样相关的参数和变量都保存在返回的函数中,称为闭包。<注意> 返回闭包时的返回函数不要包含任何循环变量,或者后续会继续发生变化的变量。
关键字lambda表示匿名函数,冒号前面表示函数参数,后面跟一个表达式,返回值就是该表达式的结果。好处是函数没有名字,不必担心函数名字冲突,缺点是只能有一个表达式。匿名函数也是一个函数对象。
增强函数的功能,本质上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__属性还是原来的属性
偏函数的作用就是固定原函数的一些参数,返回一个新的函数,方便调用,使用functools.partial创建偏函数
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行,为了简化程序,把函数继续分割为子函数,降低系统的复杂度。
而面向对象的程序设计是把计算机程序视为一组对象集合,而每个对象可以接受其他对象发来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
类(Class)是一种抽象的概念,而实例(Instance)是一个具体的对象。面向对象是抽象出Class,根据Class创建Instance
面向对象的抽象程度比函数要高,因为一个类既包含数据,又包含操作数据的方法。
<类的定义> 使用关键字class定义类,一般类名首字母大写, 使用__init__(self, args)方法绑定必要属性,python自动把实例变量传递给self,它是指向实例本身的引用,这样实例就可以访问类中的属性和方法了。也可以使用对象动态绑定属性和方法
<数据封装> 实例本身拥有数据,那么就没必要用外面的函数访问数据,可直接在类的内部定义访问数据的函数,这样就把数据封装起来了,这些封装数据的函数是和类关联起来的,称为方法(Method)
<类属性和类方法> 类属性:类本身绑定的属性,类属性和实例属性相同时类属性会被隐藏,可以使用类名访问类属性,也可以使用对象访问类属性,当对象的属性中没有时会去类属性里去找。类方法:使用装饰器@classmethord定义,只能使用类进行访问。可以通过对象的__class__属性找到其所属的类。
<给类属性指定默认值>
python没有任何机制阻止你干坏事,一切全靠自觉
当我们定义一个类时,可以从现有的类继承,新的类称为子类(subclass),被继承的类称为基类(base class)、父类或超类(super class),继承的好处就是子类自动获得了父类的所有功能,同时子类的对象不仅是子类的类型也是父类的类型。python允许多重继承
子类的__init__(self)方法:首先需要给父类的所有属性赋值,在其形参列表中先包含父类的所有参数,然后使用super().__init__()初始化父类属性,形参包含所有的父类属性,不包含self,也可以使用fatherClass.__init__(self)初始化
子类方法与父类方法重名,则子类覆盖父类
多态的意思就是当我们需要子类类型的对象时,我们只需要接受一个父类的类型就行了,因为子类也是父类的类型,所有调用父类类型对象的,调用子类类型对象也可以正常运行,这就是多态。我们只需要知道它是父类类型,具体是什么子类类型不需要知道也可以调用父类的方法,由运行时该类型的确切类型确定,这就是多态的威力,调用方只管用,不管细节。这就是著名的开闭原则:对扩展开放,允许继承类;对修改封闭,不需要修改依赖父类类型的调用方的函数。
python是动态语言,动态语言的“鸭子类型”特点决定了继承不像静态语言那样是必须的,并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看作鸭子。
<查看对象状态>
<操作对象状态>
查看是否有属性hasattr(obj, attr),动态绑定属性setattr(obj, attr),获取属性getattr(obj, attr)
当我们定义了一个class,定义了一个class的实例之后,可以给该实例绑定任何属性和方法。
如果我们要限制绑定的属性,可以在类的定义中使用__slots__ = (),元组中列出限制属性,这样定义的实例只能添加__slots__限制的属性,添加其他属性会导致AttributeError,仅对当前的实例起作用,对继承的子类不起作用,若子类也定义了__slots__,那么子类允许定义的属性就是自身的__slots__加上父类的__slots__
直接绑定属性没办法检查参数,可以添加方法设置,不过调用方法略复杂,那么可以通过@property把类方法变成属性,这样又可以检查参数,又使用方便
如果只是只读,在函数前加@property,返回函数名前加下划线
如果是读写,再定义另外一个函数前面加@name.setter,函数接受一个value赋值给self._name
@property可以让调用者写出简单的代码,同时保证对参数进行必要的检查
使用枚举类为枚举类型定义一个class类型,每个常量都是class的唯一实例,使用Enum类实现,例如:from enum import Enum,Month = Enum('Month', (...)),也可以从Enum继承,更精确的控制枚举类型,使用装饰器@unique检查保证没有重复值,from enum import unique。可以使用成员名称引用枚举常量Weekday.Mon, Weekday['Mon'],也可以根据value的值获得枚举常量Weekday(1)
try ... except ... [as ...] else ... finally ...,可以有多个except,也可以多个Error写在一起,else表示try正确处理后执行,finally一定会执行
常见的错误类型和继承关系
https://docs.python.org/3/library/exceptions.html#exception-hierarchy
单元测试是对一个模块、一个函数或一个类来进行正确性检验的工作
写在注释中的代码,也可自动执行,这样使用文档工具生成的文档里的代码就可以直接粘贴运行。使用doctest模块doctest.testmod(),doctest严格按照python交互式命令行的输入和输出判断测试结果是否正确,只要测试异常的时候可以用...表示中间的一大段烦人的输出。使用if __name__ == '__main__' 使其只有在使用命令行直接运行时才执行doctest.testmod(),在非测试环境下不执行
<读文件> 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时文件以及关闭了
数据读写不一定是文件,也可以在内存中读写数据流,from io import StringIO, BytesIO
import os
<系统名和环境变量>
<操作文件和目录> 注意操作文件和目录的函数一部分放在os模块中,一部分放在os.path中
把变量从内存中变成可存储或可传输的过程称为序列化,python中叫pickling,在其他语言中也被称之为serialization,marshalling,flattening等等,序列化之后就可以把序列化之后的内容写入磁盘,或通过网络传输到其他机器。反过来,把变量内容从序列化的对象从新读到内存里称为反序列化,即unpickling。python提供了pickle模块实现序列化
只能用于python,不同python版本之间可能都不兼容,因此只能保存那些不重要的数据,不能成功的反序列化也没关系
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可选参数如下
https://docs.python.org/3/library/json.html#json.dumps
也可以偷懒,传入default=lambda obj: obj.__dict__,一般没有定义__slots__的类都有__dict__属性,包含了对象的属性字典
Unix/Linux操作系统提供了fork()系统调用,它非常特殊,调用一次返回两次,操作系统自动把当前进程(父进程)复制一份(子进程),然后分别在父进程和子进程内放回,子进程永远返回0,父进程返回子进程ID。调用os.fork(),os.getpid()返回当前进程,os.getppid(),返回父进程
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失败了
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,子线程的名字在创建时指定,也可以不起名字
获得锁的线程用完后一定要释放锁,否则其他线程将永远等下去,成为死线程。锁的好处是确保某一段关键代码只能由一个线程完整的执行到尾,坏处也很多,首先是阻止了多进程的并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大下降,其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,又不能结束,只能由操作系统强制终止
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锁,互不影响
解决在一个线程中,函数之间传递参数的问题,创建一个全局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()
在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.')
正则表达式是一种用来匹配字符串的强有力的武器,它的设计思想是用一种描述性语言给字符串定义一个规则,凡是符合规则的字符串,我们就认为它匹配了,否则该字符串就是不合法的。
在正则表达式中,如果直接给出字符,就是精确匹配。
要匹配变长的字符,
要做更精确的匹配,可以用[]表示范围,用A|B可以匹配A或B,用^表示行开头,用$表示行的结束
正则匹配默认是贪婪匹配,加?采用非贪婪匹配
编译:在python中使用正则表达式时,re模块内部会干两件事,首先是编译正则表达式,如果正则表达式字符串本身不合法会报错,然后用编译后的正则表达式去匹配字符串。如果一个正则表达式要重复使用几千次,出于效率的考虑,我们可以预编译该表达式使用p = re.compile('正则表达式')返回一个Pattern对象,然后使用p.match('string').group()返回匹配的字符串,编译后生成Regular Expression对象,该对象自己包含了正则表达式,所以调用对应的方法时不用给出正则字符串。
https://docs.python.org/3/library/exceptions.html#exception-hierarchy
python Error 可以使用raise抛出可以接受一个参数(参数检查)
<编写函数>
<编写类>
查看所有内建函数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 |