1:接收输入
var=raw_input()
注意:raw_input()总数接收字符串形式的输入。所以,如果我们想接收其他类型的数据,则需要强制类型转换。(如果转换失败,就会抛出异常)
2:pass
用于编码时占位,相当于空语句块。在没想好怎么编码时可以用来占位,并且不影响编译运行。
3:函数执行完毕后还没遇到return语句,则自动 return None。None是python中的空类型。同Java中的 Null
4:定义默认参数要牢记一点:默认参数必须指向不变对象,即:不可执行引用类型,比如一个list的地址。不然,在第一次调用时外list赋值后,之后每次都会使用上一次使用过的list地址。如果必须使用这种变量,则默认值设为None。
5:可变参数:在参数前加 * 号表示参数是可变的,这样参数就成了一个tuple,在函数中通过迭代访问tuple即可取用传进来的参数。
也可以在调用时把一个list或tuple作为可变参数传进函数中,只需在调用时参数前加 * 即可。
>>> nums = [1, 2, 3]
>>> calc(*nums)
6:关键字参数:键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。关键字参数是在定义方法时,在参数列表最后一个加 **kw 作为标记,如:def person(name, age, **kw)
然后在调用时,可以只传入必选参数,也可以传入任意个数的关键字参数。
关键字参数有什么用?它可以扩展函数的功能。比如,在person
函数里,我们保证能接收到name
和age
这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。
和可变参数类似,也可以先组装出一个dict,然后,把该dict转换为关键字参数传进去。
7:参数组合
在Python中定义函数,可以用必选参数、默认参数、可变参数和关键字参数,这4种参数都可以一起使用,或者只用其中某些,但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数和关键字参数。
比如定义一个函数,包含上述4种参数:
def func(a, b, c=0, *args, **kw):
print 'a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw
>>> func(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> func(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> func(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> func(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
最神奇的是通过一个tuple和dict,你也可以调用该函数:
>>> args = (1, 2, 3, 4)
>>> kw = {'x': 99}
>>> func(*args, **kw)//把一个tuple和一个dict前面加 * ** 作为可变、关键字参数
a = 1 b = 2 c = 3 args = (4,) kw = {'x': 99}
要注意定义可变参数和关键字参数的语法:
*args
是可变参数,args接收的是一个tuple;
**kw
是关键字参数,kw接收的是一个dict。
以及调用函数时如何传入可变参数和关键字参数的语法:
可变参数既可以直接传入:func(1, 2, 3)
,又可以先组装list或tuple,再通过*args
传入:func(*(1, 2, 3))
;
关键字参数既可以直接传入:func(a=1, b=2)
,又可以先组装dict,再通过**kw
传入:func(**{'a': 1, 'b': 2})
8:递归函数
python函数栈递归深度最大为1000,到达1000就会报错。
高级特性
1:切片:[0:n]
表示,从索引0开始取,直到索引n为止,但不包括索引n。倒序切片的话,倒数第一个元素的索引是-1,第一个元素是-n。切片可以用于list和tuple甚至字符串,只需在后面用[n:m]作切片运算即可。
2:迭代:在Python中,迭代是通过for ... in
来完成的,而很多语言比如C或者Java,迭代list是通过下标完成的。Python的for
循环抽象程度要高于Java的for
循环,因为Python的for
循环不仅可以用在list或tuple上,还可以作用在其他可迭代对象(数组、字符串、dict、生成器等)上。
默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.itervalues()
,如果要同时迭代key和value,可以用for k, v in d.iteritems()
。
如果要对list实现类似Java那样的下标循环,Python内置的enumerate
函数可以把一个list变成索引-元素对,这样就可以在for
循环中同时迭代索引和元素本身:
for i, value in enumerate(['A', 'B', 'C']):
... print i, value
在python的迭代中,可以同时引用多个变量,只有迭代对象的每个元素有对应的值即可。如上。
3:列表生成器:通过 [元素的生成形式 用于生成元素的值 条件表达式 ] 这种形式可以生成自定义条件的list,功能十分强大。还可以用双重循环、多个变量来生成list元素。
如:[x * x for x in range(1, 11) if x % 2 == 0]
4:生成器generator:如果列表元素可以按照某种算法推算出来,我们可以一边循环一边计算出列表元素而不必事先创建好list,这种机制称为生成器(Generator)。
创建生成器有两种方法:
方法一:把一个列表生成式的[]
改成()
,就创建了一个generator。如: g = (x * x for x in range(10))
方法二:复杂的推算,可以先写成函数,然后在函数中使用while循环写出list元素的推算方法,在每一次循环中使用yield val “输出”当前次的计算结果,即可把函数改造成生成器。生成器函数与一般函数不同,它执行到yield语句就会返回结果,然后下一次执行时会接着上一次执行到的yield语句继续执行。
使用生成器:
生成器可以使用next()取用下一个list元素,但更多的是使用for..in迭代访问。
函数式编程
1:函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数。
2:函数也是变量,变量可以指向函数。
3:高级函数:可以接收另一个函数作为参数,这种函数就称之为高阶函数。
4:强大的map()/reduce()函数:
map(f,list):map函数把接收两个参数,一个是函数f,一个待处理序列list,map函数的作用是用函数f依次处理list的元素,并返回一个新的list作为结果。
reduce(f,list):reduce函数接收一个函数参数f和一个待处理序列list,其中,参数f是一个接收两个参数的函数f(x,y)。reduce函数的作用就是,用函数f(x,y)依次处理list的元素并且前一次处理结果与list下一元素作为f(x,y)的参数继续传入。也就是f(x,y)累积处理list。
灵活制定参数函数f,结合使用map()/reduce()可以把复杂的循环逻辑简单实现(把循环体中的操作抽取到函数f中去)。如:把一个数字字符串变成等值整数
def str2int(s):
def fn(x, y):
return x * 10 + y
def char2num(s):
return {'': 0, '': 1, '': 2, '': 3, '': 4, '': 5, '': 6, '': 7, '': 8, '': 9}[s]
return reduce(fn, map(char2num, s))
5:lambda表达式(匿名函数):lambda 参数:对参数的操作表达式 称为匿名函数,它是一种简化的函数书写格式。冒号后面既是对参数的操作,又函数的返回值。匿名函数有个限制,就是只能有一个表达式,不用写return
,返回值就是该表达式的结果。
6:filter(f,list)函数:filter(f,list)
把传入的函数f依次作用于list每个元素,然后根据f的返回值是True
还是False
决定保留还是丢弃该元素。用于数据筛选。
7:sorted(list,cmp)函数:把自定义的比较函数cmp作用于list,把list元素排序。
cmp函数接收两个参数,对于两个元素x
和y
,如果认为x < y
,则返回-1
,如果认为x == y
,则返回0
,如果认为x > y
,则返回1。在cmp函数中就可以自定义比较规则了。
8:偏函数:当函数的参数个数太多,需要简化时,使用functools.partial
可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。
如:原函数func(arg,arg1=argval...),参数比较多并且后面需要设定默认值的多。
这个时候,使用 new_fun=functools.partial(func,arg1=argval...)把func包装成偏函数。这样在代码中调用func时,直接使用 new_fun(变化的参数值) 即可,它会自动传入变化的参数值以及前面设定的默认值给func函数执行,并返回结果。
9:返回函数:前面我们说过,函数也是一个变量,所以我们可以把一个函数作为返回值。
在高级函数里面,定义一个内部函数,内部函数进行一些操作、运算。然后高级函数把这个内部函数返回而不是把运算结果返回。
在代码中调用高阶函数时,获得的返回值不是一个运算结果,而是一个封装了运算过程与运算结果的函数变量f。只有在需要取得运算结果时,用 () 激活这个函数f即可,即f()。
这种使用方法类似于Hibernate中的懒加载。
(并且,每一次调用高阶函数,返回值都是一个新的函数变量)
10:闭包问题:在上面说到高阶函数中返回一个内部函数,内部函数使用高级函数接收到参数值作计算。这种把相关参数和变量以及运算过程、结果都保存在返回的函数中的特性叫“闭包”。闭包很强大,但是也存在一个问题:内部函数如果引用了外部函数中定义的局部数据,那么返回后的函数变量仍然是引用该局部数据的,所以,在激活返回函数之前,如果被引用的局部数据变化了,就会引起返回函数值的变化。【类比:类中的静态变量被类的所有实例共享,静态变量改变引起所以类实例中相关内容改变】
解决办法:把引用的外部函数局部数据作为内部函数的参数值传入,而不是直接在内部函数中使用外部函数的局部数据。这样,相当于拷贝了一份数据进内部函数,使内部函数与外部函数脱耦。
11:高阶函数的应用之一:装饰器Decorator。
装饰器实现了“装饰模式”的思想。其定义原理是:定义一个高阶函数,传入一个原函数作为其参数;在高级函数内部定义一个装饰函数,装饰函数中进行一些包装操作,如:打印日志、权限校验等,然后再调用传进来的原函数;最后高阶函数返回装饰函数。
使用装饰器:通过注解 @装饰器名 加到被装饰的原函数前面,则原函数名指向了装饰过后的函数。
这样,我们在使用原函数名调用原函数时,其实是调用装饰函数。
如果装饰器需要接收参数,则需要定义一个三层的高阶函数——最外层函数接收参数,第二层是一个装饰器,第三层才是装饰函数。如:
def log(text)://外层函数接收参数
def decorator(func)://装饰器,接收原函数
def wrapper(*args, **kw)://装饰函数,使用组合参数
print '%s %s():' % (text, func.__name__)//装饰
return func(*args, **kw)//调用原函数,使用组合参数
return wrapper//返回装饰函数
return decorator//返回装饰器
使用时,只需在原函数前面,加 @装饰器(参数值) 即可。
@log('execute')
def now():
print '2013-12-25'
最后,还要把原函数一些自带属性,赋值给装饰函数,否则,装饰函数是一个新的函数,其name属性等与原函数就不相同了。
使用在装饰函数前,加 @functools.wraps(func) 把func的属性赋值给装饰函数。
一个完整的装饰器定义如下:
import functools def log(text)://接收参数
def decorator(func)://装饰器
@functools.wraps(func)//复制原函数的属性们
def wrapper(*args, **kw)://装饰函数
print '%s %s():' % (text, func.__name__)//装饰
return func(*args, **kw)//调用原函数并把结果返回
return wrapper//返回装饰函数
return decorator//返回装饰器
模块
1:在Python中,一个.py文件就称之为一个模块(Module)。使用模块还可以避免函数名和变量名冲突。为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package)。每一个包目录下面都会有一个__init__.py
的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。__init__.py
可以是空文件。
2:安装模块:使用 pip install 模块名 指令
3:使用模块
import 模块名
import 模块名 as 别名
from 模块名 import 变量名
#import与from...import的不同之处在于:
# 如果你想在程序中用argv代表sys.argv,则可使用:from sys import argv。
#如果使用import 模块名 来导入模块,则使用模块中内容时,需要指明是哪个模块的,避免名次冲突。如: mod1.argv
# 一般说来,应该避免使用from..import而使用import语句,因为这样可以使你的程序更加易读,也可以避免名称的冲突
面向对象编程
1:定义类
class 类名(继承列表)://如果没有明确继承的,则继承object
pass
2:使用类创建实例
var=类名()
3:类中定义方法:
def funcName(self,...参数列表)://第一个参数必须是self
pass
4:数据封装
Python中的静态变量与成员变量是依靠其在类中定义的位置来决定的。
成员变量,每个实例不同值——在类初始化方法的参数中定义
__init__(成员变量名):
self.name=val//创建对象时,为对象的成员属性赋值
静态变量,同类的所有对象共享——在类的方法外定义
class class_name(object):
class_var = 'I am a class variable' #类变量
局部变量——在方法内定义
def instance_method(self, formal_parameter):
local_var_in_function = formal_parameter #实例方法局部变量
5:继承与多态
继承:OOP中的继承,没啥好说的。
多态:子类重写父类的同名方法、子类实例对象既是子类类型又是父类类型、向上转型、
6:类型判断
基本数据类型判断:type(val)函数
类对象的类型判断:isinstance(obj,className)
面向对象高级编程
1:动态语言的特性之——创建了一个class的实例后,我们可以给该实例绑定任何属性和方法。
如:创建一个对象后,可以用 obj.attr=val 的形式为该对象添加任何属性。
又如:定义一个方法 def func() ,然后添加到对象中 obj.func=MethodType(func,obj,参数)
2:上面为对象动态添加的方法、属性,只会在该对象起作用。如果需要对整个类的所有对象都起作用,可以给class添加属性、方法:
如:Student.func = MethodType(func, None, 参数)
3:限制类对象,禁止动态添加属性:使用__slots__
class className(object):
__slots__=('允许的属性名')
这样,该类对象就只能拥有上面限定的属性了。若在子类中也定义__slots__
,这样,子类允许定义的属性就是自身的__slots__
加上父类的__slots__
。
4:约定的权限控制:Python中没有权限控制关键字,而是采用变量名前添加下划线的方式去提醒使用者,这是一个私有变量。
辨析:
_name:这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
__name:实例的变量名如果以双下划线开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。
__name__:以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__
、__score__
这样作变量名。
5:python中的getter与setter
对于类中的属性,一般我们不会不提倡直接用 实例对象.属性 去访问,也不提倡 实例对象.属性=val 去赋值。因为这样会暴露类中的信息,而且对所赋的值也不能作很好的严格审查。在Java中,我们会把类的成员属性设为private,然后提供public的setter和getter方法操作属性。同时,python中如果通过双下划线开头定义了变量的话,类外如何操作这些属性呢?
python提供了 @property 注解,把一个方法变成属性。我们通过这个注解,定义一个与属性同名的方法,方法内部return self.attr 即可访问到同名的属性了。并且,@property注解把该方法变成了类的属性。那么在类外,通过 对象.attrName 即可自动调用该方法,获取类中的同名属性值了。——这就是python中的getter。
此外,@property
本身又创建了另一个装饰器@attrName.setter
,负责把一个setter方法变成属性。setter方法也是用属性名作为方法名的,不过参数列表为(self,value),方法中可以对传进来的value进行合法性判断,然后再赋值给self.attrName。同理, 对象.attrName=value 赋值语句会自动转换为该setter方法。
最后,来看一个完整的例子:
class Student(object):
....前面省略,init方法中定义了一个score成员属性
@property
def score(self)://同名的方法
return self._score @score.setter //注解为 属性名.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value //赋值合法
>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
6:多重继承
如果定义一个子类需要综合多种类的特性时,可以用多重继承。这种设计通常称之为Mixin。
Mixin的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个Mixin的功能,而不是设计多层次的复杂的继承关系。
7:自定义类属性
形如__xxx__
的变量或者函数名在Python中是有特殊用途的,我们可以在自定义的类中重写这些定制方法,来定制我们的类。
如:
__str__(self)
方法:定义该类以字符串形式打印的格式与内容
__iter__(self):把一个类定义为可迭代对象。
__getitem__(self,n)
方法:像list那样按照下标取出元素。
__getattr__(self,attr)
方法:动态返回一个属性。如果用 实例.attr 访问属性找不到的话,就会自动调用这个方法,把attr传进来。在方法中可以对attr作判断,根据不同值返回不同内容。如果实在没有,会返回None。当然,我们也可以手动抛出异常。【用途:RestAPI的链式调用实现】
__call__(self):对类对象自身的特殊属性进行访问调用。
8:元类
我们说,实例是由类创建出来的。那么,类又是谁创建的呢?是由元类创建的。
连接起来就是:先定义metaclass,就可以创建类,最后创建实例。metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。
对于类的动态修改,一般只会在比较复杂、高深的地方用到。比如Python的编写ORM框架。
异常处理
见另一篇专题博文。
调试
使用logging来调试程序,可以输出不同级别的信息。
import logging #导入模块
logging.basicConfig(level=logging.INFO) //设置级别 logging.info(。。。)//在需要调试处打印输出
IO编程
操作文件与目录:使用os模块来操作系统中的文件与目录。
os模块的常用功能:
1 os.name #显示当前使用的平台
2 os.getcwd() #显示当前python脚本工作路径
3 os.listdir('dirname') #返回指定目录下的所有文件和目录名
4 os.remove('filename') #删除一个文件
5 os.makedirs('dirname/dirname') #可生成多层递规目录
6 os.rmdir('dirname') #删除单级目录
7 os.rename("oldname","newname") #重命名文件
8 os.system() #运行shell命令,注意:这里是打开一个新的shell,运行命令,当命令结束后,关闭shell
9 os.sep #显示当前平台下路径分隔符
10 os.linesep #给出当前平台使用的行终止符
11 os.environ #获取系统环境变量
12 os.path.abspath(path) #显示当前绝对路径
13 os.path.dirname(path) #返回该路径的父目录
14 os.path.basename(path) #返回该路径的最后一个目录或者文件,如果path以/或\结尾,那么就会返回空值。
15 os.path.isfile(path) #如果path是一个文件,则返回True
16 os.path.isdir(path) #如果path是一个目录,则返回True
17 os.stat() #获取文件或者目录信息
18 os.path.split(path) #将path分割成路径名和文件名。(事实上,如果你完全使用目录,它也会将最后一个目录作为文件名而分离,同时它不会判断文件或目录是否存在)
19 os.path.join(path,name) #连接目录与文件名或目录 结果为path/name
序列化:pickling
序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。
反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。
Python提供两个模块来实现序列化:cPickle
和pickle
。这两个模块功能是一样的,区别在于cPickle
是C语言写的,速度快,pickle
是纯Python写的,速度慢,跟cStringIO
和StringIO
一个道理。用的时候,先尝试导入cPickle
,如果失败,再导入pickle:
try:
import cPickle as pickle
except ImportError:
import pickle
pickle.dumps()
方法把任意对象序列化成一个str,然后,就可以把这个str写入文件
pickle.dumps(d):把d对象序列化(有S!)
pickle.dump(d, file):把d对象序列化后写入文件file
pickle.loads()/load()
方法反序列化出对象:d = pickle.loads(str),d = pickle.load(file)
如果我们要在不同的编程语言之间传递对象,就必须把对象序列化为标准格式,比如JSON。
Python内置的json
模块提供了非常完善的Python对象到JSON格式的转换。
先看看如何把Python对象变成一个JSON:
>>> import json
>>> d = dict(name='Bob', age=20, score=88)
>>> json.dumps(d)
'{"age": 20, "score": 88, "name": "Bob"}'
或json.dump(d,file)把d对象序列化为json字符串后写入file文件。
要把JSON反序列化为Python对象,用loads()
或者对应的load()
方法,前者把JSON的字符串反序列化,后者从file-like Object
中读取字符串并反序列化。
注意:要把自定义的类序列化,该类定义时,必须定义转换函数。一般采取把对象转换成dict,再由dict直接序列化得到json。反序列化同理,json先转成dict,再有转换函数把dict转换成对象。
def student2dict(std)://序列化转换函数
return {
'name': std.name,
'age': std.age,
'score': std.score
} def dict2student(d)://反序列化转换函数
return Student(d['name'], d['age'], d['score'])
多进程
Python是跨平台的,自然也应该提供一个跨平台的多进程支持。multiprocessing
模块就是跨平台版本的多进程模块,提供了一个Process
类来代表一个进程对象。
简单使用:
from multiprocessing import Process #导入模块
import os # 子进程要执行的代码
def run_proc(name):
print 'Run child process %s (%s)...' % (name, os.getpid()) if __name__=='__main__':
print 'Parent process %s.' % os.getpid()
p = Process(target=run_proc, args=('test',)) #创建一个进程对象,传递子进程要执行的任务函数,以及函数所需参数列表
print 'Process will start.'
p.start() #启动子进程
p.join() #join()
方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
print 'Process end.'
多进程使用:使用进程池
from multiprocessing import Pool
import os, time, random def long_time_task(name): #子进程要执行的任务函数
print 'Run task %s (%s)...' % (name, os.getpid())
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print 'Task %s runs %0.2f seconds.' % (name, (end - start)) if __name__=='__main__':
print 'Parent process %s.' % os.getpid()
p = Pool() #创建一个进程池,可以一个整数参数,指定进程池的大小
for i in range(5):
p.apply_async(long_time_task, args=(i,)) #为进程池中进程分配任务
print 'Waiting for all subprocesses done...'
p.close() #关闭进程池,使进程池的中数量不再改变
p.join() #启动进程池中进程
print 'All subprocesses done.'
进程间通信:
Python的multiprocessing
模块包装了底层的机制,提供了Queue
、Pipes
等多种方式来交换数据。
from multiprocessing import Process, Queue
import os, time, random # 写数据进程执行的代码:
def write(q):
for value in ['A', 'B', 'C']:
print 'Put %s to queue...' % value
q.put(value)
time.sleep(random.random()) # 读数据进程执行的代码:
def read(q):
while True:
value = q.get(True)
print 'Get %s from queue.' % value if __name__=='__main__':
q = Queue() #父进程创建一个queue,用于各个子进程数据通信
pw = Process(target=write, args=(q,)) #各个子进程创建时,传入queue给子进程
pr = Process(target=read, args=(q,))
# 启动子进程pw,写入:
pw.start()
# 启动子进程pr,读取:
pr.start()
# 等待pw结束:
pw.join()
# pr进程里是死循环,无法等待其结束,只能强行终止:
pr.terminate()
多线程
Python的标准库提供了两个模块:thread
和threading
,thread
是低级模块,threading
是高级模块,对thread
进行了封装。绝大多数情况下,我们只需要使用threading
这个高级模块。启动一个线程就是把一个函数传入并创建Thread
实例,然后调用start()
开始执行。
import time, threading # 新线程执行的代码
def loop():
print 'thread %s is running...' % threading.current_thread().name
n = 0
while n < 5:
n = n + 1
print 'thread %s >>> %s' % (threading.current_thread().name, n)
time.sleep(1)
print 'thread %s ended.' % threading.current_thread().name t = threading.Thread(target=loop, name='LoopThread')#创建一个新线程,把线程任务作为参数传进来
t.start()
t.join()
current_thread()
函数,它永远返回当前线程的实例。
并发控制:创建一个锁,threading.Lock()
lock = threading.Lock()
def run_thread(n):
for i in range(100000):
# 先要获取锁:
lock.acquire()
try:
# 执行并发操作
change_it(n)
finally:
# 改完了一定要释放锁:
lock.release()
ThreadLocal:在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的修改必须加锁。
import threading # 创建全局ThreadLocal对象
local_school = threading.local() def process_thread(name):
# 绑定student到threadLocal
local_school.student = name
process_student() def process_student():
print 'Hello, %s (in %s)' % (local_school.student, threading.current_thread().name) 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()
全局变量local_school
就是一个ThreadLocal
对象,每个Thread
对它都可以读写student
属性,但互不影响。你可以把local_school
看成全局变量,但每个属性如local_school.student
都是线程的局部变量,可以任意读写而互不干扰,也不用管理锁的问题,ThreadLocal
内部会处理。
ThreadLocal
最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。
多进程VS多线程
要实现多任务,通常我们会设计Master-Worker模式,Master负责分配任务,Worker负责执行任务,因此,多任务环境下,通常是一个Master,多个Worker。
多进程模式最大的优点就是稳定性高,因为一个子进程崩溃了,不会影响主进程和其他子进程。(当然主进程挂了所有进程就全挂了,但是Master进程只负责分配任务,挂掉的概率低)著名的Apache最早就是采用多进程模式。
多进程模式的缺点是创建进程的代价大,在Unix/Linux系统下,用fork
调用还行,在Windows下创建进程开销巨大。另外,操作系统能同时运行的进程数也是有限的,在内存和CPU的限制下,如果有几千个进程同时运行,操作系统连调度都会成问题。
多线程模式通常比多进程快一点,但是也快不到哪去,而且,多线程模式致命的缺点就是任何一个线程挂掉都可能直接造成整个进程崩溃,因为所有线程共享进程的内存。
协程
子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。
所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。
协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。
注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断。
看起来协程的执行有点像多线程(任务交替),但协程的特点在于是一个线程执行。
最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
第三方的gevent为Python提供了比较完善的协程支持,
其基本思想是:
当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。
异步IO(python3之后支持)
1:asyncio
asyncio
的编程模型就是一个消息循环。我们从asyncio
模块中直接获取一个EventLoop
的引用,然后把需要执行的协程扔到EventLoop
中执行,就实现了异步IO。
import asyncio @asyncio.coroutine
def hello():
print("Hello world!")
# 异步调用asyncio.sleep(1):
r = yield from asyncio.sleep(1)
print("Hello again!") # 获取EventLoop:
loop = asyncio.get_event_loop()
# 执行coroutine
loop.run_until_complete(hello())
loop.close()
@asyncio.coroutine
把一个generator标记为coroutine类型,然后,我们就把这个coroutine
扔到EventLoop
中执行。
为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法async
和await
,可以让coroutine的代码更简洁易读。
请注意,async
和await
是针对coroutine的新语法,要使用新的语法,只需要做两步简单的替换:
- 把
@asyncio.coroutine
替换为async
; - 把
yield from
替换为await
。
async def hello():
print("Hello world!")
r = await asyncio.sleep(1)
print("Hello again!")
aiohttp
asyncio
实现了TCP、UDP、SSL等协议,aiohttp
则是基于asyncio
实现的HTTP框架。