重庆分公司,新征程启航

为企业提供网站建设、域名注册、服务器等服务

pythonfoo函数,pythonfor

面试必备 - Python 垃圾回收机制

众所周知,Python 是一门面向对象语言,在 Python 的世界一切皆对象。所以一切变量的本质都是对象的一个指针而已。

创新互联公司专业为企业提供宝山网站建设、宝山做网站、宝山网站设计、宝山网站制作等企业网站建设、网页设计与制作、宝山企业网站模板建站服务,十多年宝山做网站经验,不只是建网站,更提供有价值的思路和整体网络服务。

Python 运行过程中会不停的创建各种变量,而这些变量是需要存储在内存中的,随着程序的不断运行,变量数量越来越多,所占用的空间势必越来越大,如果对变量所占用的内存空间管理不当的话,那么肯定会出现 out of memory。程序大概率会被异常终止。

因此,对于内存空间的有效合理管理变得尤为重要,那么 Python 是怎么解决这个问题的呢。其实很简单,对不不可能再使用到的内存进行回收即可,像 C 语言中需要程序员手动释放内存就是这个道理。但问题是如何确定哪些内存不再会被使用到呢?这就是我们今天要说的垃圾回收了。

目前垃圾回收比较通用的解决办法有三种,引用计数,标记清除以及分代回收。

引用计数也是一种最直观,最简单的垃圾收集技术。在 Python 中,大多数对象的生命周期都是通过对象的引用计数来管理的。其原理非常简单,我们为每个对象维护一个 ref 的字段用来记录对象被引用的次数,每当对象被创建或者被引用时将该对象的引用次数加一,当对象的引用被销毁时该对象的引用次数减一,当对象的引用次数减到零时说明程序中已经没有任何对象持有该对象的引用,换言之就是在以后的程序运行中不会再次使用到该对象了,那么其所占用的空间也就可以被释放了了。

我们来看看下面的例子。

函数 print_memory_info 用来获取程序占用的内存空间大小,在 foo 函数中创建一个包含一百万个整数的列表。从打印结果我们可以看出,创建完列表之后程序耗用的内存空间上升到了 55 MB。而当函数 foo 调用完毕之后内存消耗又恢复正常。

这是因为我们在函数 foo 中创建的 list 变量是局部变量,其作用域是当前函数内部,一旦函数执行完毕,局部变量的引用会被自动销毁,即其引用次数会变为零,所占用的内存空间也会被回收。

为了验证我们的想法,我们对函数 foo 稍加改造。代码如下:

稍加改造之后,即使 foo 函数调用结束其所消耗的内存也未被释放。

主要是因为我们将函数 foo 内部产生的列表返回并在主程序中接收之后,这样就会导致该列表的引用依然存在,该对象后续仍有可能被使用到,垃圾回收便不会回收该对象。

那么,什么时候对象的引用次数才会增加呢。下面四种情况都会导致对象引用次数加一。

同理,对象引用次数减一的情况也有四种。

引用计数看起来非常简单,实现起来也不复杂,只需要维护一个字段保存对象被引用的次数即可,那么是不是就代表这种算法没有缺点了呢。实则不然,我们知道引用次数为零的对象所占用的内存空间肯定是需要被回收的。那引用次数不为零的对象呢,是不是就一定不能回收呢?

我们来看看下面的例子,只是对函数 foo 进行了改造,其余未做更改。

我们看到,在函数 foo 内部生成了两个列表 list_a 和 list_b,然后将两个列表分别添加到另外一个中。由结果可以看出,即使 foo 函数结束之后其所占用的内存空间依然未被释放。这是因为对于 list_a 和 list_b 来说虽然没有被任何外部对象引用,但因为二者之间交叉引用,以至于每个对象的引用计数都不为零,这也就造成了其所占用的空间永远不会被回收的尴尬局面。这个缺点是致命的。

为了解决交叉引用的问题,Python 引入了标记清除算法和分代回收算法。

显然,可以包含其他对象引用的容器对象都有可能产生交叉引用问题,而标记清除算法就是为了解决交叉引用的问题的。

标记清除算法是一种基于对象可达性分析的回收算法,该算法分为两个步骤,分别是标记和清除。标记阶段,将所有活动对象进行标记,清除阶段将所有未进行标记的对象进行回收即可。那么现在的为问题变为了 GC 是如何判定哪些是活动对象的?

事实上 GC 会从根结点出发,与根结点直接相连或者间接相连的对象我们将其标记为活动对象(该对象可达),之后进行回收阶段,将未标记的对象(不可达对象)进行清除。前面所说的根结点可以是全局变量,也可以是调用栈。

标记清除算法主要用来处理一些容器对象,虽说该方法完全可以做到不误杀不遗漏,但 GC 时必须扫描整个堆内存,即使只有少量的非可达对象需要回收也需要扫描全部对象。这是一种巨大的性能浪费。

由于标记清除算法需要扫描整个堆的所有对象导致其性能有所损耗,而且当可以回收的对象越少时性能损耗越高。因此 Python 引入了分代回收算法,将系统中存活时间不同的对象划分到不同的内存区域,共三代,分别是 0 代,1 代 和 2 代。新生成的对象是 0 代,经过一次垃圾回收之后,还存活的对象将会升级到 1 代,以此类推,2 代中的对象是存活最久的对象。

那么什么时候触发进行垃圾回收算法呢。事实上随着程序的运行会不断的创建新的对象,同时也会因为引用计数为零而销毁大部分对象,Python 会保持对这些对象的跟踪,由于交叉引用的存在,以及程序中使用了长时间存活的对象,这就造成了新生成的对象的数量会大于被回收的对象数量,一旦二者之间的差值达到某个阈值就会启动垃圾回收机制,使用标记清除算法将死亡对象进行清除,同时将存活对象移动到 1 代。 以此类推,当二者的差值再次达到阈值时又触发垃圾回收机制,将存活对象移动到 2 代。

这样通过对不同代的阈值做不同的设置,就可以做到在不同代使用不同的时间间隔进行垃圾回收,以追求性能最大。

事实上,所有的程序都有一个相识的现象,那就是大部分的对象生存周期都是相当短的,只有少量对象生命周期比较长,甚至会常驻内存,从程序开始运行持续到程序结束。而通过分代回收算法,做到了针对不同的区域采取不同的回收频率,节约了大量的计算从而提高 Python 的性能。

除了上面所说的差值达到一定阈值会触发垃圾回收之外,我们还可以显示的调用 gc.collect() 来触发垃圾回收,最后当程序退出时也会进行垃圾回收。

本文介绍了 Python 的垃圾回收机制,垃圾回收是 Python 自带的功能,并不需要程序员去手动管理内存。

其中引用计数法是最简单直接的,但是需要维护一个字段且针对交叉引用无能为力。

标记清除算法主要是为了解决引用计数的交叉引用问题,该算法的缺点就是需要扫描整个堆的所有对象,有点浪费性能。

而分代回收算法的引入则完美解决了标记清除算法需要扫描整个堆对象的性能浪费问题。该算法也是建立在标记清除基础之上的。

最后我们可以通过 gc.collect() 手动触发 GC 的操作。

题外话,如果你看过 JVM 的垃圾回收算法之后会发现 Python 的垃圾回收算法与其是如出一辙的,事实再次证明,程序语言设计时是会相互参考的。

Python 中经常看到 foo, 请问foo是什么意思呢?

一般来说是一个函数名或者是一个变量,没有什么特别的意思,大家都这么写。就像说某人时会叫他张三李四是一样的...

python装饰器是什么意思

装饰器是程序开发中经常会用到的一个功能,用好了装饰器,开发效率如虎添翼,所以这也是Python面试中必问的问题,但对于好多小白来讲,这个功能 有点绕,自学时直接绕过去了,然后面试问到了就挂了,因为装饰器是程序开发的基础知识,这个都 不会,别跟人家说你会Python, 看了下面的文章,保证你学会装饰器。

1、先明白这段代码

#### 第一波 ####

def foo():

print 'foo'

foo     #表示是函数

foo()   #表示执行foo函数

#### 第二波 ####

def foo():

print 'foo'

foo = lambda x: x + 1

foo()   # 执行下面的lambda表达式,而不再是原来的foo函数,因为函数 foo 被重新定义了

2、需求来了

初创公司有N个业务部门,1个基础平台部门,基础平台负责提供底层的功能,如:数据库操作、redis调用、监控API等功能。业务部门使用基础功能时,只需调用基础平台提供的功能即可。如下:

############### 基础平台提供的功能如下 ###############

def f1():

print 'f1'

def f2():

print 'f2'

def f3():

print 'f3'

def f4():

print 'f4'

############### 业务部门A 调用基础平台提供的功能 ###############

f1()

f2()

f3()

f4()

############### 业务部门B 调用基础平台提供的功能 ###############

f1()

f2()

f3()

f4()

目前公司有条不紊的进行着,但是,以前基础平台的开发人员在写代码时候没有关注验证相关的问题,即:基础平台的提供的功能可以被任何人使用。现在需要对基础平台的所有功能进行重构,为平台提供的所有功能添加验证机制,即:执行功能前,先进行验证。

老大把工作交给 Low B,他是这么做的:

跟每个业务部门交涉,每个业务部门自己写代码,调用基础平台的功能之前先验证。诶,这样一来基础平台就不需要做任何修改了。

当天Low B 被开除了…

老大把工作交给 Low BB,他是这么做的:

############### 基础平台提供的功能如下 ###############

def f1():

# 验证1

# 验证2

# 验证3

print 'f1'

def f2():

# 验证1

# 验证2

# 验证3

print 'f2'

def f3():

# 验证1

# 验证2

# 验证3

print 'f3'

def f4():

# 验证1

# 验证2

# 验证3

print 'f4'

############### 业务部门不变 ###############

### 业务部门A 调用基础平台提供的功能###

f1()

f2()

f3()

f4()

### 业务部门B 调用基础平台提供的功能 ###

f1()

f2()

f3()

f4()

过了一周 Low BB 被开除了…

老大把工作交给 Low BBB,他是这么做的:

只对基础平台的代码进行重构,其他业务部门无需做任何修改

############### 基础平台提供的功能如下 ###############

def check_login():

# 验证1

# 验证2

# 验证3

pass

def f1():

check_login()

print 'f1'

def f2():

check_login()

print 'f2'

def f3():

check_login()

print 'f3'

def f4():

check_login()

print 'f4'

老大看了下Low BBB 的实现,嘴角漏出了一丝的欣慰的笑,语重心长的跟Low BBB聊了个天:

老大说:

写代码要遵循开发封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:

封闭:已实现的功能代码块

开放:对扩展开发

如果将开放封闭原则应用在上述需求中,那么就不允许在函数 f1 、f2、f3、f4的内部进行修改代码,老板就给了Low BBB一个实现方案:

def w1(func):

def inner():

# 验证1

# 验证2

# 验证3

return func()

return inner

@w1

def f1():

print 'f1'

@w1

def f2():

print 'f2'

@w1

def f3():

print 'f3'

@w1

def f4():

print 'f4'

对于上述代码,也是仅仅对基础平台的代码进行修改,就可以实现在其他人调用函数 f1 f2 f3 f4 之前都进行【验证】操作,并且其他业务部门无需做任何操作。

Low BBB心惊胆战的问了下,这段代码的内部执行原理是什么呢?

老大正要生气,突然Low BBB的手机掉到地上,恰恰屏保就是Low BBB的女友照片,老大一看一紧一抖,喜笑颜开,交定了Low BBB这个朋友。详细的开始讲解了:

单独以f1为例:

def w1(func):

def inner():

# 验证1

# 验证2

# 验证3

return func()

return inner

@w1

def f1():

print 'f1'

当写完这段代码后(函数未被执行、未被执行、未被执行),python解释器就会从上到下解释代码,步骤如下:

def w1(func):  ==将w1函数加载到内存

@w1

没错,从表面上看解释器仅仅会解释这两句代码,因为函数在没有被调用之前其内部代码不会被执行。

从表面上看解释器着实会执行这两句,但是 @w1 这一句代码里却有大文章,@函数名 是python的一种语法糖。

如上例@w1内部会执行一下操作:

执行w1函数,并将 @w1 下面的 函数 作为w1函数的参数,即:@w1 等价于 w1(f1)

所以,内部就会去执行:

def inner:

#验证

return f1()   # func是参数,此时 func 等于 f1

return inner     # 返回的 inner,inner代表的是函数,非执行函数

其实就是将原来的 f1 函数塞进另外一个函数中

将执行完的 w1 函数返回值赋值给@w1下面的函数的函数名

w1函数的返回值是:

def inner:

#验证

return 原来f1()  # 此处的 f1 表示原来的f1函数

然后,将此返回值再重新赋值给 f1,即:

新f1 = def inner:

#验证

return 原来f1()

所以,以后业务部门想要执行 f1 函数时,就会执行 新f1 函数,在 新f1 函数内部先执行验证,再执行原来的f1函数,然后将 原来f1 函数的返回值 返回给了业务调用者。

如此一来, 即执行了验证的功能,又执行了原来f1函数的内容,并将原f1函数返回值 返回给业务调用着

Low BBB 你明白了吗?要是没明白的话,我晚上去你家帮你解决吧!!!

先把上述流程看懂,之后还会继续更新…

3、问答时间

问题:被装饰的函数如果有参数呢?

#一个参数

def w1(func):

def inner(arg):

# 验证1

# 验证2

# 验证3

return func(arg)

return inner

@w1

def f1(arg):

print 'f1'

#两个参数

def w1(func):

def inner(arg1,arg2):

# 验证1

# 验证2

# 验证3

return func(arg1,arg2)

return inner

@w1

def f1(arg1,arg2):

print 'f1'

#三个参数

def w1(func):

def inner(arg1,arg2,arg3):

# 验证1

# 验证2

# 验证3

return func(arg1,arg2,arg3)

return inner

@w1

def f1(arg1,arg2,arg3):

print 'f1'

问题:可以装饰具有处理n个参数的函数的装饰器?

def w1(func):

def inner(*args,**kwargs):

# 验证1

# 验证2

# 验证3

return func(*args,**kwargs)

return inner

@w1

def f1(arg1,arg2,arg3):

print 'f1'

问题:一个函数可以被多个装饰器装饰吗?

def w1(func):

def inner(*args,**kwargs):

# 验证1

# 验证2

# 验证3

return func(*args,**kwargs)

return inner

def w2(func):

def inner(*args,**kwargs):

# 验证1

# 验证2

# 验证3

return func(*args,**kwargs)

return inner

@w1

@w2

def f1(arg1,arg2,arg3):

print 'f1'

问题:还有什么更吊的装饰器吗?

#!/usr/bin/env python

#coding:utf-8

def Before(request,kargs):

print 'before'

def After(request,kargs):

print 'after'

def Filter(before_func,after_func):

def outer(main_func):

def wrapper(request,kargs):

before_result = before_func(request,kargs)

if(before_result != None):

return before_result;

main_result = main_func(request,kargs)

if(main_result != None):

return main_result;

after_result = after_func(request,kargs)

if(after_result != None):

return after_result;

return wrapper

return outer

@Filter(Before, After)

def Index(request,kargs):

print 'index'


网站名称:pythonfoo函数,pythonfor
网页路径:http://cqcxhl.cn/article/hesjji.html

其他资讯

在线咨询
服务热线
服务热线:028-86922220
TOP