Python基础与深入(四)

1.GIL线程全局锁

Python代码的执行由Python 虚拟机(也叫解释器主循环,CPython版本)来控制,Python 在设计之初就考虑到要在解释器的主循环中,同时只有一个线程在执行,即在任意时刻,只有一个线程在解释器中运行。对Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。线程的执行速度非常之快,会让你误以为线程是并行执行的(并行),但是实际上都是轮流执行(并发或者串行)。对于io密集型任务,python的多线程起到作用,但对于cpu密集型任务,python的多线程几乎占不到任何优势,还有可能因为争夺资源而变慢。
在多线程环境中,Python 虚拟机按以下方式执行:

  1. 设置GIL
  2. 切换到一个线程去运行
  3. 运行:
    a. 指定数量的字节码指令,或者
    b. 线程主动让出控制(可以调用time.sleep(0))
  4. 把线程设置为睡眠状态
  5. 解锁GIL
  6. 再次重复以上所有步骤
    在调用外部代码(如C/C++扩展函数)的时候,GIL 将会被锁定,直到这个函数结束为止(由于在这期间没有Python 的字节码被运行,所以不会做线程切换)。
    join方法 子进程结束切换到父进程
    多进程应该避免共享资源。在多线程中,我们可以比较容易地共享资源,比如使用全局变量或者传递参数。在多进程情况下,由于每个进程有自己独立的内存空间,以上方法并不合适。此时我们可以通过共享内存和Manager的方法来共享资源。但这样做提高了程序的复杂度,并因为同步的需要而降低了程序的效率。

2.Python函数式编程

python中函数式编程支持:
filter 函数的功能相当于过滤器。调用一个布尔函数bool_func来迭代遍历每个seq中的元素;返回一个使bool_seq返回值为true的元素的序列。

1
2
3
4
>>>a = [1,2,3,4,5,6,7]
>>>b = filter(lambda x: x > 5, a)
>>>print b
>>>[6,7]

map函数是对一个序列的每个项依次执行函数,下面是对一个序列每个项都乘以2:

1
2
3
>>> a = map(lambda x:x*2,[1,2,3])
>>> list(a)
[2, 4, 6]

reduce函数是对一个序列的每个项迭代调用函数,下面是求3的阶乘:

1
2
>>> reduce(lambda x,y:x*y,range(1,4))
6

filter函数用于对序列的每个项进行迭代,不满足条件的就会被踢出序列

1
2
>>> filter(lambda x>0:x,range(-10,4))
[1,2,3]

补充:用filter求素数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def _odd_iter():
n = 1
while True:
n = n + 2
yield n
def _not_divisible(n):
return lambda x: x % n > 0
def primes():
yield 2
it = _odd_iter() # 初始序列
while True:
n = next(it) # 返回序列的第一个数
yield n
it = filter(_not_divisible(n), it) # 构造新序列
# 打印1000以内的素数:
for n in primes():
if n < 1000:
print(n)
else:
break

3.Python里的拷贝

引用和copy(),deepcopy()的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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']]

切片的对象,切片是浅拷贝还是深拷贝
切片的对象有list,tuple,str
“=”,copy,slice属于浅拷贝
deepcopy就属于深拷贝
浅拷贝和深拷贝的区别在于深拷贝是被复制的对象作为一个新的个体独立存在,
任意改变其一不会对另一个对象造成影响,而浅拷贝不会产生新的独立对象

4.Python是如何进行内存管理的

(http://developer.51cto.com/art/201007/213585.html)

答:从三个方面来说,一对象的引用计数机制,二垃圾回收机制,三内存池机制

  • 1、对象的引用计数机制

Python内部使用引用计数,来保持追踪内存中的对象,所有对象都有引用计数。

引用计数增加的情况:

1,一个对象分配一个新名称

2,将其放入一个容器中(如列表、元组或字典)

引用计数减少的情况:

1,使用del语句对对象别名显示的销毁

2,引用超出作用域或被重新赋值

sys.getrefcount( )函数可以获得对象的当前引用计数

多数情况下,引用计数比你猜测得要大得多。对于不可变数据(如数字和字符串),解释器会在程序的不同部分共享内存,以便节约内存。

  • 2、垃圾回收

1,当一个对象的引用计数归零时,它将被垃圾收集机制处理掉。

2,当两个对象a和b相互引用时,del语句可以减少a和b的引用计数,并销毁用于引用底层对象的名称。然而由于每个对象都包含一个对其他对象的应用,

因此引用计数不会归零,对象也不会销毁。(从而导致内存泄露)。为解决这一问题,解释器会定期执行一个循环检测器,搜索不可访问对象的循环并删除它们。

  • 3、内存池机制

Python提供了对内存的垃圾收集机制,但是它将不用的内存放到内存池而不是返回给操作系统。

1,Pymalloc机制。为了加速Python的执行效率,Python引入了一个内存池机制,用于管理对小块内存的申请和释放。

2,Python中所有小于256个字节的对象都使用pymalloc实现的分配器,而大的对象则使用系统的malloc。

3,对于Python对象,如整数,浮点数和List,都有其独立的私有内存池,对象间不共享他们的内存池。也就是说如果你分配又释放了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数。

5.Python的is和==

is是对比地址,==是对比值
is 比较的是两个实例对象是不是完全相同,is关键字是查看两个对象是否相同的唯一标准,他们是不是同一个对象,
占用的内存地址是否相同,即比较他们的id
== 比较的是两个实例对象的内容是否相同,即他们的内存地址可以不同,默认调用eq方法
切片slice is->False ==->True
序列直接复制= is->True
大量创建相同内容的字符串,is->True
小整数对象[-5,256]在全局解释器范围内被放入缓存共重复使用
a=1 b=1 a is b ->True ==->True
a=257 b=257 a is b ->True ==->True

is运算符比==效率高,在变量和None进行比较时,应该使用is

6.read,readline和readlines

  • read 读取整个文件
  • readline 读取下一行,使用生成器方法
  • readlines 读取整个文件到一个迭代器以供我们遍历

7.Python2和3的区别

推荐:Python 2.7.x 与 Python 3.x 的主要差异

补充:Python的编码问题

python3 bytes和str都可以代表字符序列,前者的实例包含原始的8位值;bytes.decode(‘utf-8’)
后者的实例包含unicode字符 str.encode(‘utf-8’)

python2 str和Unicode都可以代表字符序列,与python3不同的是,str的实例包含原始的8位值,str.decode(‘utf-8’)
而Unicode的实例则包含unicode字符,unicode.encode(‘utf-8’)

python2 str <--> python3 bytes
python2 unicode <--> python3 str

python2 str.decode(‘utf-8’)–>unicode
unicode.encode(‘utf-8’)–>str
python3 bytes.decode(‘utf-8’)–>str
str.encode(‘utf-8’) –>bytes

8.协程、线程和进程间的区别

进程是资源分配和拥有的单位,
进程包含多个线程,线程是指进程内的一个执行单元
一个线程可以有多个协程,一个进程也可以单独拥有多个协程
线程进程都是同步机制,而协程则是异步
IO密集型一般使用多线程或者多进程,CPU密集型一般使用多进程,强调非阻塞异步并发的一般都是使用协程,
当然有时候也是需要多进程线程池结合的,或者是其他组合方式。

进程是应用程序执行的基本单元,应用执行可以开多个进程multiprocessing模块
线程是进程内的一个基本执行单元,每个进程可以开多个线程multithread.Thread类
协程也叫微线程,一个线程可以包含多个协程gevent from gevent.pool import Pool

坚持原创技术分享,您的支持将鼓励我继续创作!