python高級編程2

1.元類

1.1.1類也是對象

在大多數(shù)編程語言中,類就是一組用來描述如何生成一個(gè)對象的代碼段。在Python中這一點(diǎn)仍然成立:

>>>classObjectCreator(object):

…pass

>>>my_object = ObjectCreator()

>>>printmy_object

<__main__.ObjectCreator object at0x8974f2c>

但是,Python中的類還遠(yuǎn)不止如此。類同樣也是一種對象。是的,沒錯(cuò),就是對象。只要你使用關(guān)鍵字class,Python解釋器在執(zhí)行的時(shí)候就會(huì)創(chuàng)建一個(gè)對象。

下面的代碼段:

>>>classObjectCreator(object):

…pass

將在內(nèi)存中創(chuàng)建一個(gè)對象,名字就是ObjectCreator。這個(gè)對象(類對象ObjectCreator)擁有創(chuàng)建對象(實(shí)例對象)的能力。但是,它的本質(zhì)仍然是一個(gè)對象,于是乎你可以對它做如下的操作:

1.你可以將它賦值給一個(gè)變量

2.你可以拷貝它

3.你可以為它增加屬性

4.你可以將它作為函數(shù)參數(shù)進(jìn)行傳遞

下面是示例:

>>> print ObjectCreator ????#你可以打印一個(gè)類,因?yàn)樗鋵?shí)也是一個(gè)對象

>>> def echo(o):

… ??????print o

>>> echo(ObjectCreator) ????????????????#你可以將類做為參數(shù)傳給函數(shù)

>>> print hasattr(ObjectCreator, 'new_attribute')

Fasle

>>> ObjectCreator.new_attribute = 'foo' #你可以為類增加屬性

>>> print hasattr(ObjectCreator, 'new_attribute')

True

>>> print ObjectCreator.new_attribute

foo

>>> ObjectCreatorMirror = ObjectCreator #你可以將類賦值給一個(gè)變量

>>> print ObjectCreatorMirror()

<__main__.ObjectCreator object at 0x8997b4c>

1.1.2動(dòng)態(tài)的創(chuàng)建類

因?yàn)轭愐彩菍ο?,你可以在運(yùn)行時(shí)動(dòng)態(tài)的創(chuàng)建它們,就像其他任何對象一樣。首先,你可以在函數(shù)中創(chuàng)建類,使用class關(guān)鍵字即可。

>>> def choose_class(name):

… ??????if name == 'foo':

… ??????????class Foo(object):

… ??????????????pass

… ??????????return Foo ????#返回的是類,不是類的實(shí)例

… ??????else:

… ??????????class Bar(object):

… ??????????????pass

… ??????????return Bar

>>> MyClass = choose_class('foo')

>>> print MyClass ?????????????#函數(shù)返回的是類,不是類的實(shí)例

>>> print MyClass() ???????????#你可以通過這個(gè)類創(chuàng)建類實(shí)例,也就是對象

<__main__.Foo object at 0x89c6d4c>

但這還不夠動(dòng)態(tài),因?yàn)槟闳匀恍枰约壕帉懻麄€(gè)類的代碼。由于類也是對象,所以它們必須是通過什么東西來生成的才對。當(dāng)你使用class關(guān)鍵字時(shí),Python解釋器自動(dòng)創(chuàng)建這個(gè)對象。但就和Python中的大多數(shù)事情一樣,Python仍然提供給你手動(dòng)處理的方法。

還記得內(nèi)建函數(shù)type嗎?這個(gè)古老但強(qiáng)大的函數(shù)能夠讓你知道一個(gè)對象的類型是什么,就像這樣:

>>> print type(1) #數(shù)值的類型

>>> print type("1") #字符串的類型

>>> print type(ObjectCreator()) #實(shí)例對象的類型

>>> print type(ObjectCreator) #類的類型

仔細(xì)觀察上面的運(yùn)行結(jié)果,發(fā)現(xiàn)使用type對ObjectCreator查看類型是,答案為type, 是不是有些驚訝。。??聪旅?/p>

1.1.3使用type創(chuàng)建類

type還有一種完全不同的功能,動(dòng)態(tài)的創(chuàng)建類。

type可以接受一個(gè)類的描述作為參數(shù),然后返回一個(gè)類。(要知道,根據(jù)傳入?yún)?shù)的不同,同一個(gè)函數(shù)擁有兩種完全不同的用法是一件很傻的事情,但這在Python中是為了保持向后兼容性)

type可以像這樣工作:

type(類名,由父類名稱組成的元組(針對繼承的情況,可以為空),包含屬性的字典(名稱和值))

比如下面的代碼:

In [2]:classTest:#定義了一個(gè)Test類

...:pass

...:

In [3]: Test()#創(chuàng)建了一個(gè)Test類的實(shí)例對象

Out[3]: <__main__.Test at0x10d3f8438>

可以手動(dòng)像這樣創(chuàng)建:

Test2 = type("Test2",(),{})#定了一個(gè)Test2類

In [5]: Test2()#創(chuàng)建了一個(gè)Test2類的實(shí)例對象

Out[5]: <__main__.Test2 at0x10d406b38>

我們使用"Test2"作為類名,并且也可以把它當(dāng)做一個(gè)變量來作為類的引用。類和變量是不同的,這里沒有任何理由把事情弄的復(fù)雜。即type函數(shù)中第1個(gè)實(shí)參,也可以叫做其他的名字,這個(gè)名字表示類的名字

In [23]: MyDogClass = type('MyDog', (), {})

In [24]: print MyDogClass

使用help來測試這2個(gè)類

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創(chuàng)建帶有屬性的類

type接受一個(gè)字典來為類定義屬性,因此

>>>Foo = type('Foo', (), {'bar':True})

可以翻譯為:

>>>classFoo(object):

… ??????bar =True

并且可以將Foo當(dāng)成一個(gè)普通的類一樣使用:

>>> print Foo

>>> print Foo.bar

True

>>> f = Foo()

>>> print f

<__main__.Foo object at 0x8a9b84c>

>>> print f.bar

True

當(dāng)然,你可以向這個(gè)類繼承,所以,如下的代碼:

>>>classFooChild(Foo):

…pass

就可以寫成:

>>> FooChild = type('FooChild', (Foo,),{})

>>> print FooChild

>>> print FooChild.bar ??# bar屬性是由Foo繼承而來

True

注意:

·type的第2個(gè)參數(shù),元組中是父類的名字,而不是字符串

·添加的屬性是類屬性,并不是實(shí)例屬性

1.1.5使用type創(chuàng)建帶有方法的類

最終你會(huì)希望為你的類增加方法。只需要定義一個(gè)有著恰當(dāng)簽名的函數(shù)并將其作為屬性賦值就可以了。

添加實(shí)例方法

In [46]:defecho_bar(self):#定義了一個(gè)普通的函數(shù)

...: ????print(self.bar)

...:

In [47]: FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})#讓FooChild類中的echo_bar屬性,指向了上面定義的函數(shù)

In [48]: hasattr(Foo,'echo_bar')#判斷Foo類中,是否有echo_bar這個(gè)屬性

Out[48]:False

In [49]:

In [49]: hasattr(FooChild,'echo_bar')#判斷FooChild類中,是否有echo_bar這個(gè)屬性

Out[49]:True

In [50]: my_foo = FooChild()

In [51]: my_foo.echo_bar()

True

添加靜態(tài)方法

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中,類也是對象,你可以動(dòng)態(tài)的創(chuàng)建類。這就是當(dāng)你使用關(guān)鍵字class時(shí)Python在幕后做的事情,而這就是通過元類來實(shí)現(xiàn)的。

1.1.6到底什么是元類

元類就是用來創(chuàng)建類的“東西”。你創(chuàng)建類就是為了創(chuàng)建類的實(shí)例對象,不是嗎?但是我們已經(jīng)學(xué)習(xí)到了Python中的類也是對象。

元類就是用來創(chuàng)建這些類(對象)的,元類就是類的類,你可以這樣理解為:

MyClass = MetaClass()#使用元類創(chuàng)建出一個(gè)對象,這個(gè)對象稱為“類”

MyObject = MyClass()#使用“類”來創(chuàng)建出實(shí)例對象

你已經(jīng)看到了type可以讓你像這樣做:

MyClass = type('MyClass', (), {})

這是因?yàn)楹瘮?shù)type實(shí)際上是一個(gè)元類。type就是Python在背后用來創(chuàng)建所有類的元類?,F(xiàn)在你想知道那為什么type會(huì)全部采用小寫形式而不是Type呢?好吧,我猜這是為了和str保持一致性,str是用來創(chuàng)建字符串對象的類,而int是用來創(chuàng)建整數(shù)對象的類。type就是創(chuàng)建類對象的類。你可以通過檢查__class__屬性來看到這一點(diǎn)。Python中所有的東西,注意,我是指所有的東西——都是對象。這包括整數(shù)、字符串、函數(shù)以及類。它們?nèi)慷际菍ο?,而且它們都是從一個(gè)類創(chuàng)建而來,這個(gè)類就是type。

>>> age = 35

>>> age.__class__

>>> name = 'bob'

>>> name.__class__

>>> def foo(): pass

>>>foo.__class__

>>> class Bar(object): pass

>>> b = Bar()

>>> b.__class__

現(xiàn)在,對于任何一個(gè)__class__的__class__屬性又是什么呢?

>>>a.__class__.__class__

>>>age.__class__.__class__

>>>foo.__class__.__class__

>>>b.__class__.__class__

因此,元類就是創(chuàng)建類這種對象的東西。type就是Python的內(nèi)建元類,當(dāng)然了,你也可以創(chuàng)建自己的元類。

1.9.7 __metaclass__屬性

你可以在定義一個(gè)類的時(shí)候?yàn)槠涮砑觃_metaclass__屬性。

classFoo(object):

__metaclass__ = something…

...省略...

如果你這么做了,Python就會(huì)用元類來創(chuàng)建類Foo。小心點(diǎn),這里面有些技巧。你首先寫下class Foo(object),但是類Foo還沒有在內(nèi)存中創(chuàng)建。Python會(huì)在類的定義中尋找__metaclass__屬性,如果找到了,Python就會(huì)用它來創(chuàng)建類Foo,如果沒有找到,就會(huì)用內(nèi)建的type來創(chuàng)建這個(gè)類。把下面這段話反復(fù)讀幾次。當(dāng)你寫如下代碼時(shí):

classFoo(Bar):

pass

Python做了如下的操作:

1.Foo中有__metaclass__這個(gè)屬性嗎?如果是,Python會(huì)通過__metaclass__創(chuàng)建一個(gè)名字為Foo的類(對象)

2.如果Python沒有找到__metaclass__,它會(huì)繼續(xù)在Bar(父類)中尋找__metaclass__屬性,并嘗試做和前面同樣的操作。

3.如果Python在任何父類中都找不到__metaclass__,它就會(huì)在模塊層次中去尋找__metaclass__,并嘗試做同樣的操作。

4.如果還是找不到__metaclass__,Python就會(huì)用內(nèi)置的type來創(chuàng)建這個(gè)類對象。

現(xiàn)在的問題就是,你可以在__metaclass__中放置些什么代碼呢?答案就是:可以創(chuàng)建一個(gè)類的東西。那么什么可以用來創(chuàng)建一個(gè)類呢?type,或者任何使用到type或者子類化type的東東都可以。

1.9.8自定義元類

元類的主要目的就是為了當(dāng)創(chuàng)建類時(shí)能夠自動(dòng)地改變類。通常,你會(huì)為API做這樣的事情,你希望可以創(chuàng)建符合當(dāng)前上下文的類。

假想一個(gè)很傻的例子,你決定在你的模塊里所有的類的屬性都應(yīng)該是大寫形式。有好幾種方法可以辦到,但其中一種就是通過在模塊級別設(shè)定__metaclass__。采用這種方法,這個(gè)模塊中的所有類都會(huì)通過這個(gè)元類來創(chuàng)建,我們只需要告訴元類把所有的屬性都改成大寫形式就萬事大吉了。

幸運(yùn)的是,__metaclass__實(shí)際上可以被任意調(diào)用,它并不需要是一個(gè)正式的類。所以,我們這里就先以一個(gè)簡單的函數(shù)作為例子開始。

python2中

#-*- coding:utf-8 -*-

defupper_attr(future_class_name, future_class_parents, future_class_attr):

#遍歷屬性字典,把不是__開頭的屬性名字變?yōu)榇髮?/p>

newAttr = {}

forname,valueinfuture_class_attr.items():

ifnotname.startswith("__"):

newAttr[name.upper()] = value

#調(diào)用type來創(chuàng)建一個(gè)類

returntype(future_class_name, future_class_parents, newAttr)

classFoo(object):

__metaclass__ = upper_attr#設(shè)置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):

#遍歷屬性字典,把不是__開頭的屬性名字變?yōu)榇髮?/p>

newAttr = {}

forname,valueinfuture_class_attr.items():

ifnotname.startswith("__"):

newAttr[name.upper()] = value

#調(diào)用type來創(chuàng)建一個(gè)類

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)

現(xiàn)在讓我們再做一次,這一次用一個(gè)真正的class來當(dāng)做元類。

#coding=utf-8

classUpperAttrMetaClass(type):

# __new__是在__init__之前被調(diào)用的特殊方法

# __new__是用來創(chuàng)建對象并返回之的方法

#而__init__只是用來將傳入的參數(shù)初始化給對象

#你很少用到__new__,除非你希望能夠控制對象的創(chuàng)建

#這里,創(chuàng)建的對象是類,我們希望能夠自定義它,所以我們這里改寫__new__

#如果你希望的話,你也可以在__init__中做些事情

#還有一些高級的用法會(huì)涉及到改寫__call__特殊方法,但是我們這里不用

def__new__(cls, future_class_name, future_class_parents, future_class_attr):

#遍歷屬性字典,把不是__開頭的屬性名字變?yōu)榇髮?/p>

newAttr = {}

forname,valueinfuture_class_attr.items():

ifnotname.startswith("__"):

newAttr[name.upper()] = value

#方法1:通過'type'來做類對象的創(chuàng)建

# return type(future_class_name, future_class_parents, newAttr)

#方法2:復(fù)用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'

就是這樣,除此之外,關(guān)于元類真的沒有別的可說的了。但就元類本身而言,它們其實(shí)是很簡單的:

1.攔截類的創(chuàng)建

2.修改類

3.返回修改之后的類

1.1.9為什么要是用元類

現(xiàn)在回到我們的大主題上來,究竟是為什么你會(huì)去使用這樣一種容易出錯(cuò)且晦澀的特性?好吧,一般來說,你根本就用不上它:

“元類就是深度的魔法,99%的用戶應(yīng)該根本不必為此操心。如果你想搞清楚究竟是否需要用到元類,那么你就不需要它。那些實(shí)際用到元類的人都非常清楚地知道他們需要做什么,而且根本不需要解釋為什么要用元類?!?—— Python界的領(lǐng)袖Tim Peters

2垃圾回收

2.1小整數(shù)對象池

整數(shù)在程序中的使用非常廣泛,Python為了優(yōu)化速度,使用了小整數(shù)對象池, 避免為整數(shù)頻繁申請和銷毀內(nèi)存空間。

Python對小整數(shù)的定義是[-5, 257)這些整數(shù)對象是提前建立好的,不會(huì)被垃圾回收。在一個(gè)Python的程序中,所有位于這個(gè)范圍內(nèi)的整數(shù)使用的都是同一個(gè)對象.

同理,單個(gè)字母也是這樣的。

但是當(dāng)定義2個(gè)相同的字符串時(shí),引用計(jì)數(shù)為0,觸發(fā)垃圾回收

2.2大整數(shù)對象池

每一個(gè)大整數(shù),均創(chuàng)建一個(gè)新的對象

2.3 intern機(jī)制

a1 ="HelloWorld"

a2 ="HelloWorld"

a3 ="HelloWorld"

a4 ="HelloWorld"

a5 ="HelloWorld"

a6 ="HelloWorld"

a7 ="HelloWorld"

a8 ="HelloWorld"

a9 ="HelloWorld"

python會(huì)不會(huì)創(chuàng)建9個(gè)對象呢?在內(nèi)存中會(huì)不會(huì)開辟9個(gè)”HelloWorld”的內(nèi)存空間呢? 想一下,如果是這樣的話,我們寫10000個(gè)對象,比如a1=”HelloWorld”…..a1000=”HelloWorld”, 那他豈不是開辟了1000個(gè)”HelloWorld”所占的內(nèi)存空間了呢?如果真這樣,內(nèi)存不就爆了嗎?所以python中有這樣一個(gè)機(jī)制——intern機(jī)制,讓他只占用一個(gè)”HelloWorld”所占的內(nèi)存空間??恳糜?jì)數(shù)去維護(hù)何時(shí)釋放。

總結(jié)

·小整數(shù)[-5,257)共用對象,常駐內(nèi)存

·單個(gè)字符共用對象,常駐內(nèi)存

·單個(gè)單詞,不可修改,默認(rèn)開啟intern機(jī)制,共用對象,引用計(jì)數(shù)為0,則銷毀

大整數(shù)不共用內(nèi)存,引用計(jì)數(shù)為0,銷毀

數(shù)值類型和字符串類型在Python中都是不可變的,這意味著你無法修改這個(gè)對象的值,每次對變量的修改,實(shí)際上是創(chuàng)建一個(gè)新的對象

2.4Garbage collection(gc垃圾回收)

現(xiàn)在的高級語言如java,c#等,都采用了垃圾收集機(jī)制,而不再是c,c++里用戶自己管理維護(hù)內(nèi)存的方式。自己管理內(nèi)存極其自由,可以任意申請內(nèi)存,但如同一把雙刃劍,為大量內(nèi)存泄露,懸空指針等bug埋下隱患。 對于一個(gè)字符串、列表、類甚至數(shù)值都是對象,且定位簡單易用的語言,自然不會(huì)讓用戶去處理如何分配回收內(nèi)存的問題。python里也同java一樣采用了垃圾收集機(jī)制,不過不一樣的是: python采用的是引用計(jì)數(shù)機(jī)制為主,標(biāo)記-清除和分代收集兩種機(jī)制為輔的策略

引用計(jì)數(shù)機(jī)制:

python里每一個(gè)東西都是對象,它們的核心就是一個(gè)結(jié)構(gòu)體:PyObject

typedefstruct_object {

intob_refcnt;

struct_typeobject *ob_type;

} PyObject;

PyObject是每個(gè)對象必有的內(nèi)容,其中ob_refcnt就是做為引用計(jì)數(shù)。當(dāng)一個(gè)對象有新的引用時(shí),它的ob_refcnt就會(huì)增加,當(dāng)引用它的對象被刪除,它的ob_refcnt就會(huì)減少

#definePy_INCREF(op) ??((op)->ob_refcnt++)//增加計(jì)數(shù)

#definePy_DECREF(op) \//減少計(jì)數(shù)

if(--(op)->ob_refcnt !=0) \

; \

else\

__Py_Dealloc((PyObject *)(op))

當(dāng)引用計(jì)數(shù)為0時(shí),該對象生命就結(jié)束了。

引用計(jì)數(shù)機(jī)制的優(yōu)點(diǎn):

·簡單

·實(shí)時(shí)性:一旦沒有引用,內(nèi)存就直接釋放了。不用像其他機(jī)制等到特定時(shí)機(jī)。實(shí)時(shí)性還帶來一個(gè)好處:處理回收內(nèi)存的時(shí)間分?jǐn)偟搅似綍r(shí)。

引用計(jì)數(shù)機(jī)制的缺點(diǎn):

·維護(hù)引用計(jì)數(shù)消耗資源

·循環(huán)引用

list1 = []

list2 = []

list1.append(list2)

list2.append(list1)

list1與list2相互引用,如果不存在其他對象對它們的引用,list1與list2的引用計(jì)數(shù)也仍然為1,所占用的內(nèi)存永遠(yuǎn)無法被回收,這將是致命的。 對于如今的強(qiáng)大硬件,缺點(diǎn)1尚可接受,但是循環(huán)引用導(dǎo)致內(nèi)存泄露,注定python還將引入新的回收機(jī)制。(標(biāo)記清除和分代收集)

2.5 Ruby 和Python 垃圾回收

2.5.1應(yīng)用程序那顆躍動(dòng)的心

GC系統(tǒng)所承擔(dān)的工作遠(yuǎn)比"垃圾回收"多得多。實(shí)際上,它們負(fù)責(zé)三個(gè)重要任務(wù)。它們

·為新生成的對象分配內(nèi)存

·識別那些垃圾對象,并且

·從垃圾對象那回收內(nèi)存。

如果將應(yīng)用程序比作人的身體:所有你所寫的那些優(yōu)雅的代碼,業(yè)務(wù)邏輯,算法,應(yīng)該就是大腦。以此類推,垃圾回收機(jī)制應(yīng)該是那個(gè)身體器官呢?(我從RuPy聽眾那聽到了不少有趣的答案:腰子、白血球:))

我認(rèn)為垃圾回收就是應(yīng)用程序那顆躍動(dòng)的心。像心臟為身體其他器官提供血液和營養(yǎng)物那樣,垃圾回收器為你的應(yīng)該程序提供內(nèi)存和對象。如果心臟停跳,過不了幾秒鐘人就完了。如果垃圾回收器停止工作或運(yùn)行遲緩,像動(dòng)脈阻塞,你的應(yīng)用程序效率也會(huì)下降,直至最終死掉。

順便提一句,兩種語言的代碼竟能如此相像:Ruby和Python在表達(dá)同一事物上真的只是略有不同。但是在這兩種語言的內(nèi)部實(shí)現(xiàn)上是否也如此相似呢?

順便提一句,兩種語言的代碼竟能如此相像:Ruby和Python在表達(dá)同一事物上真的只是略有不同。但是在這兩種語言的內(nèi)部實(shí)現(xiàn)上是否也如此相似呢?

2.5.1 ?Ruby垃圾回收

它一開始是用Lisp實(shí)現(xiàn)的。Lisp不僅是最早的函數(shù)式編程語言,在計(jì)算機(jī)科學(xué)領(lǐng)域也有許多創(chuàng)舉。其一就是利用垃圾回收機(jī)制自動(dòng)化進(jìn)行程序內(nèi)存管理的概念。

標(biāo)準(zhǔn)版的Ruby,也就是眾所周知的"Matz's Ruby Interpreter"(MRI),所使用的GC算法與McCarthy在1960年的實(shí)現(xiàn)方式很類似。無論好壞,Ruby的垃圾回收機(jī)制已經(jīng)53歲高齡了。像Lisp一樣,Ruby預(yù)先創(chuàng)建一些對象,然后在你分配新對象或者變量的時(shí)候供你使用。

2.5.2 python對象分配

我們已經(jīng)了解了Ruby預(yù)先創(chuàng)建對象并將它們存放在可用列表中。那Python又怎么樣呢?

盡管由于許多原因Python也使用可用列表(用來回收一些特定對象比如list),但在為新對象和變量分配內(nèi)存的方面Python和Ruby是不同的。

與Ruby不同,當(dāng)創(chuàng)建對象時(shí)Python立即向操作系統(tǒng)請求內(nèi)存。(Python實(shí)際上實(shí)現(xiàn)了一套自己的內(nèi)存分配系統(tǒng),在操作系統(tǒng)堆之上提供了一個(gè)抽象層。

Ruby把無用的對象留在內(nèi)存里,直到下一次GC執(zhí)行

回過來看Ruby。隨著我們創(chuàng)建越來越多的對象,Ruby會(huì)持續(xù)尋可用列表里取預(yù)創(chuàng)建對象給我們。

Ruby不會(huì)立即清除代碼中不再使用的舊對象!Ruby開發(fā)者們就像是住在一間凌亂的房間,地板上摞著衣服,要么洗碗池里都是臟盤子。作為一個(gè)Ruby程序員,無用的垃圾對象會(huì)一直環(huán)繞著你。

Python的這種垃圾回收算法被稱為引用計(jì)數(shù)。是George-Collins在1960年發(fā)明的,恰巧與John McCarthy發(fā)明的可用列表算法在同一年出現(xiàn)。就像Mike-Bernstein在6月份哥譚市Ruby大會(huì)杰出的垃圾回收機(jī)制演講中說的: "1960年是垃圾收集器的黃金年代..."

Python開發(fā)者工作在衛(wèi)生之家,你可以想象,有個(gè)患有輕度OCD(一種強(qiáng)迫癥)的室友一刻不停地跟在你身后打掃,你一放下臟碟子或杯子,有個(gè)家伙已經(jīng)準(zhǔn)備好把它放進(jìn)洗碗機(jī)了!

2.5.3標(biāo)記-刪除vs引用計(jì)數(shù)

乍一看,Python的GC算法貌似遠(yuǎn)勝于Ruby的:寧舍潔宇而居穢室乎?為什么Ruby寧愿定期強(qiáng)制程序停止運(yùn)行,也不使用Python的算法呢?

然而,引用計(jì)數(shù)并不像第一眼看上去那樣簡單。有許多原因使得不許多語言不像Python這樣使用引用計(jì)數(shù)GC算法:

首先,它不好實(shí)現(xiàn)。Python不得不在每個(gè)對象內(nèi)部留一些空間來處理引用數(shù)。這樣付出了一小點(diǎn)兒空間上的代價(jià)。但更糟糕的是,每個(gè)簡單的操作(像修改變量或引用)都會(huì)變成一個(gè)更復(fù)雜的操作,因?yàn)镻ython需要增加一個(gè)計(jì)數(shù),減少另一個(gè),還可能釋放對象。

第二點(diǎn),它相對較慢。雖然Python隨著程序執(zhí)行GC很穩(wěn)?。ㄒ话雅K碟子放在洗碗盆里就開始洗啦),但這并不一定更快。Python不停地更新著眾多引用數(shù)值。特別是當(dāng)你不再使用一個(gè)大數(shù)據(jù)結(jié)構(gòu)的時(shí)候,比如一個(gè)包含很多元素的列表,Python可能必須一次性釋放大量對象。減少引用數(shù)就成了一項(xiàng)復(fù)雜的遞歸過程了。

最后,它不是總奏效的。引用計(jì)數(shù)不能處理環(huán)形數(shù)據(jù)結(jié)構(gòu)--也就是含有循環(huán)引用的數(shù)據(jù)結(jié)構(gòu)。

2.6 python中的循環(huán)數(shù)據(jù)結(jié)構(gòu)以及引用計(jì)數(shù)

2.6.1循環(huán)引用

通過上篇,我們知道在Python中,每個(gè)對象都保存了一個(gè)稱為引用計(jì)數(shù)的整數(shù)值,來追蹤到底有多少引用指向了這個(gè)對象。無論何時(shí),如果我們程序中的一個(gè)變量或其他對象引用了目標(biāo)對象,Python將會(huì)增加這個(gè)計(jì)數(shù)值,而當(dāng)程序停止使用這個(gè)對象,則Python會(huì)減少這個(gè)計(jì)數(shù)值。一旦計(jì)數(shù)值被減到零,Python將會(huì)釋放這個(gè)對象以及回收相關(guān)內(nèi)存空間。

從六十年代開始,計(jì)算機(jī)科學(xué)界就面臨了一個(gè)嚴(yán)重的理論問題,那就是針對引用計(jì)數(shù)這種算法來說,如果一個(gè)數(shù)據(jù)結(jié)構(gòu)引用了它自身,即如果這個(gè)數(shù)據(jù)結(jié)構(gòu)是一個(gè)循環(huán)數(shù)據(jù)結(jié)構(gòu),那么某些引用計(jì)數(shù)值是肯定無法變成零的。跟Ruby不同的是,Python中你可以在代碼運(yùn)行的時(shí)候動(dòng)態(tài)定義實(shí)例變量或?qū)ο髮傩浴_@看起來似乎有點(diǎn)像Ruby缺失了某些有趣的魔法。

2.6.2在python中的零代(Generation zero)

請注意在以上剛剛說到的例子中,我們以一個(gè)不是很常見的情況結(jié)尾:我們有一個(gè)“孤島”或是一組未使用的、互相指向的對象,但是誰都沒有外部引用。換句話說,我們的程序不再使用這些節(jié)點(diǎn)對象了,所以我們希望Python的垃圾回收機(jī)制能夠足夠智能去釋放這些對象并回收它們占用的內(nèi)存空間。但是這不可能,因?yàn)樗械囊糜?jì)數(shù)都是1而不是0。Python的引用計(jì)數(shù)算法不能夠處理互相指向自己的對象。Python會(huì)循環(huán)遍歷零代列表上的每個(gè)對象,檢查列表中每個(gè)互相引用的對象,根據(jù)規(guī)則減掉其引用計(jì)數(shù)。在這個(gè)過程中,Python會(huì)一個(gè)接一個(gè)的統(tǒng)計(jì)內(nèi)部引用的數(shù)量以防過早地釋放對象。

2.6.3 python中g(shù)c的閾值

Python什么時(shí)候會(huì)進(jìn)行這個(gè)標(biāo)記過程?隨著你的程序運(yùn)行,Python解釋器保持對新創(chuàng)建的對象,以及因?yàn)橐糜?jì)數(shù)為零而被釋放掉的對象的追蹤。從理論上說,這兩個(gè)值應(yīng)該保持一致,因?yàn)槌绦蛐陆ǖ拿總€(gè)對象都應(yīng)該最終被釋放掉。

當(dāng)然,事實(shí)并非如此。因?yàn)檠h(huán)引用的原因,并且因?yàn)槟愕某绦蚴褂昧艘恍┍绕渌麑ο蟠嬖跁r(shí)間更長的對象,從而被分配對象的計(jì)數(shù)值與被釋放對象的計(jì)數(shù)值之間的差異在逐漸增長。一旦這個(gè)差異累計(jì)超過某個(gè)閾值,則Python的收集機(jī)制就啟動(dòng)了,并且觸發(fā)上邊所說到的零代算法,釋放“浮動(dòng)的垃圾”,并且將剩下的對象移動(dòng)到一代列表。

隨著時(shí)間的推移,程序所使用的對象逐漸從零代列表移動(dòng)到一代列表。而Python對于一代列表中對象的處理遵循同樣的方法,一旦被分配計(jì)數(shù)值與被釋放計(jì)數(shù)值累計(jì)到達(dá)一定閾值,Python會(huì)將剩下的活躍對象移動(dòng)到二代列表。

通過這種方法,你的代碼所長期使用的對象,那些你的代碼持續(xù)訪問的活躍對象,會(huì)從零代鏈表轉(zhuǎn)移到一代再轉(zhuǎn)移到二代。通過不同的閾值設(shè)置,Python可以在不同的時(shí)間間隔處理這些對象。Python處理零代最為頻繁,其次是一代然后才是二代。

弱代假說

來看看代垃圾回收算法的核心行為:垃圾回收器會(huì)更頻繁的處理新對象。一個(gè)新的對象即是你的程序剛剛創(chuàng)建的,而一個(gè)來的對象則是經(jīng)過了幾個(gè)時(shí)間周期之后仍然存在的對象。Python會(huì)在當(dāng)一個(gè)對象從零代移動(dòng)到一代,或是從一代移動(dòng)到二代的過程中提升(promote)這個(gè)對象。

為什么要這么做?這種算法的根源來自于弱代假說(weak generational hypothesis)。這個(gè)假說由兩個(gè)觀點(diǎn)構(gòu)成:首先是年親的對象通常死得也快,而老對象則很有可能存活更長的時(shí)間。

通過頻繁的處理零代鏈表中的新對象,Python的垃圾收集器將把時(shí)間花在更有意義的地方:它處理那些很快就可能變成垃圾的新對象。同時(shí)只在很少的時(shí)候,當(dāng)滿足閾值的條件,收集器才回去處理那些老變量。

2.6.4 gc循環(huán)引用導(dǎo)致內(nèi)存泄漏

引用計(jì)數(shù)的缺陷是循環(huán)引用的問題

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關(guān)閉

gc.disable()

f2()

執(zhí)行f2(),進(jìn)程占用的內(nèi)存會(huì)不斷增大。

·創(chuàng)建了c1,c2后這兩塊內(nèi)存的引用計(jì)數(shù)都是1,執(zhí)行c1.t=c2和c2.t=c1后,這兩塊內(nèi)存的引用計(jì)數(shù)變成2.

·在del c1后,內(nèi)存1的對象的引用計(jì)數(shù)變?yōu)?,由于不是為0,所以內(nèi)存1的對象不會(huì)被銷毀,所以內(nèi)存2的對象的引用數(shù)依然是2,在del c2后,同理,內(nèi)存1的對象,內(nèi)存2的對象的引用數(shù)都是1。

·雖然它們兩個(gè)的對象都是可以被銷毀的,但是由于循環(huán)引用,導(dǎo)致垃圾回收器都不會(huì)回收它們,所以就會(huì)導(dǎo)致內(nèi)存泄露。

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())#顯式執(zhí)行垃圾回收

print("-----4------")

print(gc.garbage)

print("-----5------")

if__name__ =='__main__':

gc.set_debug(gc.DEBUG_LEAK)#設(shè)置gc模塊的日志

f3()

python2運(yùn)行結(jié)果:

-----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------

說明:

·垃圾回收后的對象會(huì)放在gc.garbage列表里面

·gc.collect()會(huì)返回不可達(dá)的對象數(shù)目,4等于兩個(gè)對象以及它們對應(yīng)的dict

有三種情況會(huì)觸發(fā)垃圾回收:

1.調(diào)用gc.collect(),

2.當(dāng)gc模塊的計(jì)數(shù)器達(dá)到閥值的時(shí)候。

3.程序退出的時(shí)候

2.6.6 gc模塊常用功能解析

gc模塊提供一個(gè)接口給開發(fā)者設(shè)置垃圾回收的選項(xiàng)。上面說到,采用引用計(jì)數(shù)的方法管理內(nèi)存的一個(gè)缺陷是循環(huán)引用,而gc模塊的一個(gè)主要功能就是解決循環(huán)引用的問題。

1.1.1.1.1常用函數(shù):

1、gc.set_debug(flags)設(shè)置gc的debug日志,一般設(shè)置為gc.DEBUG_LEAK

2、gc.collect([generation])顯式進(jìn)行垃圾回收,可以輸入?yún)?shù),0代表只檢查第一代的對象,1代表檢查一,二代的對象,2代表檢查一,二,三代的對象,如果不傳參數(shù),執(zhí)行一個(gè)full collection,也就是等于傳2。 返回不可達(dá)(unreachable objects)對象的數(shù)目

3、gc.get_threshold()獲取的gc模塊中自動(dòng)執(zhí)行垃圾回收的頻率。

4、gc.set_threshold(threshold0[, threshold1[, threshold2])設(shè)置自動(dòng)執(zhí)行垃圾回收的頻率。

5、gc.get_count()獲取當(dāng)前自動(dòng)執(zhí)行垃圾回收的計(jì)數(shù)器,返回一個(gè)長度為3的列表

1.1.1.1.2gc模塊的自動(dòng)垃圾回收機(jī)制

必須要import gc模塊,并且is_enable()=True才會(huì)啟動(dòng)自動(dòng)垃圾回收。

這個(gè)機(jī)制的主要作用就是發(fā)現(xiàn)并處理不可達(dá)的垃圾對象。

垃圾回收=垃圾檢查+垃圾回收

在Python中,采用分代收集的方法。把對象分為三代,一開始,對象在創(chuàng)建的時(shí)候,放在一代中,如果在一次一代的垃圾檢查中,改對象存活下來,就會(huì)被放到二代中,同理在一次二代的垃圾檢查中,該對象存活下來,就會(huì)被放到三代中。

gc模塊里面會(huì)有一個(gè)長度為3的列表的計(jì)數(shù)器,可以通過gc.get_count()獲取。

例如(488,3,0),其中488是指距離上一次一代垃圾檢查,Python分配內(nèi)存的數(shù)目減去釋放內(nèi)存的數(shù)目,注意是內(nèi)存分配,而不是引用計(jì)數(shù)的增加。例如:

printgc.get_count()# (590, 8, 0)

a = ClassA()

printgc.get_count()# (591, 8, 0)

dela

printgc.get_count()# (590, 8, 0)

3是指距離上一次二代垃圾檢查,一代垃圾檢查的次數(shù),同理,0是指距離上一次三代垃圾檢查,二代垃圾檢查的次數(shù)。

gc??煊幸粋€(gè)自動(dòng)垃圾回收的閥值,即通過gc.get_threshold函數(shù)獲取到的長度為3的元組,例如(700,10,10)每一次計(jì)數(shù)器的增加,gc模塊就會(huì)檢查增加后的計(jì)數(shù)是否達(dá)到閥值的數(shù)目,如果是,就會(huì)執(zhí)行對應(yīng)的代數(shù)的垃圾檢查,然后重置計(jì)數(shù)器

例如,假設(shè)閥值是(700,10,10):

當(dāng)計(jì)數(shù)器從(699,3,0)增加到(700,3,0),gc模塊就會(huì)執(zhí)行g(shù)c.collect(0),即檢查一代對象的垃圾,并重置計(jì)數(shù)器為(0,4,0)

當(dāng)計(jì)數(shù)器從(699,9,0)增加到(700,9,0),gc模塊就會(huì)執(zhí)行g(shù)c.collect(1),即檢查一、二代對象的垃圾,并重置計(jì)數(shù)器為(0,0,1)

當(dāng)計(jì)數(shù)器從(699,9,9)增加到(700,9,9),gc模塊就會(huì)執(zhí)行g(shù)c.collect(2),即檢查一、二、三代對象的垃圾,并重置計(jì)數(shù)器為(0,0,0)

注意點(diǎn)

gc模塊唯一處理不了的是循環(huán)引用的類都有__del__方法,所以項(xiàng)目中要避免定義__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--"

運(yùn)行結(jié)果:

--1--

0

--2--

--3--

--3-1--

gc: collectable

gc: collectable

gc: collectable

gc: collectable

4

--4--

如果把del打開,運(yùn)行結(jié)果為:

--1--

0

--2--

--3--

--3-1--

gc: uncollectable

gc: uncollectable

gc: uncollectable

gc: uncollectable

4

--4--

3 內(nèi)建函數(shù)

Build-in Function,啟動(dòng)python解釋器,輸入dir(__builtins__),可以看到很多python解釋器啟動(dòng)后默認(rèn)加載的屬性和函數(shù),這些函數(shù)稱之為內(nèi)建函數(shù), 這些函數(shù)因?yàn)樵诰幊虝r(shí)使用較多,cpython解釋器用c語言實(shí)現(xiàn)了這些函數(shù),啟動(dòng)解釋器 時(shí)默認(rèn)加載。

這些函數(shù)數(shù)量眾多,不宜記憶,開發(fā)時(shí)不是都用到的,待用到時(shí)再help(function),查看如何使用,或結(jié)合百度查詢即可,在這里介紹些常用的內(nèi)建函數(shù)。

3.1 rang

range(stop) -> list of integers

range(start, stop[, step]) -> list of integers

·start:計(jì)數(shù)從start開始。默認(rèn)是從0開始。例如range(5)等價(jià)于range(0,5);

·stop:到stop結(jié)束,但不包括stop.例如:range(0,5) 是[0, 1, 2, 3, 4]沒有5

·step:每次跳躍的間距,默認(rèn)為1。例如:range(0,5) 等價(jià)于range(0, 5, 1)

python2中range返回列表,python3中range返回一個(gè)迭代值。如果想得到列表,可通過list函數(shù)

a = range(5)

list(a)

創(chuàng)建列表的另外一種方法

In [21]: testList = [x+2forxinrange(5)]

In [22]: testList

Out[22]: [2,3,4,5,6]

3.2 map函數(shù)

map函數(shù)會(huì)根據(jù)提供的函數(shù)對指定序列做映射

map(...)

map(function, sequence[, sequence, ...]) -> list

·function:是一個(gè)函數(shù)

·sequence:是一個(gè)或多個(gè)序列,取決于function需要幾個(gè)參數(shù)

·返回值是一個(gè)map

參數(shù)序列中的每一個(gè)元素分別調(diào)用function函數(shù),返回包含每次function函數(shù)返回值的list。

#函數(shù)需要一個(gè)參數(shù)

map(lambdax: x*x, [1,2,3])

#結(jié)果為:[1, 4, 9]

#函數(shù)需要兩個(gè)參數(shù)

map(lambdax, y: x+y, [1,2,3], [4,5,6])

#結(jié)果為:[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))

#結(jié)果為:[(0, 'Sun'), (1, 'M'), (2, 'T'), (3, 'W'), (4, 'T'), (5, 'F'), (6, 'S')]

3.3 filter函數(shù)

filter函數(shù)會(huì)對指定序列執(zhí)行過濾操作

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:接受一個(gè)參數(shù),返回布爾值True或False

·sequence:序列可以是str,tuple,list

filter函數(shù)會(huì)對序列參數(shù)sequence中的每個(gè)元素調(diào)用function函數(shù),最后返回的結(jié)果包含調(diào)用結(jié)果為True的元素。

返回值的類型和參數(shù)sequence的類型相同

filter(lambdax: x%2, [1,2,3,4])

[1,3]

filter(None,"she")

'she'

3.4 reduce函數(shù)

reduce函數(shù),reduce函數(shù)會(huì)對參數(shù)序列中元素進(jìn)行累積

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:該函數(shù)有兩個(gè)參數(shù)

·sequence:序列可以是str,tuple,list

·initial:固定初始值

reduce依次從sequence中取一個(gè)元素,和上一次調(diào)用function的結(jié)果做參數(shù)再次調(diào)用function。 第一次調(diào)用function時(shí),如果提供initial參數(shù),會(huì)以sequence中的第一個(gè)元素和initial作為參數(shù)調(diào)用function,否則會(huì)以序列sequence中的前兩個(gè)元素做參數(shù)調(diào)用function。 注意function函數(shù)不能為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函數(shù)已經(jīng)被從全局名字空間里移除了,它現(xiàn)在被放置在fucntools模塊里用的話要先引入:from functools import reduce

3.5 sorted函數(shù)

3.6partial函數(shù)(偏函數(shù))

3.7 wraps函數(shù)

使用裝飾器時(shí),有一些細(xì)節(jié)需要被注意。例如,被裝飾后的函數(shù)其實(shí)已經(jīng)是另外一個(gè)函數(shù)了(函數(shù)名等函數(shù)屬性會(huì)發(fā)生改變)。

添加后由于函數(shù)名和函數(shù)的doc發(fā)生了改變,對測試結(jié)果有一些影響,例如:

defnote(func):

"note function"

defwrapper():

"wrapper function"

print('note something')

returnfunc()

returnwrapper

@note

deftest():

"test function"

print('I am test')

test()

print(test.__doc__)

運(yùn)行結(jié)果

note something

I am test

wrapper function

所以,Python的functools包中提供了一個(gè)叫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__)

運(yùn)行結(jié)果

note something

I am test

test function

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一元類 1類也是對象 在大多數(shù)編程語言中,類就是一組用來描述如何生成一個(gè)對象的代碼段。在Python中這一點(diǎn)仍然成...
    五行缺覺閱讀 1,160評論 0 1
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,540評論 19 139
  • http://python.jobbole.com/85231/ 關(guān)于專業(yè)技能寫完項(xiàng)目接著寫寫一名3年工作經(jīng)驗(yàn)的J...
    燕京博士閱讀 7,792評論 1 118
  • 總結(jié): 無力無奈完全沒有戰(zhàn)斗力的一天,我想靜靜!
    軒寧媽閱讀 171評論 0 1
  • 本地?cái)?shù)據(jù)存儲(chǔ)指的是將數(shù)據(jù)存儲(chǔ)在設(shè)備中,在需要數(shù)據(jù)的時(shí)候調(diào)用,數(shù)據(jù)并不會(huì)因?yàn)閼?yīng)用退出,或者網(wǎng)絡(luò)斷開而無法獲取。在Re...
    二木又土閱讀 20,129評論 0 17

友情鏈接更多精彩內(nèi)容