有的变量都可以理解是内存中一个对象的**“引用”,或者,也可以看似c中void***的感觉。
类型是属于***对象***的,而不是***变量***。
对象有两种,“可更改”(mutable)与“不可更改”(immutable)对象。
在python中,strings, tuples, 和numbers是不可更改的对象,
list, dict, set 等则是可以修改的对象。
引用传递和值传递(函数传值)
说明:可变对象为引用传递,不可变对象为值传递
引用传递:传递列表或者字典时,如果改变引用的值,就修改了原始的对象
通过id
来看引用a
的内存地址可以比较理解:
a = 1
def fun(a):
print "func_in",id(a) # func_in 41322472
a = 2
print "re-point",id(a), id(2) # re-point 41322448 41322448
print "func_out",id(a), id(1) # func_out 41322472 41322472
fun(a)
print a # 1
注:具体的值在不同电脑上运行时可能不同。
可以看到,在执行完a = 2
之后,a
引用中保存的值,即内存地址发生变化,由原来1
对象的所在的地址变成了2
这个实体对象的内存地址。
而第2个例子a
引用保存的内存值就不会发生变化:
a = []
def fun(a):
print "func_in",id(a) # func_in 53629256
a.append(1)
print "func_out",id(a) # func_out 53629256
fun(a)
print a # [1]
如果还不明白的话,这里有更好的解释: http://stackoverflow.com/questions/986006/how-do-i-pass-a-variable-by-reference
Python虽然是多范式的编程语言,但它的数据模型却是 纯面向对象 的。与那些仅在语法层面声称纯OO的编程语言(如Java)相比,Python的这种纯粹性更加深入骨髓。
在Python的世界里,一切皆为对象:数值、序列、字典、函数、模块、文件、类、类实例 等等,无一例外(参考 Data model)。其中,“类也是对象” 的概念最让人匪夷所思,这完全超越了传统的OO思想。
元类(metaclass)是Python 2.2中引入的概念,利用元类可以 定制类的创建行为(Customizing class creation)。“元类” 的概念同样让人难以理解,然而理解 “元类” 是理解 “类也是对象” 的关键。
对于类定义:
class Foo(Base):
def say(self):
print 'hello'
Python解释器 执行class语句 时,处理步骤如下:
确定元类mcls
。元类的查找优先级为:
__metaclass__
__metaclass__
__metaclass__
types.ClassType
;新式类:type
)使用元类mcls
创建类Foo
。创建语意等价于:
def say(self):
print 'hello'
# 元类的参数:mcls(name, bases, dict)
Foo = mcls('Foo', (Base,), {'say': say})
3. 创建成功后,类Foo
是 元类mcls
的 实例。
综上:创建类 其实是一种更高级别的 实例化过程,本质上与 创建类实例 相似。
实例化过程 | 类 | 实例 | 语意形式 |
---|---|---|---|
创建类Foo | 元类mcls | 类Foo | class Foo: pass <=> Foo = mcls(‘Foo’, (), {}) |
创建类实例foo | 类Foo | 类实例foo | foo = Foo() |
原则上,元类可以是:任何接受参数 name, bases, dict 并返回 类 的 可调用对象(参考 metaclass)。
def metacls_func(name, bases, dict):
# do customizing here
return type(name, bases, dict
根据最佳实践指导,更好的习惯是使用 类 作为元类,典型风格如下:
class MetaCls(type):
def __new__(cls, name, bases, dict):
# do customizing here
return super(MetaCls, cls).__new__(cls, name, bases, dict)
注意:
__new__
,还可以借助__init__
和__call__
来定制被创建的类Python其实有3个方法
def foo(x):
print "executing foo(%s)"%(x)
class A(object):
def foo(self,x):
print "executing foo(%s,%s)"%(self,x)
@classmethod
def class_foo(cls,x):
print "executing class_foo(%s,%s)"%(cls,x)
@staticmethod
def static_foo(x):
print "executing static_foo(%s)"%x
a=A()
foo(x)
,这个函数就是最常用的。它的工作跟类、实例无关。foo(self, x)
。实例方法的调用离不开实例,我们需要把实例自己传给函数,调用的时候是:a.foo(x)
(其实是foo(a, x)
)。类方法一样。只不过它传递的是类而不是实例,A.class_foo(x)
。
对于静态方法其实和普通的方法一样,不需要对谁进行绑定,唯一的区别是调用的时候需要使用a.static_foo(x)
或者A.static_foo(x)
来调用.
\ | 实例方法 | 类方法 | 静态方法 |
---|---|---|---|
a = A() | a.foo(x) | a.class_foo(x) | a.static_foo(x) |
A | 不可用 | A.class_foo(x) | A.static_foo(x) |
更多关于这个问题:
类变量:
是可在类的所有实例之间共享的值(也就是说,它们不是单独分配给每个实例的)。例如下例中, num_of_instance 就是类变量,用于跟踪存在着多少个Test 的实例。
实例变量:
实例化之后,每个实例单独拥有的变量。
参考:http://stackoverflow.com/questions/6470428/catch-multiple-exceptions-in-one-line-except-block
Python自省-------在运行时能够获得对象的类型
方法 | 作用 |
---|---|
help() | 查看函数或模块用途的详细说明 |
dir() | 返回对象所有属性 |
type() | 查看对象类型 |
hasattr() | 查看对象是否有特定属性 |
getattr() | 得到对象的特定属性 |
seetattr() | 设置对象的特定属性 |
isinstance() | 判断一个对象是否是一个已知的类型 |
issubclass() | 判断一个类是不是另一个类的子类 |
id() | 返回地址值 |
callable() | 判断对象是否可调用 |
a = [1,2,3]
b = {'a':1,'b':2,'c':3}
c = True
print type(a),type(b),type(c) #
print isinstance(a,list) # True
d = {key: value for (key, value) in iterable}
列表推导式可以利用 range 区间、元组、列表、字典和集合等数据类型,快速生成一个满足指定需求的列表。
列表推导式的语法格式如下:
[表达式 for 迭代变量 in 可迭代对象 [if 条件表达式] ]
此格式中,[if 条件表达式] 不是必须的,可以使用,也可以省略。
列表推导式的语法格式,除去 [if 条件表达式] 部分,其余各部分的***含义以及执行顺序***和 for 循环是完全一样的(表达式其实就是 for 循环中的循环体),即它的执行顺序如下所示:
for 迭代变量 in 可迭代对象
表达式
这样认为,它只是对 for 循环语句的格式做了一下简单的变形,并用 [] 括起来而已,只不过最大的不同之处在于,列表推导式最终会将循环过程中,计算表达式得到的一系列值组成一个列表。
例如如下代码(程序一):
a_range = range(10)
# 对a_range执行for表达式
a_list = [x * x for x in a_range]
# a_list集合包含10个元素
print(a_list)
# 运行上面代码,可以看到如下输出结果:
[0 , 1 , 4 , 9 , 16 , 25 , 36 , 49 , 64, 81]
我们还可以在列表推导式中添加 if 条件语句,这样列表推导式将只迭代那些符合条件的元素。例如如下代码:
b_list = [x * x for x in a_range if x % 2 == 0]
# a_list集合包含5个元素
print(b_list)
# 运行上面代码,可以看到如下输出结果:
[0 ,4 , 16, 36, 64]
另外,以上所看到的列表推导式都只有一个循环,实际上它可使用多个循环,就像嵌套循环一样。例如如下代码:
d_list = [(x, y) for x in range(5) for y in range(4)]
# d_list列表包含20个元素
print(d_list)
#运行上面代码,可以看到如下输出结果:
[(0, 0), (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (1, 3), (2, 0), (2, 1), (2, 2), (2, 3), (3, 0), (3, 1), (3, 2), (3, 3), (4, 0), (4, 1), (4, 2), (4, 3)]
上面代码中,x 是遍历 range(5) 的迭代变量(计数器),因此该 x 可迭代 5 次;y 是遍历 range(4) 的计数器,因此该 y 可迭代 4 次。因此,该(x,y)表达式一共会迭代 20 次。上面的 for 表达式相当于如下嵌套循环:
dd_list = []
for x in range(5):
for y in range(4):
dd_list.append((x, y))
当然,也支持类似于三层嵌套的 for 表达式,例如如下代码:
e_list = [[x, y, z] for x in range(5) for y in range(4) for z in range(6)]
# e_list列表包含120个元素
print(e_list)
对于包含多个循环的 for 表达式,同样可指定 if 条件。假如我们有一个需求:程序要将两个列表中的数值按“能否整除”的关系配对在一起。比如 src_a 列表中包含 30,src_b 列表中包含 5,其中 30 可以整除 5,那么就将 30 和 5 配对在一起。对于上面的需求使用 for 表达式来实现非常简单,例如如下代码:
src_a = [30, 12, 66, 34, 39, 78, 36, 57, 121]
src_b = [3, 5, 7, 11]
# 只要y能整除x,就将它们配对在一起
result = [(x, y) for x in src_b for y in src_a if y % x == 0]
print(result)
运行上面代码,可以看到如下输出结果:
[(3, 30), (3, 12), (3, 66), (3, 39), (3, 78), (3, 36), (3, 57), (5, 30), (11, 66), (11, 121)]
元组推导式可以利用 range 区间、元组、列表、字典和集合等数据类型,快速生成一个满足指定需求的元组。
元组推导式的语法格式如下:
(表达式 for 迭代变量 in 可迭代对象 [if 条件表达式] )
其中,用 [] 括起来的部分,可以使用,也可以省略。
例如,我们可以使用下面的代码生成一个包含数字 1~9 的元组:
a = (x for x in range(1,10))
print(a)
运行结果为:
<generator object <genexpr> at 0x0000020BAD136620>
从上面的执行结果可以看出,使用元组推导式生成的结果并不是一个***元组***,而是一个***生成器对象***(后续会介绍),这一点和列表推导式是不同的。
如果我们想要使用元组推导式获得新元组或新元组中的元素,有以下三种方式:
使用 tuple() 函数,可以直接将生成器对象转换成元组,例如:
a = (x for x in range(1,10))
print(tuple(a))
运行结果为:
(1, 2, 3, 4, 5, 6, 7, 8, 9)
直接使用 for 循环遍历生成器对象,可以获得各个元素,例如:
a = (x for x in range(1,10))
for i in a:
print(i,end=' ')
print(tuple(a))
# 运行结果为:
1 2 3 4 5 6 7 8 9 ()
使用 ***next() 方法***遍历生成器对象,也可以获得各个元素,例如:
a = (x for x in range(3))
print(a.__next__())
print(a.__next__())
print(a.__next__())
a = tuple(a)
print("转换后的元组:",a)
运行结果为:
0
1
2
转换后的元组: ()
注意,无论是使用 for 循环遍历生成器对象,还是使用 next() 方法遍历生成器对象,遍历后***原生成器对象将不复存在***,这就是遍历后转换原生成器对象却得到空元组的原因。
Python 中,使用字典推导式可以借助列表、元组、字典、集合以及 range 区间,快速生成符合需求的字典。
字典推导式的语法格式如下:
{表达式 for 迭代变量 in 可迭代对象 [if 条件表达式]}
其中,用 [] 括起来的部分,可以使用,也可以省略。
可以看到,和其它推导式的语法格式相比,唯一不同在于,字典推导式用的是大括号{}。
【例 1】
listdemo = ['C语言中文网','c.biancheng.net']
#将列表中各字符串值为键,各字符串的长度为值,组成键值对
newdict = {key:len(key) for key in listdemo}
print(newdict)
# 运行结果为:
{'C语言中文网': 6, 'c.biancheng.net': 15}
【例 2】交换现有字典中各键值对的键和值。
olddict={'C语言中文网': 6, 'c.biancheng.net': 15}
newdict = {v: k for k, v in olddict.items()}
print(newdict)
# 运行结果为:
{6: 'C语言中文网', 15: 'c.biancheng.net'}
【例 3】使用 if 表达式筛选符合条件的键值对。
olddict={'C语言中文网': 6, 'c.biancheng.net': 15}
newdict = {v: k for k, v in olddict.items() if v>10}
print(newdict)
# 运行结果为:
{15: 'c.biancheng.net'}
Python中,使用集合推导式可以借助列表、元组、字典、集合以及 range 区间,快速生成符合需求的集合。
集合推导式的语法格式和字典推导式完全相同,如下所示:
{ 表达式 for 迭代变量 in 可迭代对象 [if 条件表达式] }
其中,用 [] 括起来的部分,可以使用,也可以省略。
【例 1】
setnew = {i**2 for i in range(3)}
print(setnew)
运行结果为:
{0, 1, 4}
【例 2】既然生成的是集合,那么其保存的元素必须是唯一的。
tupledemo = (1,1,2,3,4,5,6,6)
setnew = {x**2 for x in tupledemo if x%2==0}
print(setnew)
运行结果为:
{16, 4, 36}
【例 3】
dictdemo = {'1':1,'2':2,'3':3}
setnew = {x for x in dictdemo.keys()}
print(setnew)
运行结果为:
{'2', '1', '3'}
>>> class MyClass():
... def __init__(self):
... self.__superprivate = "Hello"
... self._semiprivate = ", world!"
...
>>> mc = MyClass()
>>> print mc.__superprivate
Traceback (most recent call last):
File "" , line 1, in <module>
AttributeError: myClass instance has no attribute '__superprivate'
>>> print mc._semiprivate
, world!
>>> print mc.__dict__
{'_MyClass__superprivate': 'Hello', '_semiprivate': ', world!'}
__foo__
:一种约定,Python内部的名字,用来区别其他用户自定义的命名,以防冲突,就是例如__init__()
,__del__()
,__call__()
这些特殊方法
_foo
:一种约定,用来***指定变量私有***。程序员用来指定私有变量的一种方式。不能用from module import * 导入,其他方面和公有一样访问;
__foo
:这个有真正的意义:解析器用***_classname__foo
***来代替这个名字,以区别和其他类相同的命名,它无法直接像公有成员一样随便访问,通过对象名._类名__xxx这样的方式可以访问。
foo_
:一个变量的***最合适的名称***已经被一个***关键字***所占用。 因此,像class或def这样的名称不能用作Python中的变量名称。 在这种情况下,你可以附加一个下划线来解决命名冲突。
单下划线 _:有时候单个独立下划线是用作一个名字,来表示某个变量是临时的或无关紧要的。
例如,在下面的循环中,我们不需要访问正在运行的索引,我们可以使用"_"来表示它只是一个临时值:
>>> for _ in range(32):
... print('Hello, World.')
在***拆分(unpacking)表达式***中将单个下划线用作"不关心的"变量,以忽略特定的值。 同样,这个含义只是"依照约定",并不会在Python解释器中触发特殊的行为。 单个下划线仅仅是一个有效的变量名称,会有这个用途而已。
详情见:http://stackoverflow.com/questions/1301346/the-meaning-of-a-single-and-a-double-underscore-before-an-object-name-in-python
或者: http://www.zhihu.com/question/19754941
.format
更便利。%
无法同时传递一个变量和元组,现在不推荐.format
用法 * 该函数把字符串当成一个模板,通过传入的参数进行格式化,并且使用大括号‘{}’作为特殊字符代替‘%’
(1)不带编号,即“{}”
(2)带数字编号,可调换顺序,即“{1}”、“{2}”
(3)带关键字,即“{a}”、“{tom}”
不带字段
print('{} {}'.format('hello','world')) # hello world
带数字编号
print('{0} {1}'.format('hello','world')) # hello world
# 打乱顺序
print('{0} {1} {0}'.format('hello','world')) # hello world
带关键字
print('{a} {tom} {a}'.format(tom='hello',a='world')) # world hello world
通过下标或key匹配参数
coord = (3, 5)
'X: {0[0]}; Y: {0[1]}'.format(coord) # 'X: 3; Y: 5'
a = {'a': 'test_a', 'b': 'test_b'}
'X: {0[a]}; Y: {0[b]}'.format(a)# 'X: test_a; Y: test_b'
print('{0:b}'.format(3))# 11
print('{:c}'.format(20))#
print('{:d}'.format(20))#20
print('{:o}'.format(20))#24
print('{:x}'.format(20))#14
print('{:e}'.format(20))#2.000000e+01
print('{:g}'.format(20.1))# 20.1
print('{:f}'.format(20))#20.000000
print('{:n}'.format(20))#20
print('{:%}'.format(20))#2000.000000%
"int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}".format(42) #'int: 42; hex: 2a; oct: 52; bin: 101010
"int: {0:d}; hex: {0:#x}; oct: {0:#o}; bin: {0:#b}".format(42)
'int: 42; hex: 0x2a; oct: 0o52; bin: 0b101010
<
(默认)左对齐
>
右对齐
^
中间对齐、
=
(只用于数字)在小数点后进行补齐
取位数“{:4s}”、"{:.2f}"等
print('{} and {}'.format('hello','world')) # 默认左对齐hello and world
print('{:10s} and {:>10s}'.format('hello','world')) # 取10位左对齐,取10位右对齐hello and world
print('{:^10s} and {:^10s}'.format('hello','world')) # 取10位中间对齐hello and world print('{} is {:.2f}'.format(1.123,1.123)) # 取2位小数1.123 is 1.12
print('{0} is {0:>10.2f}'.format(1.123)) # 取2位小数,右对齐,取10位1.123 is 1.12
{{
和 }}
>>> x = " {{ Hello }} {0} "
>>> print x.format(42)
' { Hello } 42 '
\{
和\}
文档在这里 Python documentation for format string syntax"
{}
表示替代字段。任何不再大括号中的将被看做文本,会被原样输出。如果你在文本中包含大括号, 你可以使用 {{
和 }}
。生成器(generator):使用了关键字 yield 的函数被称为生成器(generator)。
跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。
#!/usr/bin/python3
import sys
def fibonacci(n): # 生成器函数 - 斐波那契
a, b, counter = 0, 1, 0
while True:
if (counter > n):
return
yield a
a, b = b, a + b
counter += 1
f = fibonacci(10) # f 是一个迭代器,由生成器返回生成
while True:
try:
print (next(f), end=" ")
except StopIteration:
sys.exit()
# 0 1 1 2 3 5 8 13 21 34 55
迭代器:一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能***往前***不会***后退***。
迭代器有两个基本的方法:
字符串,列表或元组对象都可用于创建迭代器
把一个类作为一个迭代器使用需要在类中实现两个方法__ iter__()
与 __next__()
以及Python 的构造函数为 __init__()
, 它会在对象初始化的时候执行。
__iter__()
方法返回一个特殊的迭代器对象, 这个迭代器对象实现了__next__()
方法并通过 StopIteration
异常标识迭代的完成。
__next__()
方法会返回下一个迭代器对象。
创建一个返回数字的迭代器,初始值为 1,逐步递增 1:
class MyNumbers: def __iter__(self): self.a = 1
return self
def __next__(self): x = self.a
self.a += 1
return x
myclass = MyNumbers()myiter = iter(myclass)
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))
在 20 次迭代后停止执行:
class MyNumbers:
def __iter__(self):
self.a = 1
return self
def __next__(self):
if self.a <= 20:
x = self.a
self.a += 1
return x
else:
raise StopIteration
myclass = MyNumbers()
myiter = iter(myclass)
for x in myiter: print(x)
*args
and **kwargs
用*args
和**kwargs
只是为了方便并没有强制使用它们.
当你不确定你的函数里将要传递多少参数时,你可以用*args
。
例如,它可以传递任意数量的参数:
>>> def print_everything(*args):
for count, thing in enumerate(args):
... print '{0}. {1}'.format(count, thing)
...
>>> print_everything('apple', 'banana', 'cabbage')
0. apple
1. banana
2. cabbage
**kwargs
允许你使用没有事先定义的参数名:>>> def table_things(**kwargs):
... for name, value in kwargs.items():
... print '{0} = {1}'.format(name, value)
...
>>> table_things(apple = 'fruit', cabbage = 'vegetable')
cabbage = vegetable
apple = fruit
*args
和**kwargs
。命名参数在列表的最前端.例如:def table_things(titlestring, **kwargs)
*args
和**kwargs
可以同时在函数的定义中,但是*args
必须在**kwargs
前面.
并不是必须写成 *args
和 **kwargs
。 *(星号)
才是*必须的***. 你也可以写成 *ar 和 **k
。而写成 *args 和kwargs 只是一个通俗的命名约定。
当调用函数时你也可以用*
和**
语法.例如:
>>> def print_three_things(a, b, c):
... print 'a = {0}, b = {1}, c = {2}'.format(a,b,c)
...
>>> mylist = ['aardvark', 'baboon', 'cat']
>>> print_three_things(*mylist)
a = aardvark, b = baboon, c = cat
就像你看到的一样,它可以传递列表(或者元组)的每一项并把它们解包.注意必须与它们在函数里的参数相吻合.当然,你也可以在函数定义或者函数调用时用*.
AOP:在运行时,编译时,类和方法加载时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。***切入到指定类指定方法的代码片段***称为切面,
装饰器的作用就是为已经存在的对象添加额外的功能。
函数就是对象.因此,对象:
def my_new_decorator(a_function_to_decorate):
# 在函数里面,装饰器在运行中定义函数: 包装.
# 这个函数将被包装在原始函数的外面,所以可以在原始函数之前和之后执行其他代码..
def the_wrapper_function():
# 把要在原始函数被调用前的代码放在这里
print "Before the function runs"
# 调用原始函数(用括号)
a_function_to_decorate()
# 把要在原始函数调用后的代码放在这里
print "After the function runs"
# 在这里"a_function_to_decorate" 函数永远不会被执行
# 在这里返回刚才包装过的函数
# 在包装函数里包含要在原始函数前后执行的代码.
return the_wrapper_function
# 加入你建了个函数,不想修改了
def a_stand_alone_function():
print "I am a stand alone function, don't you dare modify me"
a_stand_alone_function()
#输出: I am a stand alone function, don't you dare modify me
# 现在,你可以装饰它来增加它的功能
# 把它传递给装饰器,它就会返回一个被包装过的函数.
a_function_decorated = my_new_decorator(a_stand_alone_function)
# 执行
a_function_decorated()
#输出s:
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs
@my_new_decorator
def another_stand_alone_function():
print "Leave me alone"
another_stand_alone_function()
#输出:
#Before the function runs
#Leave me alone
#After the function runs
从这里可以看出@decorator就是下面的简写:
another_stand_alone_function = my_new_decorator(another_stand_alone_function)
def a_decorator_passing_arguments(function_to_decorate):
def a_wrapper_accepting_arguments(arg1, arg2):
print "I got args! Look:", arg1, arg2
function_to_decorate(arg1, arg2)
return a_wrapper_accepting_arguments
# 当你调用装饰器返回的函数时,也就调用了包装器,把参数传入包装器里,
# 它将把参数传递给被装饰的函数里.
@a_decorator_passing_arguments
def print_full_name(first_name, last_name):
print "My name is", first_name, last_name
print_full_name("Peter", "Venkman")
# 输出:
#I got args! Look: Peter Venkman
#My name is Peter Venkman
# 装饰器就是一个'平常不过'的函数
def my_decorator(func):
print "I am an ordinary function"
def wrapper():
print "I am function returned by the decorator"
func()
return wrapper
# 因此你可以不用"@"也可以调用他
def lazy_function():
print "zzzzzzzz"
decorated_function = my_decorator(lazy_function)
#输出: I am an ordinary function
# 之所以输出 "I am an ordinary function"是因为你调用了函数,
# 并非什么魔法.
@my_decorator
def lazy_function():
print "zzzzzzzz"
#输出: I am an ordinary function
这里调用decorated_function()才会输出装饰器里面的方法,建一个装饰器.它只是一个新函数,去掉中间变量他就会变为真正的装饰器,那么如何去掉中间变量
def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):
print "I make decorators! And I accept arguments:", decorator_arg1, decorator_arg2
def my_decorator(func):
print "I am the decorator. Somehow you passed me arguments:", decorator_arg1, decorator_arg2
def wrapped(function_arg1, function_arg2) :
print ("I am the wrapper around the decorated function.\n"
"I can access all the variables\n"
"\t- from the decorator: {0} {1}\n"
"\t- from the function call: {2} {3}\n"
"Then I can pass them to the decorated function"
.format(decorator_arg1, decorator_arg2,
function_arg1, function_arg2))
return func(function_arg1, function_arg2)
return wrapped
return my_decorator
@decorator_maker_with_arguments("Leonard", "Sheldon")
def decorated_function_with_arguments(function_arg1, function_arg2):
print ("I am the decorated function and only knows about my arguments: {0}"
" {1}".format(function_arg1, function_arg2))
decorated_function_with_arguments("Rajesh", "Howard")
#输出:
#I make decorators! And I accept arguments: Leonard Sheldon
#I am the decorator. Somehow you passed me arguments: Leonard Sheldon
#I am the wrapper around the decorated function.
#I can access all the variables
# - from the decorator: Leonard Sheldon
# - from the function call: Rajesh Howard
#Then I can pass them to the decorated function
#I am the decorated function and only knows about my arguments: Rajesh Howard
“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
我们并不关心对象是什么类型,到底是不是鸭子,只关心行为。
比如在python中,有很多file-like的东西,比如StringIO,GzipFile,socket。它们有很多相同的方法,我们把它们当作文件使用。
又比如list.extend()方法中,我们并不关心它的参数是不是list,只要它是可迭代的,所以它的参数可以是list/tuple/dict/字符串/生成器等.
鸭子类型在动态语言中经常使用,非常灵活,使得python不像java那样专门去弄一大堆的设计模式。
函数重载主要是为了解决两个问题。
对于情况 1 ,函数功能相同,但是参数类型不同
对于情况 2 ,函数功能相同,但参数个数不同。缺省参数。对那些缺少的参数设定为缺省参数即可解决问题。因为你假设函数功能相同,那么那些缺少的参数终归是需要用的。
python 自然就不需要函数重载了。
Python语言提供了运算符重载功能,增强了语言的灵活性。
Python语言本身提供了很多内置方法,它的运算符重载就是通过重写这些Python内置方法实现的。
这些内置方法都是以双下划线开头和结尾的,类似于__X__
的形式,python通过这种特殊的命名方式来拦截操作符,以实现重载。当Python的内置操作运用于类对象时,Python会去搜索并调用对象中指定的方法完成操作。
类可以重载加减运算、打印、函数调用、索引等内置运算,运算符重载使我们的对象的行为与内置对象的一样。Python在调用操作符时会自动调用这样的方法,例如,如果类实现了__add__方法,当类的对象出现在+运算符中时会调用这个方法。
*常见运算符重载方法*
方法名 | 重载说明 | 运算符调用方式 |
---|---|---|
init | 构造函数 | 对象创建: X = Class(args) |
del | 析构函数 | X对象收回 |
add/sub | 加减运算 | X+Y, X+=Y/X-Y, X-=Y |
or | 运算符| | X|Y, X|=Y |
repr_/str | 打印/转换 | print(X)、repr(X)/str(X) |
bool | 布尔测试 | bool(X) |
lt, gt, le, ge, eq, ne | 特定的比较 | 依次为X |
radd | 右侧加法 | other+X |
iadd | 实地(增强的)加法 | X+=Y(or else add) |
iter, next | 迭代 | I=iter(X), next() |
contains | 成员关系测试 | item in X(X为任何可迭代对象) |
index | 整数值 | hex(X), bin(X), oct(X) |
enter, exit | 环境管理器 | with obj as var: |
get, set, delete | 描述符属性 | X.attr, X.attr=value, del X.attr |
new | 创建 | 在__init__之前创建对象 |
__new__
和__init__
的区别这个__new__
确实很少见到,先做了解吧.
__new__
是一个静态方法,而__init__
是一个实例方法.__new__
方法会返回一个创建的实例,而__init__
什么都不返回.__new__
返回一个cls的实例时后面的__init__
才能被调用.__new__
,初始化一个实例时用__init__
.stackoverflow
ps: __metaclass__
是创建类时起作用.所以我们可以分别使用__metaclass__
,__new__
和__init__
来分别在类创建,实例创建和实例初始化的时候做一些小手脚.
单例模式***是一种常用的软件设计模式。在它的核心结构中只包含一个被称为***单例类***的***特殊类。通过单例模式可以保证系统中一个类只有一个实例,而且该实例易于外界访问。从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
__new__()
在__init__()
之前被调用,用于生成实例对象。利用这个方法和类的属性的特点可以实现设计模式的单例模式。单例模式是指创建唯一对象,单例模式设计的类只能实例
__new__
方法class Singleton(object):
def __new__(cls, *args, **kw):
if not hasattr(cls, '_instance'):
orig = super(Singleton, cls)
cls._instance = object.__new__(cls, *args, **kw)
return cls._instance
class MyClass(Singleton):
a = 1
创建实例时把所有实例的__dict__
指向同一个字典,这样它们具有相同的属性和方法.
class Borg(object):
_state = {}
def __new__(cls, *args, **kw):
ob = super(Borg, cls).__new__(cls, *args, **kw)
ob.__dict__ = cls._state
return ob
class MyClass2(Borg):
a = 1
def singleton(cls):
instances = {}
def getinstance(*args, **kw):
if cls not in instances:
instances[cls] = cls(*args, **kw)
return instances[cls]
return getinstance
@singleton
class MyClass:
...
作为python的模块是天然的单例模式
# mysingleton.py
class My_Singleton(object):
def foo(self):
pass
my_singleton = My_Singleton()
# to use
from mysingleton import my_singleton
my_singleton.foo()
def singleton(cls):
instances = {}
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
@singleton
class Foo(object):
pass
foo1 = Foo()
foo2 = Foo()
print(foo1 is foo2) # True
第二种方法:使用基类
New 是真正创建实例对象的方法,所以重写基类的new 方法,以此保证创建对象的时候只生成一个实例
class Singleton(object):
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls._instance
class Foo(Singleton):
pass
foo1 = Foo()
foo2 = Foo()
print(foo1 is foo2) # True
第三种方法:元类,元类是用于创建类对象的类,类对象创建实例对象时一定要调用call方法,因此在调用call时候保证始终只创建一个实例即可,type是python的元类
class Singleton(type):
def __call__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instance
# Python2
class Foo(object):
__metaclass__ = Singleton
# Python3
class Foo(metaclass=Singleton):
pass
foo1 = Foo()
foo2 = Foo()
print(foo1 is foo2) # True
Python 中,一个变量的作用域总是由在代码中被赋值的地方所决定的。
def addx(x):
def adder(y): return x + y
return adder
c = addx(8)
>>> c.__name__
'adder'
>>> c(10)
18
如果在一个内部函数里:adder(y)就是这个内部函数,对在外部作用域(但不是在全局作用域)的变量进行引用:x就是被引用的变量,x在外部作用域addx里面,但不在全局作用域里,则这个内部函数adder就是一个闭包。
闭包=函数块+定义函数时的环境,
adder就是函数块,x就是环境。
二,使用闭包注意事项
>>> def foo():
... m = 0
... def foo1():
... m = 1
... print m
...
... print m
... foo1()
... print m
...
>>> foo()
0
1
0
从执行结果可以看出,虽然在闭包里面也定义了一个变量m,但是其不会改变外部函数中的局部变量m。
def foo():
a = 1
def bar():
a = a + 1
return a
return bar
这段程序的本意是要通过在每次调用闭包函数时都对变量a进行递增的操作。但在实际使用时
>>> c = foo()
>>> print c()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in bar
UnboundLocalError: local variable 'a' referenced before assignment
这是因为在执行代码 c = foo()时,python会导入全部的闭包函数体bar()来分析其的局部变量,python规则指定所有在赋值语句左面的变量都是局部变量,则在闭包bar()中,变量a在赋值符号"="的左面,被python认为是bar()中的局部变量。再接下来执行print c()时,程序运行至a = a + 1时,因为先前已经把a归为bar()中的局部变量,所以python会在bar()中去找在赋值语句右面的a的值,结果找不到,就会报错。解决的方法很简单。
def foo():
a = [1]
def bar():
a[0] = a[0] + 1
return a[0]
return bar
只要将a设定为一个容器就可以了。这样使用起来多少有点不爽,所以在python3以后,在a = a + 1 之前,使用语句nonloacal a就可以了,该语句显式的指定a不是闭包的局部变量。
for i in range(3):
print i
三,作用
用途1,当闭包执行完后,仍然能够保持住当前的运行环境。
比如说,如果你希望函数的每次执行结果,都是基于这个函数上次的运行结果。我以一个类似棋盘游戏的例子来说明。假设棋盘大小为50*50,左上角为坐标系原点(0,0),我需要一个函数,接收2个参数,分别为方向(direction),步长(step),该函数控制棋子的运动。棋子运动的新的坐标除了依赖于方向和步长以外,当然还要根据原来所处的坐标点,用闭包就可以保持住这个棋子原来所处的坐标。
origin = [0, 0] # 坐标系统原点
legal_x = [0, 50] # x轴方向的合法坐标
legal_y = [0, 50] # y轴方向的合法坐标
def create(pos=origin):
def player(direction,step):
# 这里应该首先判断参数direction,step的合法性,比如direction不能斜着走,step不能为负等
# 然后还要对新生成的x,y坐标的合法性进行判断处理,这里主要是想介绍闭包,就不详细写了。
new_x = pos[0] + direction[0]*step
new_y = pos[1] + direction[1]*step
pos[0] = new_x
pos[1] = new_y
#注意!此处不能写成 pos = [new_x, new_y],原因在上文有说过
return pos
return player
player = create() # 创建棋子player,起点为原点
print player([1,0],10) # 向x轴正方向移动10步
print player([0,1],20) # 向y轴正方向移动20步
print player([-1,0],10) # 向x轴负方向移动10步
输出为
[10, 0]
[10, 20]
[0, 20]
用途2,闭包可以根据外部作用域的局部变量来得到不同的结果,这有点像一种类似配置功能的作用,我们可以修改外部的变量,闭包根据这个变量展现出不同的功能。比如有时我们需要对某些文件的特殊行进行分析,先要提取出这些特殊行。
def make_filter(keep):
def the_filter(file_name):
file = open(file_name)
lines = file.readlines()
file.close()
filter_doc = [i for i in lines if keep in i]
return filter_doc
return the_filter
如果我们需要取得文件"result.txt"中含有"pass"关键字的行,则可以这样使用例子程序
filter = make_filter("pass")
filter_result = filter("result.txt")
以上两种使用场景,用面向对象也是可以很简单的实现的,但是在用Python进行函数式编程时,闭包对数据的持久化以及按配置产生不同的功能,是很有帮助的。
当 Python 遇到一个变量的话他会按照这样的顺序进行搜索:
本地作用域(Local)→当前作用域被嵌入的本地作用域(Enclosing locals)→全局/模块作用域(Global)→内置作用域(Built-in)
作用域:命名空间,python创建、改变或查找变量名都是在所谓的命名空间中。***变量在赋值创建***时,python中***代码赋值***的地方决定了这个变量存在于哪个命名空间,也就是他的可见范围。
对于函数,函数为程序增加一个额外的命名空间层来最小化相同变量之间的冲突:默认情况下,一个函数内赋值的所有变量名都与该函数的命名空间相互关联。这条规则意味着:
可见,一个变量的作用域取决于它在代码中被赋值的位置,而与函数调用完全无关
注意:一个函数内部任何类型的赋值都会把一个名称划定为局部的,包括=语句,import的模块名,def的函数名,函数形式参数名等。如果你在def中以任何方式赋值一个名称,他都会默认为该函数的局部名称。
但是,要注意原位置改变对象并不会把变量划分为局部变量,只有对变量赋值才可以。例如L在模块顶层被赋值为列表,在一个函数内类似L.append(X)的语句并不会将L划分为局部变量,而L = X却可以。append是修改对象,而=是赋值,要明白修改一个对象并不是对一个变量名赋值。
总结三条简单的规则
变量名解析机制称为LEGB规则,也是有作用域的命名而来的:
是一个名为builtins的内置模块,但是必须要导入之后才能使用内置作用域
global语句告诉python生成一个或多个全局变量,总结一下全局变量:
global OLD_URL
程序设计中最少化全局变量,因为你可能也不知道语句在什么时候最后一次执行,值是多少;最小化跨文件的修改,变量的值可能化发生难以预测的改变甚至报错。
这里的作用域主要是***作用域查找规则LEGB的第二个层次E,即def或lambda定义的外层函数的局部作用域。嵌套函数产生相对应的嵌套作用域,对应嵌套的代码结构。***
嵌套的函数作用域中变量查找规则,在一个函数中:
在存在嵌套函数时,若global存在,global声明会将变量映射到外层模块,嵌套函数中变量也许会被引用,但他们必须要nonlocal声明才能修改。
nonlocal和global都遵循LEGB准则,但是限制了查找规则。
使用global语句意味着名称存在于外层的模块中,global使得作用域查找从外围的作用域开始,并且允许对那里 的名称赋值。如果名称不存在该模块中,作用域查找会继续进行到内置作用域,但是全局名称的赋值总是在模块的作用域中创建或修改它们。
nonlocal语句将作用域查找限制为只在外层的def中,同时要求名称已经存在,并且允许对它们赋值,作用域不会继续查找到全局或内置作用域。nonlocal也意味着忽略局部作用域。
线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取的独立线程运行的限制,说白了就是一个核只能在同一时间运行一个线程。对于io密集型任务,python的多线程起到作用,但对于cpu密集型任务,python的多线程几乎占不到任何优势,还有可能因为争夺资源而变慢。
线程的因作用可以划分为不同的类型。大致可分为:
解决办法就是多进程和下面的协程(协程也只是单CPU,但是能减小切换代价提升性能)。
import threading
import time
def run(n):
print("task", n)
# time.sleep(1)
print('2s')
# time.sleep(1)
print('1s')
#time.sleep(1)
print('0s')
# time.sleep(1)
if __name__ == '__main__':
t1 = threading.Thread(target=run, args=("t1",))
t2 = threading.Thread(target=run, args=("t2",))
t1.start()
t2.start()
----------------------------------
task t1
2s
1s
0s
task t2
2s
1s
0s
import threading
import time
class MyThread(threading.Thread):
def __init__(self, n):
super(MyThread, self).__init__() # 重构run函数必须要写
self.n = n
def run(self):
print("task", self.n)
time.sleep(1)
print('2s')
time.sleep(1)
print('1s')
time.sleep(1)
print('0s')
time.sleep(1)
if __name__ == "__main__":
t1 = MyThread("t1")
t2 = MyThread("t2")
t1.start()
t2.start()
----------------------------------
>>> task t1
>>> task t2
>>> 2s
>>> 2s
>>> 1s
>>> 1s
>>> 0s
>>> 0s
import threading
import time
class MyThread(threading.Thread):
def __init__(self, n):
super(MyThread, self).__init__() # 重构run函数必须要写
self.n = n
def run(self):
print("task", self.n)
time.sleep(1)
print('2s')
time.sleep(1)
print('1s')
time.sleep(1)
print('0s')
time.sleep(1)
if __name__ == "__main__":
t1 = MyThread("t1")
t2 = MyThread("t2")
t1.start()
t2.start()
----------------------------------
>>> task t1
>>> task t2
>>> 2s
>>> 2s
>>> 1s
>>> 1s
>>> 0s
>>> 0s
import threading
import time
def run(n):
print("task", n)
time.sleep(1) #此时子线程停1s
print('3')
time.sleep(1)
print('2')
time.sleep(1)
print('1')
if __name__ == '__main__':
t = threading.Thread(target=run, args=("t1",))
t.setDaemon(True) #把子进程设置为守护线程,必须在start()之前设置
t.start()
print("end")
----------------------------------
>>> task t1
>>> end
import threading
import time
def run(n):
print("task", n)
time.sleep(1) #此时子线程停1s
print('3')
time.sleep(1)
print('2')
time.sleep(1)
print('1')
if __name__ == '__main__':
t = threading.Thread(target=run, args=("t1",))
t.setDaemon(True) #把子进程设置为守护线程,必须在start()之前设置
t.start()
t.join() # 设置主线程等待子线程结束
print("end")
----------------------------------
>>> task t1
>>> 3
>>> 2
>>> 1
>>> end
import threading
import time
g_num = 100
def work1():
global g_num
for i in range(3):
g_num += 1
print("in work1 g_num is : %d" % g_num)
time.sleep(1)
def work2():
global g_num
for i in range(3):
print("in work2 g_num is : %d" % g_num)
time.sleep(1)
if __name__ == '__main__':
t1 = threading.Thread(target=work1)
t1.start()
# time.sleep(1)
t2 = threading.Thread(target=work2)
t2.start()
# t1.join()
# t2.join()
---------------------------------
in work1 g_num is : 101
in work2 g_num is : 101
in work2 g_num is : 101
in work1 g_num is : 102
in work1 g_num is : 103
in work2 g_num is : 103
由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁,即同一时刻允许一个线程执行操作。线程锁用于锁定资源,你可以定义多个锁, 像下面的代码, 当你需要独占某一资源时,任何一个锁都可以锁这个资源,就好比你用不同的锁都可以把相同的一个门锁住是一个道理。
由于线程之间是进行随机调度,如果有多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期,我们也称此为“线程不安全”。
为了方式上面情况的发生,就出现了互斥锁(Lock)
from threading import Thread,Lock
import os,time
def work():
global n
lock.acquire()
temp=n
time.sleep(0.1)
n=temp-1
lock.release()
if __name__ == '__main__':
lock=Lock()
n=100
l=[]
for i in range(100):
p=Thread(target=work)
l.append(p)
p.start()
for p in l:
p.join()
import threading
import time
def Func(lock):
global gl_num
lock.acquire()
gl_num += 1
time.sleep(1)
print(gl_num)
lock.release()
if __name__ == '__main__':
gl_num = 0
lock = threading.RLock()
for i in range(10):
t = threading.Thread(target=Func, args=(lock,))
t.start()
import threading
import time
def run(n, semaphore):
semaphore.acquire() #加锁
time.sleep(1)
print("run the thread:%s\n" % n)
semaphore.release() #释放
if __name__ == '__main__':
num = 0
semaphore = threading.BoundedSemaphore(5) # 最多允许5个线程同时运行
for i in range(22):
t = threading.Thread(target=run, args=("t-%s" % i, semaphore))
t.start()
while threading.active_count() != 1:
pass # print threading.active_count()
else:
print('-----all threads done-----')
事件处理的机制:全局定义了一个“Flag”,当flag值为“False”,那么event.wait()就会阻塞,当flag值为“True”,那么event.wait()便不再阻塞。
事件对象管理一个内部标志,通过set()
方法将其设置为True
,并使用clear()
方法将其设置为False
。wait()
方法阻塞,直到标志为True
。该标志初始为False
。
is_set()
当且仅当内部标志为True
时返回True
。
set()
将内部标志设置为True
。所有等待它成为True
的线程都被唤醒。当标志保持在True
的状态时,线程调用wait()
是不会阻塞的。
clear()
将内部标志重置为False
。随后,调用wait()
的线程将阻塞,直到另一个线程调用set()
将内部标志重新设置为True
。
wait(timeout=None)
阻塞直到内部标志为真。如果内部标志在wait()
方法调用时为True
,则立即返回。否则,则阻塞,直到另一个线程调用set()
将标志设置为True
,或发生超时。
该方法总是返回True
,除非设置了timeout
并发生超时。
#利用Event类模拟红绿灯
import threading
import time
event = threading.Event()
def lighter():
count = 0
event.set() #初始值为绿灯 Flag=True
while True:
if 5 < count <=10 :
event.clear() # 红灯,清除标志位 Flag=False
print("\33[41;1mred light is on...\033[0m")
elif count > 10:
event.set() # 绿灯,设置标志位 Flag = True
count = 0
else:
print("\33[42;1mgreen light is on...\033[0m")
time.sleep(1)
count += 1
def car(name):
while True:
if event.is_set(): #判断是否设置了标志位 Flag==True
print("[%s] running..."%name)
time.sleep(1)
else: # Flag==False
print("[%s] sees red light,waiting..."%name)
event.wait()
print("[%s] green light is on,start going..."%name)
light = threading.Thread(target=lighter,)
light.start()
car = threading.Thread(target=car,args=("MINI",))
car.start()
其他语言,CPU 是多核时是支持多个线程同时执行。但在 Python 中,无论是单核还是多核,同时只能由一个线程在执行。其根源是 GIL 的存在。
GIL 的全称是 ***Global Interpreter Lock(全局解释器锁)***,来源是 Python 设计之初的考虑,为了数据安全所做的决定。某个线程想要执行,必须先拿到 GIL,我们可以把 GIL 看作是“通行证”,并且在一个 Python 进程中,GIL 只有一个。拿不到通行证的线程,就不允许进入 CPU 执行。
而目前 Python 的解释器有多种,例如:
每次释放 GIL锁,线程进行锁竞争、切换线程,会消耗资源。这就导致打印线程执行时长,会发现耗时更长的原因。
并且由于 GIL 锁存在,Python 里一个进程永远只能同时执行一个线程(拿到 GIL 的线程才能执行),
进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
结构组成:程序、数据和进程控制块
多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。
并行:指两个或两个以上事件(或线程)在同一时刻发生,是真正意义上的不同事件或线程在同一时刻,在不同CPU资源呢上(多核),同时执行。
特点
指一个物理CPU(也可以多个物理CPU) 在若干道程序(或线程)之间多路复用,并发性是对有限物理资源强制行使多用户共享以提高效率。
特点
FCFS 先来先服务调度法(First Come First Service):按照先后顺序处理事件的一种算法。
SJF/SPN 短作业优先法(Shortest Job First):又称为短进程优先算法(SPN,Shortest Process Next),能有效减少平均周转时间。
RR 时间片轮转法(Round Robin):让每个进程在就绪队列中的等待时间与享受服务的时间成比例,也就是需要将CPU的处理时间分成固定大小的时间片,如果一个进程在被调度选中之后用完了系统规定的时间片,但又未完成要求的任务,则它自行释放自己所占有的CPU而排到就绪队列的末尾,等待下一次调度。同时,进程调度程序又去调度当前就绪队列中的第一个进程。
如果对这些进程区别对待,给予不同的优先级和时间片从直观上看,可以进一步改善系统服务质量和效率。例如,我们可把就绪队列按照进程到达就绪队列的类型和进程被阻塞时的阻塞原因分成不同的就绪队列,每个队列按FCFS原则排列,各队列之间的进程享有不同的优先级,但同一队列内优先级相同。这样,当一个进程在执行完它的时间片之后,或从睡眠中被唤醒以及被创建之后,将进入不同的就绪队列。
多级反馈队列调度算法则不必事先知道各种进程所需的执行时间,而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法。在采用多级反馈队列调度算法的系统中,调度算法的实施过程如下所述。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N8qoSbkT-1614861271631)(C:\Users\Administrator\Desktop\interview_python-master\img\进程调度.png)]
就绪(Ready)状态:当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。
执行/运行(Running)状态:当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。
阻塞(Blocked)状态:正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。
同步(synchronous): 所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。
异步(asynchronous):所谓异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。
但凡是硬件,都需要有操作系统去管理,只要有操作系统,就有进程的概念,就需要有创建进程的方式,一些操作系统只为一个应用程序设计,比如微波炉中的控制器,一旦启动微波炉,所有的进程都已经存在。
无论哪一种,新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的。
process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建。
语法:Process([group [, target [, name [, args [, kwargs]]]]])
由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)。
注意:1. 必须使用关键字方式来指定参数;2. args指定的为传给target函数的位置参数,是一个元祖形式,必须有逗号。
参数介绍:
group:参数未使用,默认值为None。
target:表示调用对象,即子进程要执行的任务。
args:表示调用的位置参数元祖。
kwargs:表示调用对象的字典。如kwargs = {‘name’:Jack, ‘age’:18}。
name:子进程名称。
返回值:实例化对象
方法/属性 | 说明 |
---|---|
start() | 启动进程,调用进程中的run()方法。 |
run() | 进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法 。 |
terminate() | 强制终止进程,不会进行任何清理操作。如果该进程终止前,创建了子进程,那么该子进程在其强制结束后变为僵尸进程;如果该进程还保存了一个锁那么也将不会被释放,进而导致死锁。使用时,要注意。 |
is_alive() | 判断某进程是否存活,存活返回True,否则False。 |
join([timeout]) | 主线程等待子线程终止。timeout为可选择超时时间;需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程 。 |
daemon | 默认值为False,如果设置为True,代表该进程为后台守护进程;当该进程的父进程终止时,该进程也随之终止;并且设置为True后,该进程不能创建子进程,设置该属性必须在start()之前 |
name | 进程名称。 |
pid | 进程pid |
exitcode | 进程运行时为None,如果为-N,表示被信号N结束了。 |
authkey | 进程身份验证,默认是由os.urandom()随机生成32字符的字符串。这个键的用途是设计涉及网络连接的底层进程间的通信提供安全性,这类连接只有在具有相同身份验证才能成功。 |
实例1:
import os
from multiprocessing import Process
def func_one():
print("This is son_one")
print("son_one:%s father:%s" % (os.getpid(), os.getppid()))
def func_two():
print("This is son_two")
print("son_two:%s father:%s" % (os.getpid(), os.getppid()))
if __name__ == '__main__':
p_one = Process(target=func_one)
P_two = Process(target=func_two)
p_one.start()
P_two.start()
print("son:%s father:%s" % (os.getpid(), os.getppid())) # father是pycharm
结果:
son:14560 father:8040
This is son_one
This is son_two
son_one:5228 father:14560
son_two:9736 father:14560Process finished with exit code 0
实例2:
我们也可以使用迭代来处理进程的执行:
import time
from multiprocessing import Process
def func_one(name):
print("My name is", name)
time.sleep(1)
def func_two(age):
print("My age is", age)
time.sleep(5)
if __name__ == '__main__':
lst_one = []
lst_two = []
for i in range(5):
p_one = Process(target=func_one, args=('Mike',))
p_two = Process(target=func_two, args=(18,))
p_one.start()
p_two.start()
lst_two.append(p_two)
# [p_two.join() for p_two in lst_two] # 父进程要等子进程结束
print("End")
结果:
End
My name is Mike
My age is 18
My age is 18
My age is 18
My age is 18
My name is Mike
My name is Mike
My age is 18
My name is Mike
My name is MikeProcess finished with exit code 0
去掉注释结果:
My name is Mike
My age is 18
My name is Mike
My name is Mike
My age is 18
My age is 18
My age is 18
My name is Mike
My name is Mike
My age is 18
EndProcess finished with exit code 0
除了上面这些开启进程的方法之外,还有一种以继承Process的方式开启进程的方式:
import os
from multiprocessing import Process
class MyProcess(Process):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
print("进程为%s,父进程为%s" % (os.getpid(), os.getppid()))
print("我的名字是%s" % self.name)
if __name__ == '__main__':
p_one = MyProcess('张三')
p_two = MyProcess('李四')
p_thr = MyProcess('王五')
p_one.start() # 自动调用run()
p_two.start()
p_thr.run() # 直接调用run()
p_one.join()
p_two.join()
# p_thr.join() # 调用run()函数的不可以调用join()
print("主进程结束")
结果:
进程为22376,父进程为17808
我的名字是王五
进程为15212,父进程为22376
我的名字是张三
进程为20588,父进程为22376
我的名字是李四
主进程结束Process finished with exit code 0
注意:直接调用run()函数的进程不能使用join()方法。
下面我们来演示进程之间数据隔离的问题:
from multiprocessing import Process
def func_one():
global n
n = 0
print("在func_one中的n为%s" % n)
def func_two():
global n
n = 1
print("在func_two中的n为%s" % n)
if __name__ == '__main__':
n = 100
p_one = Process(target=func_one)
p_two = Process(target=func_two)
p_one.start()
p_two.start()
print("主进程的n为%s" % n)
结果:
主进程的n为100
在func_one中的n为0
在func_two中的n为1Process finished with exit code 0
守护进程就是会随着主进程的结束而结束的进程,具有以下两个特点:
实例1:
import os
import time
from multiprocessing import Process
class MyProcess(Process):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
print("进程为%s,父进程为%s" % (os.getpid(), os.getppid()))
print("我的名字是%s" % self.name)
if __name__ == '__main__':
p_one = MyProcess('张三')
p_two = MyProcess('李四')
p_two.daemon = True # 默认为False,必须在start()之前设置
p_one.start()
p_two.start()
time.sleep(5)
print("主进程结束")
结果:
进程为22452,父进程为22344
我的名字是张三
进程为11336,父进程为22344
我的名字是李四
主进程结束Process finished with exit code 0
服务端:
from socket import *
from multiprocessing import Process
HOST = '127.0.0.1'
PORT = 8080
ADDRESS = (HOST, PORT)
BUFF_SIZE = 1024
ss = socket(AF_INET, SOCK_STREAM)
ss.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
ss.bind(ADDRESS)
ss.listen(5)
def talk(conn, add):
while 1:
try:
msg = conn.recv(BUFF_SIZE)
if not msg:
break
conn.send(msg.upper())
except Exception:
break
if __name__ == '__main__':
while 1:
conn, add = ss.accept()
p = Process(target=talk, args=(conn, add))
p.start()
客户端:
from socket import *
HOST = '127.0.0.1'
PORT = 8080
ADDRESS = (HOST, PORT)
BUFF_SIZE = 1024
sc = socket(AF_INET,SOCK_STREAM)
sc.connect(ADDRESS)
while 1:
msg = input(">>>".strip())
if not msg:
continue
sc.send(msg.encode('utf-8'))
msg = sc.recv(BUFF_SIZE)
print(msg.decode('utf-8'))
terminate()和is_alive(): 结束进程和判断进程是否存活。如,
当多个进程使用同一份数据资源的时候,就会引发数据安全或顺序混乱问题:
from multiprocessing import Process, Lock
import time
import json
import random
def search():
dic = json.load(open('db'))
time.sleep(random.random()) # 模拟读取数据
print("\033[43m剩余票数:%s\033[0m" % dic['count'])
def get():
dic = json.load(open('db'))
time.sleep(random.random()) # 模拟网络延迟
if dic['count'] > 0:
dic['count'] -= 1 # 购票成功后减一
time.sleep(1)
json.dump(dic, open('db', 'w'))
print("\033[43m购票成功\033[0m")
else:
print("尚无余票")
def task():
search()
get()
if __name__ == '__main__':
for i in range(100):
p = Process(target=task)
p.start()
没有使用锁,以上代码会造成错误和紊乱。下面是加锁改进版:
from multiprocessing import Process, Lock
import time
import json
import random
def search():
dic = json.load(open('db'))
time.sleep(random.random()) # 模拟读取数据
print("\033[43m剩余票数:%s\033[0m" % dic['count'])
def get():
dic = json.load(open('db'))
time.sleep(random.random()) # 模拟网络延迟
if dic['count'] > 0:
dic['count'] -= 1 # 购票成功后减一
time.sleep(1)
json.dump(dic, open('db', 'w'))
print("\033[32m购票成功\033[0m")
else:
print("\033[31m尚无余票\033[0m")
def task(lock):
lock.acquire()
search()
get()
lock.release()
if __name__ == '__main__':
lock = Lock()
for i in range(10):
p = Process(target=task, args=(lock,))
p.start()
结果(db文件为{“count”: 3}):
剩余票数:3
购票成功
剩余票数:2
购票成功
剩余票数:1
购票成功
剩余票数:0
尚无余票
剩余票数:0
尚无余票
剩余票数:0
尚无余票
剩余票数:0
尚无余票
剩余票数:0
尚无余票
剩余票数:0
尚无余票
剩余票数:0
尚无余票Process finished with exit code 0
另一个版本:
from multiprocessing import Process, Lock
import time
import json
import random
def search(num):
dic = json.load(open('db'))
time.sleep(random.random())
print("第%s个人查到余票还剩下%s张" % (num, dic['count']))
def get(num, lock):
lock.acquire()
dic = json.load(open('db'))
time.sleep(random.random())
if dic['count'] > 0:
print('\033[31m 第%s个人买到票了\033[0m' % num)
dic['count'] -= 1
json.dump(dic, open('db', 'w'))
time.sleep(random.random())
else:
print('\033[32m 第%s个人没有买到票\033[0m' % num)
lock.release()
if __name__ == '__main__':
l = Lock()
for i in range(100):
p_search = Process(target=search, args=(i + 1,))
p_search.start()
for i in range(10):
p_get = Process(target=get, args=(i + 1, l))
p_get.start()
结果:(略)
#因此我们最好找寻一种解决方案能够兼顾:1、效率高(多个进程共享一块内存的数据)2、帮我们处理好锁问题。
mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。 队列和管道都是将数据存放于内存中 队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来, 我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。
事件(Event),用于线程间通信,即程序中的其一个线程需要通过判断某个线程的状态来确定自己下一步的操作,就用到了event对象。
事件处理的机制:
红绿灯实例:
from multiprocessing import Process, Event
import time
import random
def traffic_light(e):
'''信号灯函数'''
while 1: # 红绿灯得一直亮着,要么是红灯要么是绿灯
if e.is_set(): # True,代表绿灯亮,那么此时代表可以过车
time.sleep(5) # 所以在这让灯等5秒钟,这段时间让车过
print('\033[31m红灯亮!\n车辆等待中...\033[0m') # 绿灯亮了5秒后应该提示到红灯亮
e.clear() # 把is_set设置为False
else:
time.sleep(5) # 此时代表红灯亮了,此时应该红灯亮5秒,在此等5秒
print('\033[32m绿灯亮!\n车辆通过中...\033[0m') # 红的亮够5秒后,该绿灯亮了
e.set() # 将is_set设置为True
def car_status(num, e):
e.wait() # 车等在红绿灯,此时要看是红灯还是绿灯,如果is_set为True就是绿灯,此时可以过车
print('第%s辆车过去了' % num)
if __name__ == '__main__':
event = Event()
tra_light = Process(target=traffic_light, args=(event,)) # 信号灯的进程
tra_light.start()
for i in range(50): # 描述50辆车的进程
if i % 3 == 0:
time.sleep(random.randint(1, 5)) # 车辆出现时间随机
car = Process(target=car_status, args=(i + 1, event,))
car.start()
结果:(略)
创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。
Queue[maxsize]:maxsize
是队列中允许的最大项数。如果省略此参数,则无大小限制。底层队列使用管道和锁定实现。另外,还需要运行支持线程以便队列中的数据传输到底层管道中。
设q为队列实例化对象:
方法 | 说明 |
---|---|
q.get( [ block [ ,timeout ] ] ) | 返回q中的一个项目。如果q为空,此方法将阻塞,直到队列中有项目可用为止。block用于控制阻塞行为,默认为True. 如果设置为False,将引发Queue.Empty异常(定义在Queue模块中)。timeout是可选超时时间,用在阻塞模式中。如果在制定的时间间隔内没有项目变为可用,将引发Queue.Empty异常。 |
q.get_nowait( ) | 同q.get(False)方法。 |
q.put(item [, block [,timeout ] ] ) | 将item放入队列。如果队列已满,此方法将阻塞至有空间可用为止。block控制阻塞行为,默认为True。如果设置为False,将引发Queue.Empty异常(定义在Queue库模块中)。timeout指定在阻塞模式中等待可用空间的时间长短。超时后将引发Queue.Full异常。 |
q.qsize() | 返回队列中目前项目的正确数量。此函数的结果并不可靠,因为在返回结果和在稍后程序中使用结果之间,队列中可能添加或删除了项目。在某些系统上,此方法可能引发NotImplementedError异常。 |
q.empty() | 如果调用此方法时 q为空,返回True。如果其他进程或线程正在往队列中添加项目,结果是不可靠的。也就是说,在返回和使用结果之间,队列中可能已经加入新的项目。 |
q.full() | 如果q已满,返回为True. 由于线程的存在,结果也可能是不可靠的(参考q.empty()方法)。 |
q.close() | 关闭队列,防止队列中加入更多数据。调用此方法时,后台线程将继续写入那些已入队列但尚未写入的数据,但将在此方法完成时马上关闭。如果q被垃圾收集,将自动调用此方法。关闭队列不会在队列使用者中生成任何类型的数据结束信号或异常。例如,如果某个使用者正被阻塞在get()操作上,关闭生产者中的队列不会导致get()方法返回错误。 |
q.cancel_join_thread() | 不会再进程退出时自动连接后台线程。这可以防止join_thread()方法阻塞。 |
q.join_thread() | 连接队列的后台线程。此方法用于在调用q.close()方法后,等待所有队列项被消耗。默认情况下,此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread()方法可以禁止这种行为。 |
import time
from multiprocessing import Process, Queue
def func(queue):
queue.put([time.asctime(), 'Hello', 'Python'])
if __name__ == '__main__':
q = Queue()
p = Process(target=func, args=(q,))
p.start()
print(q.get())
p.join()
结果:
[‘Mon Aug 27 15:26:47 2018’, ‘Hello’, ‘Python’]
Process finished with exit code 0
import os
import time
import multiprocessing
def input_queue(q):
info = str(os.getpid()) + '(put):' + str(time.asctime())
q.put(info)
print(info)
def output_queue(q):
info = q.get()
print('\033[32m%s\033[0m' % info)
if __name__ == '__main__':
multiprocessing.freeze_support()
record_one = []
record_two = []
queue = multiprocessing.Queue(3)
# 放入数据
for i in range(10):
p = multiprocessing.Process(target=input_queue, args=(queue,))
p.start()
record_one.append(p)
# 取出数据
for i in range(10):
p = multiprocessing.Process(target=output_queue, args=(queue,))
p.start()
record_one.append(p)
for p in record_one:
p.join()
for p in record_two:
p.join()
结果:
328(put):Mon Aug 27 15:45:34 2018
328(put):Mon Aug 27 15:45:34 2018
27164(put):Mon Aug 27 15:45:34 2018
27164(put):Mon Aug 27 15:45:34 2018
8232(put):Mon Aug 27 15:45:34 2018
8232(put):Mon Aug 27 15:45:34 2018
23804(put):Mon Aug 27 15:45:34 2018
23804(put):Mon Aug 27 15:45:34 2018
23004(put):Mon Aug 27 15:45:34 2018
23004(put):Mon Aug 27 15:45:34 2018
22184(put):Mon Aug 27 15:45:34 2018
22184(put):Mon Aug 27 15:45:34 2018
24948(put):Mon Aug 27 15:45:34 2018
24948(put):Mon Aug 27 15:45:34 2018
19704(put):Mon Aug 27 15:45:34 2018
19704(put):Mon Aug 27 15:45:34 2018
8200(put):Mon Aug 27 15:45:34 2018
8200(put):Mon Aug 27 15:45:34 2018
14380(put):Mon Aug 27 15:45:34 2018
14380(put):Mon Aug 27 15:45:34 2018Process finished with exit code 0
Pipe([duplex]):在线程之间创建一条管道,并返回元祖(con1,con2),其中con1,con2表示管道两端连接的对象。
duplex:默认管道为全双工的,如果将duplex映射为False,con1只能用于接收,con2只能由于发送。
注意:必须在产生Process之前产生管道。
方法 | 说明 |
---|---|
con1.recv() | 接收conn2.send(obj)发送的对象。如果没有消息可接收,recv方法会一直阻塞。如果连接的另外一端已经关闭,那么recv方法会抛出EOFError。 |
con1.send(obj) | 通过连接发送对象。obj是与序列化兼容的任意对象。 |
con1.close() | 关闭连接。如果conn1被垃圾回收,将自动调用此方法。 |
con1.fileno() | 返回连接使用的整数文件描述符。 |
conn1.poll([timeout]) | 如果连接上的数据可用,返回True。timeout指定等待的最长时限。如果省略此参数,方法将立即返回结果。如果将timeout射成None,操作将无限期地等待数据到达。 |
conn1.recv_bytes([maxlength]) | 接收c.send_bytes()方法发送的一条完整的字节消息。maxlength指定要接收的最大字节数。如果进入的消息,超过了这个最大值,将引发IOError异常,并且在连接上无法进行进一步读取。如果连接的另外一端已经关闭,再也不存在任何数据,将引发EOFError异常。 |
conn.send_bytes(buffer [, offset [, size]]) | 通过连接发送字节数据缓冲区,buffer是支持缓冲区接口的任意对象,offset是缓冲区中的字节偏移量,而size是要发送字节数。结果数据以单条消息的形式发出,然后调用c.recv_bytes()函数进行接收。 |
conn1.recv_bytes_into(buffer [, offset]) | 接收一条完整的字节消息,并把它保存在buffer对象中,该对象支持可写入的缓冲区接口(即bytearray对象或类似的对象)。offset指定缓冲区中放置消息处的字节位移。返回值是收到的字节数。如果消息长度大于可用的缓冲区空间,将引发BufferTooShort异常。 |
实例:
from multiprocessing import Process, Pipe
def func(conn):
conn.send("Hello,This is Python!") # 发送
conn.close()
if __name__ == '__main__':
parent_conn, child_conn = Pipe()
p = Process(target=func, args=(child_conn,))
p.start()
print(parent_conn.recv()) # 接收
p.join()
结果:
Hello,This is Python!
Process finished with exit code 0
下面的操作将引发EOFError:
from multiprocessing import Process, Pipe
def func(parent_conn, child_conn):
# parent_conn.close() # 写了close()将引发OSError
while 1:
try:
print(child_conn.recv())
except EOFError:
child_conn.close()
if __name__ == '__main__':
parent_conn, child_conn = Pipe()
p = Process(target=func, args=(parent_conn, child_conn))
p.start()
child_conn.close()
parent_conn.send("Hello,This is Python!")
parent_conn.close()
p.join()
结果:
Hello,This is Python!
from multiprocessing import Manager, Process, Lock
def work(d, lock):
with lock: # 不加锁而操作共享的数据,数据会出乱
d['count'] -= 1
if __name__ == '__main__':
lock = Lock()
with Manager() as m:
dic = m.dict({'count': 100})
p_l = []
for i in range(100):
p = Process(target=work, args=(dic, lock))
p_l.append(p)
p.start()
for p in p_l:
p.join()
print(dic)
结果:(略)
问题:为什么要有进程池?进程池的概念?
在程序实际处理问题过程中,忙时会有成千上万的任务需要被执行,闲时可能只有零星任务。那么在成千上万个任务需要被执行的时候,我们就需要去创建成千上万个进程么?首先,创建进程需要消耗时间,销毁进程也需要消耗时间。第二即便开启了成千上万的进程,操作系统也不能让他们同时执行,这样反而会影响程序的效率。因此我们不能无限制的根据任务开启或者结束进程。那么我们要怎么做呢?
在这里,要给大家介绍一个进程池的概念,定义一个池子,在里面放上固定数量的进程,有需求来了,就拿一个池中的进程来处理任务,等到处理完毕,进程并不关闭,而是将进程再放回进程池中继续等待任务。如果有很多任务需要执行,池中的进程数量不够,任务就要等待之前的进程执行任务完毕归来,拿到空闲进程才能继续执行。也就是说,池中进程的数量是固定的,那么同一时间最多有固定数量的进程在运行。这样不会增加操作系统的调度难度,还节省了开闭进程的时间,也一定程度上能够实现并发效果。
语法:Pool([numprocess [,initializer [, initargs]]]):创建进程池
参数:
- numprocess:要创建的进程数,默认为cpu_count()的值。
- initializer:是每个工作进程启动时要执行的可调用对象,默认为None。
- initargs:是要传给initializer的参数组。
方法 | 说明 |
---|---|
apply(func[, args=()[, kwds={}]]) | 该函数用于传递不定参数,主进程会被阻塞直到函数执行结束(不建议使用,并且3.x以后不在出现)。 |
apply_async(func[, args=()[, kwds={}[, callback=None]]]) | 与apply用法一样,但它是非阻塞且支持结果返回进行回调。 |
map(func, iterable[, chunksize=None]) | Pool类中的map方法,与内置的map函数用法行为基本一致,它会使进程阻塞直到返回结果。 注意,虽然第二个参数是一个迭代器,但在实际使用中,必须在整个队列都就绪后,程序才会运行子进程。 |
close() | 关闭进程池(pool),使其不在接受新的任务。 |
terminate() | 结束工作进程,不在处理未处理的任务。 |
join() | 主进程阻塞等待子进程的退出,join方法必须在close或terminate之后使用。 |
下面演示一些实例:
import time
from multiprocessing import Pool
def run(num):
'''
计算num的num次方
:param num: 数字
:return: num的num次方
'''
time.sleep(1)
return num ** num
if __name__ == '__main__':
lst = [1, 2, 3, 4, 5, 6]
print("顺序:")
t_one = time.time()
for fn in lst:
run(fn)
t_two = time.time()
print("执行时间:", t_two - t_one)
print("多进程:")
pool = Pool(5)
res = pool.map(run, lst)
pool.close()
pool.join()
t_thr = time.time()
print("执行时间:", t_thr - t_two)
print(res)
结果:
顺序:
执行时间: 6.0030517578125
多进程:
执行时间: 2.216660976409912
[1, 4, 27, 256, 3125, 46656]Process finished with exit code 0
上例是一个创建多个进程并发处理与顺序执行处理同一数据,所用时间的差别。从结果可以看出,并发执行的时间明显比顺序执行要快很多,但是进程是要耗资源的,所以平时工作中,进程数也不能开太大。
程序中的res表示全部进程执行结束后全局的返回结果集,run函数有返回值,所以一个进程对应一个返回结果,这个结果存在一个列表中,也就是一个结果堆中,实际上是用了队列的原理,等待所有进程都执行完毕,就返回这个列表(列表的顺序不定)。
对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),让其不再接受新的Process了。
再来分析一个例子:
import time
from multiprocessing import Pool
def run(num):
time.sleep(2)
print(num ** num)
if __name__ == '__main__':
start_time = time.time()
lst = [1, 2, 3, 4, 5, 6]
pool = Pool(10)
pool.map(run, lst)
pool.close()
pool.join()
end_time = time.time()
print("时间:", end_time - start_time)
结果:
42561
466563125
27
时间: 2.455129384994507
Process finished with exit code 0
再运行:
14
27
256
3125
46656
时间: 2.4172260761260986Process finished with exit code 0
问题:结果中为什么还有空行和没有折行的数据呢?
其实这跟进程调度有关,当有多个进程并行执行时,每个进程得到的时间片时间不一样,哪个进程接受哪个请求以及执行完成时间都是不定的,所以会出现输出乱序的情况。那为什么又会有没这行和空行的情况呢?因为有可能在执行第一个进程时,刚要打印换行符时,切换到另一个进程,这样就极有可能两个数字打印到同一行,并且再次切换回第一个进程时会打印一个换行符,所以就会出现空行的情况。
利用Python进行系统管理的时候,特别是同时操作多个文件目录,或者远程控制多台主机,并行操作可以节约大量的时间。当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,这时候进程池Pool发挥作用的时候就到了。
协程,又称微线程,纤程。英文名Coroutine。
线程是系统级别的,它们由操作系统调度。
而协程则是程序级别的由程序根据需要自己调度。在一个线程中会有很多函数,我们把这些函数称为子程序,在子程序执行过程中可以中断去执行别的子程序,而别的子程序也可以中断回来继续执行之前的子程序,这个过程就称为协程。也就是说在同一线程内一段代码在执行过程中会中断然后跳转执行别的代码,接着在之前中断的地方继续开始执行,类似与yield操作。
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
协程的优点:
协程的缺点:
Python中的协程经历了很长的一段发展历程。其大概经历了如下三个阶段:
生产者生产消息后,直接通过yield跳转到消费者开始执行,
待消费者执行完毕后,
切换回生产者继续生产。
#-*- coding:utf8 -*-
def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('[CONSUMER]Consuming %s...' % n)
r = '200 OK'
def producer(c):
# 启动生成器
c.send(None)
n = 0
while n < 5:
n = n + 1
print('[PRODUCER]Producing %s...' % n)
r = c.send(n)
print('[PRODUCER]Consumer return: %s' % r)
c.close()
if __name__ == '__main__':
c = consumer()
producer(c)
send(msg)
与next()
的区别在于:
send
可以传递参数给yield
表达式,这时传递的参数会作为yield
表达式的值,而yield
的参数是返回给调用者的值。换句话说,就是send
可以强行修改上一个yield
表达式的值。上述例子运行之后输出结果如下:
[PRODUCER]Producing 1...
[CONSUMER]Consuming 1...
[PRODUCER]Consumer return: 200 OK
[PRODUCER]Producing 2...
[CONSUMER]Consuming 2...
[PRODUCER]Consumer return: 200 OK
[PRODUCER]Producing 3...
[CONSUMER]Consuming 3...
[PRODUCER]Consumer return: 200 OK
[PRODUCER]Producing 4...
[CONSUMER]Consuming 4...
[PRODUCER]Consumer return: 200 OK
[PRODUCER]Producing 5...
[CONSUMER]Consuming 5...
[PRODUCER]Consumer return: 200 OK
除了Python2.x中协程的实现方式,Python3.x还提供了如下方式实现协程: asyncio + yield from (python3.4+) asyncio + async/await (python3.5+)
Python3.4以后引入了asyncio模块,可以很好的支持协程。
asyncio是Python3.4版本引入的标准库,直接内置了对异步IO的支持。asyncio的异步操作,需要在coroutine中通过yield from
完成。看如下代码(需要在Python3.4以后版本使用):
#-*- coding:utf8 -*-
import asyncio
@asyncio.coroutine
def test(i):
print('test_1', i)
r = yield from asyncio.sleep(1)
print('test_2', i)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
tasks = [test(i) for i in range(3)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
@asyncio.coroutine
把一个generator标记为coroutine类型,然后就把这个coroutine扔到EventLoop中执行。test()会首先打印出test_1,然后yield from
语法可以让我们方便地调用另一个generator。由于asyncio.sleep()
也是一个coroutine,所以线程不会等待asyncio.sleep()
,而是直接中断并执行下一个消息循环。当asyncio.sleep()
返回时,线程就可以从yield from
拿到返回值(此处是None),然后接着执行下一行语句。把asyncio.sleep(1)
看成是一个耗时1秒的IO操作,在此期间主线程并未等待,而是去执行EventLoop中其他可以执行的coroutine了,因此可以实现并发执行。
为了简化并更好地标识异步IO,从Python3.5开始引入了新的语法async和await,可以让coroutine的代码更简洁易读。请注意,async和await是coroutine的新语法,使用新语法只需要做两步简单的替换:
看如下代码(在Python3.5以上版本使用):
#-*- coding:utf8 -*-
import asyncio
async def test(i):
print('test_1', i)
await asyncio.sleep(1)
print('test_2', i)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
tasks = [test(i) for i in range(3)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
运行结果与之前一致。与前一节相比,这里只是把yield from换成了await,@asyncio.coroutine换成了async,其余不变。
Gevent是一个基于Greenlet实现的网络库,通过greenlet实现协程。基本思想是一个greenlet就认为是一个协程,当一个greenlet遇到IO操作的时候,比如访问网络,就会自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO操作。
Greenlet是作为一个C扩展模块,它封装了libevent事件循环的API,可以让开发者在不改变编程习惯的同时,用同步的方式写异步IO的代码。
#-*- coding:utf8 -*-
import gevent
def test(n):
for i in range(n):
print(gevent.getcurrent(), i)
if __name__ == '__main__':
g1 = gevent.spawn(test, 3)
g2 = gevent.spawn(test, 3)
g3 = gevent.spawn(test, 3)
g1.join()
g2.join()
g3.join()
运行结果:
<Greenlet at 0x10a6eea60: test(3)> 0
<Greenlet at 0x10a6eea60: test(3)> 1
<Greenlet at 0x10a6eea60: test(3)> 2
<Greenlet at 0x10a6eed58: test(3)> 0
<Greenlet at 0x10a6eed58: test(3)> 1
<Greenlet at 0x10a6eed58: test(3)> 2
<Greenlet at 0x10a6eedf0: test(3)> 0
<Greenlet at 0x10a6eedf0: test(3)> 1
<Greenlet at 0x10a6eedf0: test(3)> 2
可以看到3个greenlet是依次运行而不是交替运行。要让greenlet交替运行,可以通过gevent.sleep()
交出控制权:
def test(n):
for i in range(n):
print(gevent.getcurrent(), i)
gevent.sleep(1)
运行结果:
<Greenlet at 0x10382da60: test(3)> 0
<Greenlet at 0x10382dd58: test(3)> 0
<Greenlet at 0x10382ddf0: test(3)> 0
<Greenlet at 0x10382da60: test(3)> 1
<Greenlet at 0x10382dd58: test(3)> 1
<Greenlet at 0x10382ddf0: test(3)> 1
<Greenlet at 0x10382da60: test(3)> 2
<Greenlet at 0x10382dd58: test(3)> 2
<Greenlet at 0x10382ddf0: test(3)> 2
当然在实际的代码里,我们不会用gevent.sleep()
去切换协程,而是在执行到IO操作时gevent会自动完成,所以gevent需要将Python自带的一些标准库的运行方式由阻塞式调用变为协作式运行。这一过程在启动时通过monkey patch完成:
#-*- coding:utf8 -*-
from gevent import monkey; monkey.patch_all()
from urllib import request
import gevent
def test(url):
print('Get: %s' % url)
response = request.urlopen(url)
content = response.read().decode('utf8')
print('%d bytes received from %s.' % (len(content), url))
if __name__ == '__main__':
gevent.joinall([
gevent.spawn(test, 'http://httpbin.org/ip'),
gevent.spawn(test, 'http://httpbin.org/uuid'),
gevent.spawn(test, 'http://httpbin.org/user-agent')
])
运行结果:
Get: http://httpbin.org/ip
Get: http://httpbin.org/uuid
Get: http://httpbin.org/user-agent
53 bytes received from http://httpbin.org/uuid.
40 bytes received from http://httpbin.org/user-agent.
31 bytes received from http://httpbin.org/ip.
从结果看,3个网络操作是并发执行的,而且结束顺序不同,但只有一个线程。
至此Python中的协程就介绍完毕了,示例程序中都是以sleep代表异步IO的,在实际项目中可以使用协程异步的读写网络、读写文件、渲染界面等,而在等待协程完成的同时,CPU还可以进行其他的计算,协程的作用正在于此。那么协程和多线程的差异在哪里呢?多线程的切换需要靠操作系统来完成,当线程越来越多时切换的成本会很高,而协程是在一个线程内切换的,切换过程由我们自己控制,因此开销小很多,这就是协程和多线程的根本差异。
闭包(closure)是函数式编程的重要的语法结构。闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性。
当一个内嵌函数引用其外部作作用域的变量,我们就会得到一个闭包. 总结一下,创建一个闭包必须满足以下几点:
其实就是一个匿名函数,为什么叫lambda?因为和后面的函数式编程有关.
推荐: 知乎
这个需要适当的了解一下吧,毕竟函数式编程在Python中也做了引用.
推荐: 酷壳
python中函数式编程支持:
filter 函数的功能相当于过滤器。调用一个布尔函数bool_func
来迭代遍历每个seq中的元素;返回一个使bool_seq
返回值为true的元素的序列。
>>>a = [1,2,3,4,5,6,7]
>>>b = filter(lambda x: x > 5, a)
>>>print b
>>>[6,7]
map函数是对一个序列的每个项依次执行函数,下面是对一个序列每个项都乘以2:
>>> a = map(lambda x:x*2,[1,2,3])
>>> list(a)
[2, 4, 6]
reduce函数是对一个序列的每个项迭代调用函数,下面是求3的阶乘:
>>> reduce(lambda x,y:x*y,range(1,4))
6
引用和copy(),deepcopy()的区别
import copy
a = [1, 2, 3, 4, ['a', 'b']] #原始对象
b = a #赋值,传对象的引用
c = copy.copy(a) #对象拷贝,浅拷贝
d = copy.deepcopy(a) #对象拷贝,深拷贝
a.append(5) #修改对象a
a[4].append('c') #修改对象a中的['a', 'b']数组对象
print 'a = ', a
print 'b = ', b
print 'c = ', c
print 'd = ', d
输出结果:
a = [1, 2, 3, 4, ['a', 'b', 'c'], 5]
b = [1, 2, 3, 4, ['a', 'b', 'c'], 5]
c = [1, 2, 3, 4, ['a', 'b', 'c']]
d = [1, 2, 3, 4, ['a', 'b']]
Python GC主要使用引用计数(reference counting)来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用问题,通过“分代回收”(generation collection)以空间换时间的方法提高垃圾回收效率。
PyObject是每个对象必有的内容,其中ob_refcnt
就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt
就会增加,当引用它的对象被删除,它的ob_refcnt
就会减少.引用计数为0时,该对象生命就结束了。
优点:
缺点:
基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。
分代回收的整体思想是:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。
Python默认定义了三代对象集合,索引数越大,对象存活时间越长。
举例:
当某些内存块M经过了3次垃圾收集的清洗之后还存活时,我们就将内存块M划到一个集合A中去,而新分配的内存都划分到集合B中去。当垃圾收集开始工作时,大多数情况都只对集合B进行垃圾回收,而对集合A进行垃圾回收要隔相当长一段时间后才进行,这就使得垃圾收集机制需要处理的内存少了,效率自然就提高了。在这个过程中,集合B中的某些内存块由于存活时间长而会被转移到集合A中,当然,集合A中实际上也存在一些垃圾,这些垃圾的回收会因为这种分代的机制而被延迟。
推荐: http://www.jianshu.com/p/J4U6rR
is是对比地址,==是对比值
推荐:Python 2.7.x 与 Python 3.x 的主要差异
super() lets you avoid referring to the base class explicitly, which can be nice. But the main advantage comes with multiple inheritance, where all sorts of fun stuff can happen. See the standard docs on super if you haven’t already.
Note that the syntax changed in Python 3.0: you can just say super().__init__
() instead of super(ChildB, self).__init__
() which IMO is quite a bit nicer.
http://stackoverflow.com/questions/576169/understanding-python-super-with-init-methods
Python2.7中的super方法浅见
都在循环时使用,xrange内存性能更好。
for i in range(0, 20):
for i in xrange(0, 20):
__repr__
的目的是明确的
__str__
的目的是可读性
__str__
的用法包含__repr__
1. 算数运算
*
代表乘法
**
代表乘方
>>> 2 * 5
10
>>> 2 ** 5
32
2. 函数形参
*args 和 **kwargs 主要用于函数定义。
并不是必须写成 *args
和 **kwargs
。 *(星号)
才是*必须的***. 你也可以写成 *ar 和 **k
。而写成 *args 和kwargs 只是一个通俗的命名约定。
python函数传递参数的方式有两种:
位置参数(positional argument)
关键词参数(keyword argument)
*args 与 **kwargs 的区别,两者都是 python 中的可变参数:
如果同时使用 *args
和 **kwargs
时,必须 *args
参数列要在 **kwargs
之前。
3. 函数实参
如果函数的形参是定长参数,也可以使用 *args 和 **kwargs 调用函数,类似对元组和字典进行解引用:
>>> def fun(data1, data2, data3):
... print("data1: ", data1)
... print("data2: ", data2)
... print("data3: ", data3)
...
>>> args = ("one", 2, 3)
>>> fun(*args)
data1: one
data2: 2
data3: 3
>>> kwargs = {"data3": "one", "data2": 2, "data1": 3}
>>> fun(**kwargs)
data1: 3
data2: 2
data3: one
4. 序列解包
序列解包 往期博客有写过,这里只列出一个例子,序列解包没有 **。
>>> a, b, *c = 0, 1, 2, 3
>>> a
0
>>> b
1
>>> c
[2, 3]
a. 整型 int、 长整型 long、浮点型 float、 复数 complex
b. 字符串 str、 列表 list、 元祖 tuple
c. 字典 dict 、 集合 set
d. Python3 中没有 long,只有无限精度的 int
__doc__
表示类的描述信息__module__
和 __class__
__module__
表示当前操作的对象在那个模块
__class__
表示当前操作的对象的类是什么
__init__
构造方法,通过类创建对象时,自动触发执行。
__del__
__call__
__dict__
class Province:
country = 'China'
def __init__(self, name, count):
self.name = name
self.count = count
def func(self, *args, **kwargs):
print 'func'
# 获取类的成员,即:静态字段、方法、
print Province.__dict__
# 输出:{'country': 'China', '__module__': '__main__', 'func': , '__init__': , '__doc__': None}
obj1 = Province('HeBei',10000)
print obj1.__dict__
# 获取 对象obj1 的成员
# 输出:{'count': 10000, 'name': 'HeBei'}
obj2 = Province('HeNan', 3888)
print obj2.__dict__
# 获取 对象obj1 的成员
# 输出:{'count': 3888, 'name': 'HeNan'}
__str__
class Foo:
def __str__(self):
return 'wupeiqi'
obj = Foo()
print obj
# 输出:wupeiqi
__getitem__
、__setitem__
、__delitem__
用于索引操作,如字典。以上分别表示获取、设置、删除数据
#!/usr/bin/env python
# -*- coding:utf-8 -*-
class Foo(object):
def __getitem__(self, key):
print '__getitem__',key
def __setitem__(self, key, value):
print '__setitem__',key,value
def __delitem__(self, key):
print '__delitem__',key
obj = Foo()
result = obj['k1'] # 自动触发执行 __getitem__
obj['k2'] = 'wupeiqi' # 自动触发执行 __setitem__
del obj['k1'] # 自动触发执行 __delitem__
__getslice__
、__setslice__
、__delslice__
该三个方法用于分片操作,如:列表
#!/usr/bin/env python
# -*- coding:utf-8 -*-
class Foo(object):
def __getslice__(self, i, j):
print '__getslice__',i,j
def __setslice__(self, i, j, sequence):
print '__setslice__',i,j
def __delslice__(self, i, j):
print '__delslice__',i,j
obj = Foo()
obj[-1:1] # 自动触发执行 __getslice__
obj[0:1] = [11,22,33,44] # 自动触发执行 __setslice__
del obj[0:2] # 自动触发执行 __delslice__
__iter__
用于迭代器,之所以列表、字典、元组可以进行for循环,是因为类型内部定义了 iter
class Foo(object):
pass
obj = Foo()
for i in obj:
print i
# 报错:TypeError: 'Foo' object is not iterable
#!/usr/bin/env python
# -*- coding:utf-8 -*-
class Foo(object):
def __iter__(self):
pass
obj = Foo()
for i in obj:
print i
# 报错:TypeError: iter() returned non-iterator of type 'NoneType'
#!/usr/bin/env python
# -*- coding:utf-8 -*-
class Foo(object):
def __init__(self, sq):
self.sq = sq
def __iter__(self):
return iter(self.sq)
obj = Foo([11,22,33,44])
for i in obj:
print i
__contains__
class NoisyString(str):
def __contains__(self, other):
print('testing if "{0}" in "{1}"'.format(other, self))
return super(NoisyString, self).__contains__(other)
ns = NoisyString('a string with a substring inside')
>>> 'substring' in ns
testing if "substring" in "a string with a substring inside"
True
__lt__
,__le__
,__eq__
,__ne__
,__gt__
,__ge__
__new__
和 __metaclass__
阅读以下代码:
class Foo(object):
def __init__(self):
pass
obj = Foo() # obj是通过Foo类实例化的对象
print type(obj) # 输出:
表示,obj 对象由Foo类创建print type(Foo) # 输出:
表示,Foo类对象由 type 类创建所以,obj对象是Foo类的一个实例,Foo类对象是 type 类的一个实例,即:Foo类对象 是通过type类的构造方法创建。
那么,创建类就可以有两种方式:
a). 普通方式
class Foo(object):
def func(self):
print 'hello wupeiqi'
b).特殊方式(type类的构造函数)
def func(self):
print 'hello wupeiqi'
Foo = type('Foo',(object,), {'func': func})
#type第一个参数:类名
#type第二个参数:当前类的基类
#type第三个参数:类的成员
==》 类 是由 type 类实例化产生
答:类中有一个属性 metaclass,其用来表示该类由 谁 来实例化创建,所以,我们可以为 __metaclass__
设置一个type类的派生类,从而查看 类 创建的过程。
class MyType(type):
def __init__(self, what, bases=None, dict=None):
super(MyType, self).__init__(what, bases, dict)
def __call__(self, *args, **kwargs):
obj = self.__new__(self, *args, **kwargs)
self.__init__(obj)
class Foo(object):
__metaclass__ = MyType
def __init__(self, name):
self.name = name
def __new__(cls, *args, **kwargs):
return object.__new__(cls, *args, **kwargs)
# 第一阶段:解释器从上到下执行代码创建Foo类
# 第二阶段:通过Foo类创建obj对象
obj = Foo()
Python中对象分两种
__new__
、 __init__
、以及__call__
的执行顺序
__new__
__init__
__call__