1.元类
1.1.1类也是对象
在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在Python中这一点仍然成立:
>>>classObjectCreator(object):
…pass
…
>>>my_object = ObjectCreator()
>>>printmy_object
<__main__.ObjectCreator object at0x8974f2c>
但是,Python中的类还远不止如此。类同样也是一种对象。是的,没错,就是对象。只要你使用关键字class,Python解释器在执行的时候就会创建一个对象。
下面的代码段:
>>>classObjectCreator(object):
…pass
…
将在内存中创建一个对象,名字就是ObjectCreator。这个对象(类对象ObjectCreator)拥有创建对象(实例对象)的能力。但是,它的本质仍然是一个对象,于是乎你可以对它做如下的操作:
1.你可以将它赋值给一个变量
2.你可以拷贝它
3.你可以为它增加属性
4.你可以将它作为函数参数进行传递
下面是示例:
>>> print ObjectCreator #你可以打印一个类,因为它其实也是一个对象
>>> def echo(o):
… print o
…
>>> echo(ObjectCreator) #你可以将类做为参数传给函数
>>> print hasattr(ObjectCreator, 'new_attribute')
Fasle
>>> ObjectCreator.new_attribute = 'foo' #你可以为类增加属性
>>> print hasattr(ObjectCreator, 'new_attribute')
True
>>> print ObjectCreator.new_attribute
foo
>>> ObjectCreatorMirror = ObjectCreator #你可以将类赋值给一个变量
>>> print ObjectCreatorMirror()
<__main__.ObjectCreator object at 0x8997b4c>
1.1.2动态的创建类
因为类也是对象,你可以在运行时动态的创建它们,就像其他任何对象一样。首先,你可以在函数中创建类,使用class关键字即可。
>>> def choose_class(name):
… if name == 'foo':
… class Foo(object):
… pass
… return Foo #返回的是类,不是类的实例
… else:
… class Bar(object):
… pass
… return Bar
…
>>> MyClass = choose_class('foo')
>>> print MyClass #函数返回的是类,不是类的实例
>>> print MyClass() #你可以通过这个类创建类实例,也就是对象
<__main__.Foo object at 0x89c6d4c>
但这还不够动态,因为你仍然需要自己编写整个类的代码。由于类也是对象,所以它们必须是通过什么东西来生成的才对。当你使用class关键字时,Python解释器自动创建这个对象。但就和Python中的大多数事情一样,Python仍然提供给你手动处理的方法。
还记得内建函数type吗?这个古老但强大的函数能够让你知道一个对象的类型是什么,就像这样:
>>> print type(1) #数值的类型
>>> print type("1") #字符串的类型
>>> print type(ObjectCreator()) #实例对象的类型
>>> print type(ObjectCreator) #类的类型
仔细观察上面的运行结果,发现使用type对ObjectCreator查看类型是,答案为type, 是不是有些惊讶。。。看下面
1.1.3使用type创建类
type还有一种完全不同的功能,动态的创建类。
type可以接受一个类的描述作为参数,然后返回一个类。(要知道,根据传入参数的不同,同一个函数拥有两种完全不同的用法是一件很傻的事情,但这在Python中是为了保持向后兼容性)
type可以像这样工作:
type(类名,由父类名称组成的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
比如下面的代码:
In [2]:classTest:#定义了一个Test类
...:pass
...:
In [3]: Test()#创建了一个Test类的实例对象
Out[3]: <__main__.Test at0x10d3f8438>
可以手动像这样创建:
Test2 = type("Test2",(),{})#定了一个Test2类
In [5]: Test2()#创建了一个Test2类的实例对象
Out[5]: <__main__.Test2 at0x10d406b38>
我们使用"Test2"作为类名,并且也可以把它当做一个变量来作为类的引用。类和变量是不同的,这里没有任何理由把事情弄的复杂。即type函数中第1个实参,也可以叫做其他的名字,这个名字表示类的名字
In [23]: MyDogClass = type('MyDog', (), {})
In [24]: print MyDogClass
使用help来测试这2个类
In [10]: help(Test) #用help查看Test类
Help on class Test in module __main__:
class Test(builtins.object)
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
In [8]: help(Test2) #用help查看Test2类
Help on class Test2 in module __main__:
class Test2(builtins.object)
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
1.1.4使用type创建带有属性的类
type接受一个字典来为类定义属性,因此
>>>Foo = type('Foo', (), {'bar':True})
可以翻译为:
>>>classFoo(object):
… bar =True
并且可以将Foo当成一个普通的类一样使用:
>>> print Foo
>>> print Foo.bar
True
>>> f = Foo()
>>> print f
<__main__.Foo object at 0x8a9b84c>
>>> print f.bar
True
当然,你可以向这个类继承,所以,如下的代码:
>>>classFooChild(Foo):
…pass
就可以写成:
>>> FooChild = type('FooChild', (Foo,),{})
>>> print FooChild
>>> print FooChild.bar # bar属性是由Foo继承而来
True
注意:
·type的第2个参数,元组中是父类的名字,而不是字符串
·添加的属性是类属性,并不是实例属性
1.1.5使用type创建带有方法的类
最终你会希望为你的类增加方法。只需要定义一个有着恰当签名的函数并将其作为属性赋值就可以了。
添加实例方法
In [46]:defecho_bar(self):#定义了一个普通的函数
...: print(self.bar)
...:
In [47]: FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})#让FooChild类中的echo_bar属性,指向了上面定义的函数
In [48]: hasattr(Foo,'echo_bar')#判断Foo类中,是否有echo_bar这个属性
Out[48]:False
In [49]:
In [49]: hasattr(FooChild,'echo_bar')#判断FooChild类中,是否有echo_bar这个属性
Out[49]:True
In [50]: my_foo = FooChild()
In [51]: my_foo.echo_bar()
True
添加静态方法
In [36]: @staticmethod
...:deftestStatic():
...: print("static method ....")
...:
In [37]: Foochild = type('Foochild', (Foo,), {"echo_bar":echo_bar,"testStatic":
...: testStatic})
In [38]: fooclid = Foochild()
In [39]: fooclid.testStatic
Out[39]:
In [40]: fooclid.testStatic()
static method ....
In [41]: fooclid.echo_bar()
True
添加类方法
In [42]: @classmethod
...:deftestClass(cls):
...: print(cls.bar)
...:
In [43]:
In [43]: Foochild = type('Foochild', (Foo,), {"echo_bar":echo_bar,"testStatic":
...: testStatic,"testClass":testClass})
In [44]:
In [44]: fooclid = Foochild()
In [45]: fooclid.testClass()
True
你可以看到,在Python中,类也是对象,你可以动态的创建类。这就是当你使用关键字class时Python在幕后做的事情,而这就是通过元类来实现的。
1.1.6到底什么是元类
元类就是用来创建类的“东西”。你创建类就是为了创建类的实例对象,不是吗?但是我们已经学习到了Python中的类也是对象。
元类就是用来创建这些类(对象)的,元类就是类的类,你可以这样理解为:
MyClass = MetaClass()#使用元类创建出一个对象,这个对象称为“类”
MyObject = MyClass()#使用“类”来创建出实例对象
你已经看到了type可以让你像这样做:
MyClass = type('MyClass', (), {})
这是因为函数type实际上是一个元类。type就是Python在背后用来创建所有类的元类。现在你想知道那为什么type会全部采用小写形式而不是Type呢?好吧,我猜这是为了和str保持一致性,str是用来创建字符串对象的类,而int是用来创建整数对象的类。type就是创建类对象的类。你可以通过检查__class__属性来看到这一点。Python中所有的东西,注意,我是指所有的东西——都是对象。这包括整数、字符串、函数以及类。它们全部都是对象,而且它们都是从一个类创建而来,这个类就是type。
>>> age = 35
>>> age.__class__
>>> name = 'bob'
>>> name.__class__
>>> def foo(): pass
>>>foo.__class__
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
现在,对于任何一个__class__的__class__属性又是什么呢?
>>>a.__class__.__class__
>>>age.__class__.__class__
>>>foo.__class__.__class__
>>>b.__class__.__class__
因此,元类就是创建类这种对象的东西。type就是Python的内建元类,当然了,你也可以创建自己的元类。
1.9.7 __metaclass__属性
你可以在定义一个类的时候为其添加__metaclass__属性。
classFoo(object):
__metaclass__ = something…
...省略...
如果你这么做了,Python就会用元类来创建类Foo。小心点,这里面有些技巧。你首先写下class Foo(object),但是类Foo还没有在内存中创建。Python会在类的定义中寻找__metaclass__属性,如果找到了,Python就会用它来创建类Foo,如果没有找到,就会用内建的type来创建这个类。把下面这段话反复读几次。当你写如下代码时:
classFoo(Bar):
pass
Python做了如下的操作:
1.Foo中有__metaclass__这个属性吗?如果是,Python会通过__metaclass__创建一个名字为Foo的类(对象)
2.如果Python没有找到__metaclass__,它会继续在Bar(父类)中寻找__metaclass__属性,并尝试做和前面同样的操作。
3.如果Python在任何父类中都找不到__metaclass__,它就会在模块层次中去寻找__metaclass__,并尝试做同样的操作。
4.如果还是找不到__metaclass__,Python就会用内置的type来创建这个类对象。
现在的问题就是,你可以在__metaclass__中放置些什么代码呢?答案就是:可以创建一个类的东西。那么什么可以用来创建一个类呢?type,或者任何使用到type或者子类化type的东东都可以。
1.9.8自定义元类
元类的主要目的就是为了当创建类时能够自动地改变类。通常,你会为API做这样的事情,你希望可以创建符合当前上下文的类。
假想一个很傻的例子,你决定在你的模块里所有的类的属性都应该是大写形式。有好几种方法可以办到,但其中一种就是通过在模块级别设定__metaclass__。采用这种方法,这个模块中的所有类都会通过这个元类来创建,我们只需要告诉元类把所有的属性都改成大写形式就万事大吉了。
幸运的是,__metaclass__实际上可以被任意调用,它并不需要是一个正式的类。所以,我们这里就先以一个简单的函数作为例子开始。
python2中
#-*- coding:utf-8 -*-
defupper_attr(future_class_name, future_class_parents, future_class_attr):
#遍历属性字典,把不是__开头的属性名字变为大写
newAttr = {}
forname,valueinfuture_class_attr.items():
ifnotname.startswith("__"):
newAttr[name.upper()] = value
#调用type来创建一个类
returntype(future_class_name, future_class_parents, newAttr)
classFoo(object):
__metaclass__ = upper_attr#设置Foo类的元类为upper_attr
bar ='bip'
print(hasattr(Foo,'bar'))
print(hasattr(Foo,'BAR'))
f = Foo()
print(f.BAR)
python3中
#-*- coding:utf-8 -*-
defupper_attr(future_class_name, future_class_parents, future_class_attr):
#遍历属性字典,把不是__开头的属性名字变为大写
newAttr = {}
forname,valueinfuture_class_attr.items():
ifnotname.startswith("__"):
newAttr[name.upper()] = value
#调用type来创建一个类
returntype(future_class_name, future_class_parents, newAttr)
classFoo(object, metaclass=upper_attr):
bar ='bip'
print(hasattr(Foo,'bar'))
print(hasattr(Foo,'BAR'))
f = Foo()
print(f.BAR)
现在让我们再做一次,这一次用一个真正的class来当做元类。
#coding=utf-8
classUpperAttrMetaClass(type):
# __new__是在__init__之前被调用的特殊方法
# __new__是用来创建对象并返回之的方法
#而__init__只是用来将传入的参数初始化给对象
#你很少用到__new__,除非你希望能够控制对象的创建
#这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
#如果你希望的话,你也可以在__init__中做些事情
#还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用
def__new__(cls, future_class_name, future_class_parents, future_class_attr):
#遍历属性字典,把不是__开头的属性名字变为大写
newAttr = {}
forname,valueinfuture_class_attr.items():
ifnotname.startswith("__"):
newAttr[name.upper()] = value
#方法1:通过'type'来做类对象的创建
# return type(future_class_name, future_class_parents, newAttr)
#方法2:复用type.__new__方法
#这就是基本的OOP编程,没什么魔法
# return type.__new__(cls, future_class_name, future_class_parents, newAttr)
#方法3:使用super方法
returnsuper(UpperAttrMetaClass, cls).__new__(cls, future_class_name, future_class_parents, newAttr)
#python2的用法
classFoo(object):
__metaclass__ = UpperAttrMetaClass
bar ='bip'
# python3的用法
# class Foo(object, metaclass = UpperAttrMetaClass):
# bar = 'bip'
print(hasattr(Foo,'bar'))
#输出: False
print(hasattr(Foo,'BAR'))
#输出:True
f = Foo()
print(f.BAR)
#输出:'bip'
就是这样,除此之外,关于元类真的没有别的可说的了。但就元类本身而言,它们其实是很简单的:
1.拦截类的创建
2.修改类
3.返回修改之后的类
1.1.9为什么要是用元类
现在回到我们的大主题上来,究竟是为什么你会去使用这样一种容易出错且晦涩的特性?好吧,一般来说,你根本就用不上它:
“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。” —— Python界的领袖Tim Peters
2垃圾回收
2.1小整数对象池
整数在程序中的使用非常广泛,Python为了优化速度,使用了小整数对象池, 避免为整数频繁申请和销毁内存空间。
Python对小整数的定义是[-5, 257)这些整数对象是提前建立好的,不会被垃圾回收。在一个Python的程序中,所有位于这个范围内的整数使用的都是同一个对象.
同理,单个字母也是这样的。
但是当定义2个相同的字符串时,引用计数为0,触发垃圾回收
2.2大整数对象池
每一个大整数,均创建一个新的对象
2.3 intern机制
a1 ="HelloWorld"
a2 ="HelloWorld"
a3 ="HelloWorld"
a4 ="HelloWorld"
a5 ="HelloWorld"
a6 ="HelloWorld"
a7 ="HelloWorld"
a8 ="HelloWorld"
a9 ="HelloWorld"
python会不会创建9个对象呢?在内存中会不会开辟9个”HelloWorld”的内存空间呢? 想一下,如果是这样的话,我们写10000个对象,比如a1=”HelloWorld”…..a1000=”HelloWorld”, 那他岂不是开辟了1000个”HelloWorld”所占的内存空间了呢?如果真这样,内存不就爆了吗?所以python中有这样一个机制——intern机制,让他只占用一个”HelloWorld”所占的内存空间。靠引用计数去维护何时释放。
总结
·小整数[-5,257)共用对象,常驻内存
·单个字符共用对象,常驻内存
·单个单词,不可修改,默认开启intern机制,共用对象,引用计数为0,则销毁
大整数不共用内存,引用计数为0,销毁
数值类型和字符串类型在Python中都是不可变的,这意味着你无法修改这个对象的值,每次对变量的修改,实际上是创建一个新的对象
2.4Garbage collection(gc垃圾回收)
现在的高级语言如java,c#等,都采用了垃圾收集机制,而不再是c,c++里用户自己管理维护内存的方式。自己管理内存极其自由,可以任意申请内存,但如同一把双刃剑,为大量内存泄露,悬空指针等bug埋下隐患。 对于一个字符串、列表、类甚至数值都是对象,且定位简单易用的语言,自然不会让用户去处理如何分配回收内存的问题。python里也同java一样采用了垃圾收集机制,不过不一样的是: python采用的是引用计数机制为主,标记-清除和分代收集两种机制为辅的策略
引用计数机制:
python里每一个东西都是对象,它们的核心就是一个结构体:PyObject
typedefstruct_object {
intob_refcnt;
struct_typeobject *ob_type;
} PyObject;
PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少
#definePy_INCREF(op) ((op)->ob_refcnt++)//增加计数
#definePy_DECREF(op) \//减少计数
if(--(op)->ob_refcnt !=0) \
; \
else\
__Py_Dealloc((PyObject *)(op))
当引用计数为0时,该对象生命就结束了。
引用计数机制的优点:
·简单
·实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。
引用计数机制的缺点:
·维护引用计数消耗资源
·循环引用
list1 = []
list2 = []
list1.append(list2)
list2.append(list1)
list1与list2相互引用,如果不存在其他对象对它们的引用,list1与list2的引用计数也仍然为1,所占用的内存永远无法被回收,这将是致命的。 对于如今的强大硬件,缺点1尚可接受,但是循环引用导致内存泄露,注定python还将引入新的回收机制。(标记清除和分代收集)
2.5 Ruby 和Python 垃圾回收
2.5.1应用程序那颗跃动的心
GC系统所承担的工作远比"垃圾回收"多得多。实际上,它们负责三个重要任务。它们
·为新生成的对象分配内存
·识别那些垃圾对象,并且
·从垃圾对象那回收内存。
如果将应用程序比作人的身体:所有你所写的那些优雅的代码,业务逻辑,算法,应该就是大脑。以此类推,垃圾回收机制应该是那个身体器官呢?(我从RuPy听众那听到了不少有趣的答案:腰子、白血球:))
我认为垃圾回收就是应用程序那颗跃动的心。像心脏为身体其他器官提供血液和营养物那样,垃圾回收器为你的应该程序提供内存和对象。如果心脏停跳,过不了几秒钟人就完了。如果垃圾回收器停止工作或运行迟缓,像动脉阻塞,你的应用程序效率也会下降,直至最终死掉。
顺便提一句,两种语言的代码竟能如此相像:Ruby和Python在表达同一事物上真的只是略有不同。但是在这两种语言的内部实现上是否也如此相似呢?
顺便提一句,两种语言的代码竟能如此相像:Ruby和Python在表达同一事物上真的只是略有不同。但是在这两种语言的内部实现上是否也如此相似呢?
2.5.1 Ruby垃圾回收
它一开始是用Lisp实现的。Lisp不仅是最早的函数式编程语言,在计算机科学领域也有许多创举。其一就是利用垃圾回收机制自动化进行程序内存管理的概念。
标准版的Ruby,也就是众所周知的"Matz's Ruby Interpreter"(MRI),所使用的GC算法与McCarthy在1960年的实现方式很类似。无论好坏,Ruby的垃圾回收机制已经53岁高龄了。像Lisp一样,Ruby预先创建一些对象,然后在你分配新对象或者变量的时候供你使用。
2.5.2 python对象分配
我们已经了解了Ruby预先创建对象并将它们存放在可用列表中。那Python又怎么样呢?
尽管由于许多原因Python也使用可用列表(用来回收一些特定对象比如list),但在为新对象和变量分配内存的方面Python和Ruby是不同的。
与Ruby不同,当创建对象时Python立即向操作系统请求内存。(Python实际上实现了一套自己的内存分配系统,在操作系统堆之上提供了一个抽象层。
Ruby把无用的对象留在内存里,直到下一次GC执行
回过来看Ruby。随着我们创建越来越多的对象,Ruby会持续寻可用列表里取预创建对象给我们。
Ruby不会立即清除代码中不再使用的旧对象!Ruby开发者们就像是住在一间凌乱的房间,地板上摞着衣服,要么洗碗池里都是脏盘子。作为一个Ruby程序员,无用的垃圾对象会一直环绕着你。
Python的这种垃圾回收算法被称为引用计数。是George-Collins在1960年发明的,恰巧与John McCarthy发明的可用列表算法在同一年出现。就像Mike-Bernstein在6月份哥谭市Ruby大会杰出的垃圾回收机制演讲中说的: "1960年是垃圾收集器的黄金年代..."
Python开发者工作在卫生之家,你可以想象,有个患有轻度OCD(一种强迫症)的室友一刻不停地跟在你身后打扫,你一放下脏碟子或杯子,有个家伙已经准备好把它放进洗碗机了!
2.5.3标记-删除vs引用计数
乍一看,Python的GC算法貌似远胜于Ruby的:宁舍洁宇而居秽室乎?为什么Ruby宁愿定期强制程序停止运行,也不使用Python的算法呢?
然而,引用计数并不像第一眼看上去那样简单。有许多原因使得不许多语言不像Python这样使用引用计数GC算法:
首先,它不好实现。Python不得不在每个对象内部留一些空间来处理引用数。这样付出了一小点儿空间上的代价。但更糟糕的是,每个简单的操作(像修改变量或引用)都会变成一个更复杂的操作,因为Python需要增加一个计数,减少另一个,还可能释放对象。
第二点,它相对较慢。虽然Python随着程序执行GC很稳健(一把脏碟子放在洗碗盆里就开始洗啦),但这并不一定更快。Python不停地更新着众多引用数值。特别是当你不再使用一个大数据结构的时候,比如一个包含很多元素的列表,Python可能必须一次性释放大量对象。减少引用数就成了一项复杂的递归过程了。
最后,它不是总奏效的。引用计数不能处理环形数据结构--也就是含有循环引用的数据结构。
2.6 python中的循环数据结构以及引用计数
2.6.1循环引用
通过上篇,我们知道在Python中,每个对象都保存了一个称为引用计数的整数值,来追踪到底有多少引用指向了这个对象。无论何时,如果我们程序中的一个变量或其他对象引用了目标对象,Python将会增加这个计数值,而当程序停止使用这个对象,则Python会减少这个计数值。一旦计数值被减到零,Python将会释放这个对象以及回收相关内存空间。
从六十年代开始,计算机科学界就面临了一个严重的理论问题,那就是针对引用计数这种算法来说,如果一个数据结构引用了它自身,即如果这个数据结构是一个循环数据结构,那么某些引用计数值是肯定无法变成零的。跟Ruby不同的是,Python中你可以在代码运行的时候动态定义实例变量或对象属性。这看起来似乎有点像Ruby缺失了某些有趣的魔法。
2.6.2在python中的零代(Generation zero)
请注意在以上刚刚说到的例子中,我们以一个不是很常见的情况结尾:我们有一个“孤岛”或是一组未使用的、互相指向的对象,但是谁都没有外部引用。换句话说,我们的程序不再使用这些节点对象了,所以我们希望Python的垃圾回收机制能够足够智能去释放这些对象并回收它们占用的内存空间。但是这不可能,因为所有的引用计数都是1而不是0。Python的引用计数算法不能够处理互相指向自己的对象。Python会循环遍历零代列表上的每个对象,检查列表中每个互相引用的对象,根据规则减掉其引用计数。在这个过程中,Python会一个接一个的统计内部引用的数量以防过早地释放对象。
2.6.3 python中gc的阈值
Python什么时候会进行这个标记过程?随着你的程序运行,Python解释器保持对新创建的对象,以及因为引用计数为零而被释放掉的对象的追踪。从理论上说,这两个值应该保持一致,因为程序新建的每个对象都应该最终被释放掉。
当然,事实并非如此。因为循环引用的原因,并且因为你的程序使用了一些比其他对象存在时间更长的对象,从而被分配对象的计数值与被释放对象的计数值之间的差异在逐渐增长。一旦这个差异累计超过某个阈值,则Python的收集机制就启动了,并且触发上边所说到的零代算法,释放“浮动的垃圾”,并且将剩下的对象移动到一代列表。
随着时间的推移,程序所使用的对象逐渐从零代列表移动到一代列表。而Python对于一代列表中对象的处理遵循同样的方法,一旦被分配计数值与被释放计数值累计到达一定阈值,Python会将剩下的活跃对象移动到二代列表。
通过这种方法,你的代码所长期使用的对象,那些你的代码持续访问的活跃对象,会从零代链表转移到一代再转移到二代。通过不同的阈值设置,Python可以在不同的时间间隔处理这些对象。Python处理零代最为频繁,其次是一代然后才是二代。
弱代假说
来看看代垃圾回收算法的核心行为:垃圾回收器会更频繁的处理新对象。一个新的对象即是你的程序刚刚创建的,而一个来的对象则是经过了几个时间周期之后仍然存在的对象。Python会在当一个对象从零代移动到一代,或是从一代移动到二代的过程中提升(promote)这个对象。
为什么要这么做?这种算法的根源来自于弱代假说(weak generational hypothesis)。这个假说由两个观点构成:首先是年亲的对象通常死得也快,而老对象则很有可能存活更长的时间。
通过频繁的处理零代链表中的新对象,Python的垃圾收集器将把时间花在更有意义的地方:它处理那些很快就可能变成垃圾的新对象。同时只在很少的时候,当满足阈值的条件,收集器才回去处理那些老变量。
2.6.4 gc循环引用导致内存泄漏
引用计数的缺陷是循环引用的问题
importgc
classClassA():
def__init__(self):
print('object born,id:%s'%str(hex(id(self))))
deff2():
whileTrue:
c1 = ClassA()
c2 = ClassA()
c1.t = c2
c2.t = c1
delc1
delc2
#把python的gc关闭
gc.disable()
f2()
执行f2(),进程占用的内存会不断增大。
·创建了c1,c2后这两块内存的引用计数都是1,执行c1.t=c2和c2.t=c1后,这两块内存的引用计数变成2.
·在del c1后,内存1的对象的引用计数变为1,由于不是为0,所以内存1的对象不会被销毁,所以内存2的对象的引用数依然是2,在del c2后,同理,内存1的对象,内存2的对象的引用数都是1。
·虽然它们两个的对象都是可以被销毁的,但是由于循环引用,导致垃圾回收器都不会回收它们,所以就会导致内存泄露。
2.6.5垃圾回收
#coding=utf-8
importgc
classClassA():
def__init__(self):
print('object born,id:%s'%str(hex(id(self))))
# def __del__(self):
# print('object del,id:%s'%str(hex(id(self))))
deff3():
print("-----0------")
# print(gc.collect())
c1 = ClassA()
c2 = ClassA()
c1.t = c2
c2.t = c1
print("-----1------")
delc1
delc2
print("-----2------")
print(gc.garbage)
print("-----3------")
print(gc.collect())#显式执行垃圾回收
print("-----4------")
print(gc.garbage)
print("-----5------")
if__name__ =='__main__':
gc.set_debug(gc.DEBUG_LEAK)#设置gc模块的日志
f3()
python2运行结果:
-----0------
object born,id:0x724b20
object born,id:0x724b48
-----1------
-----2------
[]
-----3------
gc: collectable
gc: collectable
gc: collectable
gc: collectable
4
-----4------
[<__main__.ClassA instance at0x724b20>, <__main__.ClassA instance at0x724b48>, {'t': <__main__.ClassA instance at0x724b48>}, {'t': <__main__.ClassA instance at0x724b20>}]
-----5------
说明:
·垃圾回收后的对象会放在gc.garbage列表里面
·gc.collect()会返回不可达的对象数目,4等于两个对象以及它们对应的dict
有三种情况会触发垃圾回收:
1.调用gc.collect(),
2.当gc模块的计数器达到阀值的时候。
3.程序退出的时候
2.6.6 gc模块常用功能解析
gc模块提供一个接口给开发者设置垃圾回收的选项。上面说到,采用引用计数的方法管理内存的一个缺陷是循环引用,而gc模块的一个主要功能就是解决循环引用的问题。
1.1.1.1.1常用函数:
1、gc.set_debug(flags)设置gc的debug日志,一般设置为gc.DEBUG_LEAK
2、gc.collect([generation])显式进行垃圾回收,可以输入参数,0代表只检查第一代的对象,1代表检查一,二代的对象,2代表检查一,二,三代的对象,如果不传参数,执行一个full collection,也就是等于传2。 返回不可达(unreachable objects)对象的数目
3、gc.get_threshold()获取的gc模块中自动执行垃圾回收的频率。
4、gc.set_threshold(threshold0[, threshold1[, threshold2])设置自动执行垃圾回收的频率。
5、gc.get_count()获取当前自动执行垃圾回收的计数器,返回一个长度为3的列表
1.1.1.1.2gc模块的自动垃圾回收机制
必须要import gc模块,并且is_enable()=True才会启动自动垃圾回收。
这个机制的主要作用就是发现并处理不可达的垃圾对象。
垃圾回收=垃圾检查+垃圾回收
在Python中,采用分代收集的方法。把对象分为三代,一开始,对象在创建的时候,放在一代中,如果在一次一代的垃圾检查中,改对象存活下来,就会被放到二代中,同理在一次二代的垃圾检查中,该对象存活下来,就会被放到三代中。
gc模块里面会有一个长度为3的列表的计数器,可以通过gc.get_count()获取。
例如(488,3,0),其中488是指距离上一次一代垃圾检查,Python分配内存的数目减去释放内存的数目,注意是内存分配,而不是引用计数的增加。例如:
printgc.get_count()# (590, 8, 0)
a = ClassA()
printgc.get_count()# (591, 8, 0)
dela
printgc.get_count()# (590, 8, 0)
3是指距离上一次二代垃圾检查,一代垃圾检查的次数,同理,0是指距离上一次三代垃圾检查,二代垃圾检查的次数。
gc模快有一个自动垃圾回收的阀值,即通过gc.get_threshold函数获取到的长度为3的元组,例如(700,10,10)每一次计数器的增加,gc模块就会检查增加后的计数是否达到阀值的数目,如果是,就会执行对应的代数的垃圾检查,然后重置计数器
例如,假设阀值是(700,10,10):
当计数器从(699,3,0)增加到(700,3,0),gc模块就会执行gc.collect(0),即检查一代对象的垃圾,并重置计数器为(0,4,0)
当计数器从(699,9,0)增加到(700,9,0),gc模块就会执行gc.collect(1),即检查一、二代对象的垃圾,并重置计数器为(0,0,1)
当计数器从(699,9,9)增加到(700,9,9),gc模块就会执行gc.collect(2),即检查一、二、三代对象的垃圾,并重置计数器为(0,0,0)
注意点
gc模块唯一处理不了的是循环引用的类都有__del__方法,所以项目中要避免定义__del__方法
importgc
classClassA():
pass
# def __del__(self):
# print('object born,id:%s'%str(hex(id(self))))
gc.set_debug(gc.DEBUG_LEAK)
a = ClassA()
b = ClassA()
a.next = b
b.prev = a
print"--1--"
printgc.collect()
print"--2--"
dela
print"--3--"
delb
print"--3-1--"
printgc.collect()
print"--4--"
运行结果:
--1--
0
--2--
--3--
--3-1--
gc: collectable
gc: collectable
gc: collectable
gc: collectable
4
--4--
如果把del打开,运行结果为:
--1--
0
--2--
--3--
--3-1--
gc: uncollectable
gc: uncollectable
gc: uncollectable
gc: uncollectable
4
--4--
3 内建函数
Build-in Function,启动python解释器,输入dir(__builtins__),可以看到很多python解释器启动后默认加载的属性和函数,这些函数称之为内建函数, 这些函数因为在编程时使用较多,cpython解释器用c语言实现了这些函数,启动解释器 时默认加载。
这些函数数量众多,不宜记忆,开发时不是都用到的,待用到时再help(function),查看如何使用,或结合百度查询即可,在这里介绍些常用的内建函数。
3.1 rang
range(stop) -> list of integers
range(start, stop[, step]) -> list of integers
·start:计数从start开始。默认是从0开始。例如range(5)等价于range(0,5);
·stop:到stop结束,但不包括stop.例如:range(0,5) 是[0, 1, 2, 3, 4]没有5
·step:每次跳跃的间距,默认为1。例如:range(0,5) 等价于range(0, 5, 1)
python2中range返回列表,python3中range返回一个迭代值。如果想得到列表,可通过list函数
a = range(5)
list(a)
创建列表的另外一种方法
In [21]: testList = [x+2forxinrange(5)]
In [22]: testList
Out[22]: [2,3,4,5,6]
3.2 map函数
map函数会根据提供的函数对指定序列做映射
map(...)
map(function, sequence[, sequence, ...]) -> list
·function:是一个函数
·sequence:是一个或多个序列,取决于function需要几个参数
·返回值是一个map
参数序列中的每一个元素分别调用function函数,返回包含每次function函数返回值的list。
#函数需要一个参数
map(lambdax: x*x, [1,2,3])
#结果为:[1, 4, 9]
#函数需要两个参数
map(lambdax, y: x+y, [1,2,3], [4,5,6])
#结果为:[5, 7, 9]
deff1( x, y ):
return(x,y)
l1 = [0,1,2,3,4,5,6]
l2 = ['Sun','M','T','W','T','F','S']
l3 = map( f1, l1, l2 )
print(list(l3))
#结果为:[(0, 'Sun'), (1, 'M'), (2, 'T'), (3, 'W'), (4, 'T'), (5, 'F'), (6, 'S')]
3.3 filter函数
filter函数会对指定序列执行过滤操作
filter(...)
filter(function or None, sequence) -> list, tuple, or string
Return those items of sequence for which function(item) is true. If
function is None, return the items that are true. If sequence is a tuple
or string, return the same type, else return a list.
·function:接受一个参数,返回布尔值True或False
·sequence:序列可以是str,tuple,list
filter函数会对序列参数sequence中的每个元素调用function函数,最后返回的结果包含调用结果为True的元素。
返回值的类型和参数sequence的类型相同
filter(lambdax: x%2, [1,2,3,4])
[1,3]
filter(None,"she")
'she'
3.4 reduce函数
reduce函数,reduce函数会对参数序列中元素进行累积
reduce(...)
reduce(function, sequence[, initial]) -> value
Apply a function of two arguments cumulatively to the items of a sequence,
from left to right, so as to reduce the sequence to a single value.
For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates
((((1+2)+3)+4)+5). If initial is present, it is placed before the items
of the sequence in the calculation, and serves as a default when the
sequence is empty.
·function:该函数有两个参数
·sequence:序列可以是str,tuple,list
·initial:固定初始值
reduce依次从sequence中取一个元素,和上一次调用function的结果做参数再次调用function。 第一次调用function时,如果提供initial参数,会以sequence中的第一个元素和initial作为参数调用function,否则会以序列sequence中的前两个元素做参数调用function。 注意function函数不能为None。
reduce(lambdax, y: x+y, [1,2,3,4])
10
reduce(lambdax, y: x+y, [1,2,3,4],5)
15
reduce(lambdax, y: x+y, ['aa','bb','cc'],'dd')
'ddaabbcc'
在Python3里,reduce函数已经被从全局名字空间里移除了,它现在被放置在fucntools模块里用的话要先引入:from functools import reduce
3.5 sorted函数
3.6partial函数(偏函数)
3.7 wraps函数
使用装饰器时,有一些细节需要被注意。例如,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变)。
添加后由于函数名和函数的doc发生了改变,对测试结果有一些影响,例如:
defnote(func):
"note function"
defwrapper():
"wrapper function"
print('note something')
returnfunc()
returnwrapper
@note
deftest():
"test function"
print('I am test')
test()
print(test.__doc__)
运行结果
note something
I am test
wrapper function
所以,Python的functools包中提供了一个叫wraps的装饰器来消除这样的副作用。例如:
importfunctools
defnote(func):
"note function"
@functools.wraps(func)
defwrapper():
"wrapper function"
print('note something')
returnfunc()
returnwrapper
@note
deftest():
"test function"
print('I am test')
test()
print(test.__doc__)
运行结果
note something
I am test
test function