1.列表解析式和字典解析式
列表解析式語(yǔ)法 [expr for value in collection if condition]
生成式語(yǔ)法 (expr for value in collection if condition) 生成器
簡(jiǎn)單用法
In [1]: [i*1 for i in range(5)]Out[2]: [0, 1, 2, 3, 4]In [29]: {x:random() for x in 'abc'}Out[29]: {'a': 2, 'b': 2, 'c': 2}復(fù)雜用法
>>> [(x,y) for x in range(5) if x%2==0 for y in range(5) if y %2==1] [(0, 1), (0, 3), (2, 1), (2, 3), (4, 1), (4, 3)]2.三元運(yùn)算
# 普通條件語(yǔ)句if 1 == 1: name = 'wupeiqi'else: name = 'alex' # 三元運(yùn)算name = 'wupeiqi' if 1 == 1 else 'alex'3.map,reduce,filter,sorted函數(shù)的用法
map()函數(shù)接收兩個(gè)參數(shù),一個(gè)是函數(shù)名,一個(gè)是序列,map將傳入的函數(shù)依次作用到序列的每個(gè)元素,并把結(jié)果作為新的list返回。
>>> def f(x): return x * x >>> map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])[1, 4, 9, 16, 25, 36, 49, 64, 81] >>> map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9])['1', '2', '3', '4', '5', '6', '7', '8', '9']
map通常與lambda結(jié)合使用,寫(xiě)出精致而高效的語(yǔ)句
reduce()把一個(gè)函數(shù)作用在一個(gè)序列[x1, x2, x3...]上,這個(gè)函數(shù)必須接收兩個(gè)參數(shù),reduce把結(jié)果繼續(xù)和序列的下一個(gè)元素做累積計(jì)算,結(jié)果返回的是一個(gè)值。其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)>>> def fn(x, y): ... return x * 10 + y...>>> reduce(fn, [1, 3, 5, 7, 9])13579filter()和map()也接收一個(gè)函數(shù)名和一個(gè)序列。和map()不同的是,filter()把傳入的函數(shù)依次作用于每個(gè)元素,然后根據(jù)返回值是True還是False決定保留還是丟棄該元素。
例如,在一個(gè)list中,刪掉偶數(shù),只保留奇數(shù),可以這么寫(xiě):
def is_odd(n): return n % 2 == 1filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15])# 結(jié)果: [1, 5, 9, 15]sorted(iterable[,cmp,[,key[,reverse=True]]])
iterable包括字符串,序列,字典等可迭代對(duì)象,默認(rèn)情況下reverse=True(升序),reverse=False為降序
sorted與sort的區(qū)別???
用list.sort()方法來(lái)排序,此時(shí)list本身將被修改
In [1]: a=[3,5,9,5,2]In [2]: b=sorted(a)In [3]: bOut[4]: [2, 3, 5, 5, 9]In [5]: aOut[6]: [3, 5, 9, 5, 2]In [7]: a.sort()In [8]: aOut[9]: [2, 3, 5, 5, 9]
綜合練習(xí)
num = input('輸入一個(gè)整數(shù):')#將整數(shù)轉(zhuǎn)換成字符串s = str(num)#定義map參數(shù)函數(shù)def f(s): #字符與數(shù)字字典 dic = {'1':1,'2':2,'3':3,'4':4,'5':5,"6":6,'7':7,'8':8,'9':9,'0':0} return dic[s]#定義reduce參數(shù)函數(shù)def add(x,y): return x + y#調(diào)用map()函數(shù),將字符串轉(zhuǎn)換成對(duì)應(yīng)數(shù)字序列,并打印s = map(f,s)print "輸入整數(shù)%d的組成數(shù)字為%s"%(num,s),#調(diào)用reduce函數(shù),對(duì)數(shù)字序列求和,并打印Sum = reduce(add,s)print "其和為:%d"%Sum4.lambda匿名函數(shù)
lambda arg:expression# 定義函數(shù)(普通方式)def func(arg): return arg + 1# 執(zhí)行函數(shù)result = func(123)# ###################### lambda ####################### 定義函數(shù)(lambda表達(dá)式)my_lambda = lambda arg : arg + 1# 執(zhí)行函數(shù)result = my_lambda(123)5.dict按值搜索最大值或最小值
In [1]: d = {'a':7,'b':8,'c':5}In [2]: min(d.items(),key=lambda x:x[1])Out[3]:('c', 5)In [4]: min(d.items(),key=lambda x:x[1])[1]Out[5]:5In [6]: d.items()#返回元素為元祖的序列Out[7]: dict_items([('b', 8), ('a', 7), ('c', 5)]) Out[8]: help(min)min(iterable, *[, default=obj, key=func]) -> valuemin(arg1, arg2, *args, *[, key=func]) -> value6.for循環(huán)中變量i的作用域
在for 循環(huán)里,變量一直存在在上下文中。就算是在循環(huán)外面,變量引用仍然有效。這里有個(gè)問(wèn)題容易被忽略,如果在循環(huán)之前已經(jīng)有一個(gè)同名對(duì)象存在,這個(gè)對(duì)象是被覆蓋的。
x = 5for x in range(10): passprint(x)#9----------------------------------for i in range(4): d = i*2 s = "string" + str(i)print(d)#6print(s)#srting37.值引用和地址引用
python中變量名里存儲(chǔ)的是對(duì)象的地址(引用),這區(qū)別于c語(yǔ)言
a=10時(shí)的內(nèi)存示意圖
image
In [1]: m=10000In [2]: id(m)Out[2]: 4365939312In [3]: n=m #地址賦值In [4]: id(n)Out[4]: 4365939312了解python里的賦值的含義???
def f(m): | def的時(shí)候就已經(jīng)在棧中存放了 m=[2,3] #賦值相當(dāng)于重新綁定對(duì)象 | 變量m,當(dāng)把a(bǔ)傳給m時(shí),m引用了a | 的內(nèi)容,但是對(duì)m賦值的時(shí)候,ma = [4,5] | 指向了新值,不影響a對(duì)應(yīng)的值。f(a) |print(a) #[4,5] |def f(m): m.append(4)a = [4,5]f(a)print(a) #[4,5,4]
python函數(shù)參數(shù)傳遞是傳對(duì)象或者說(shuō)是傳對(duì)象的引用。函數(shù)參數(shù)在傳遞的過(guò)程中將整個(gè)對(duì)象傳入,對(duì)可變對(duì)象的修改在函數(shù)外部以及內(nèi)部都可見(jiàn),調(diào)用者和被調(diào)用者之間共享這個(gè)對(duì)象,而對(duì)于不可變對(duì)象,由于并不能真正被修改,因此,修改往往是通過(guò)生成一個(gè)新對(duì)象然后賦值實(shí)現(xiàn)的?!?1條建議之31》
當(dāng)一個(gè)引用傳遞給函數(shù)的時(shí)候,函數(shù)自動(dòng)復(fù)制一份引用,這個(gè)函數(shù)里的引用和外邊的引用沒(méi)有半毛關(guān)系了.所以第一個(gè)例子里函數(shù)把引用指向了一個(gè)不可變對(duì)象,當(dāng)函數(shù)返回的時(shí)候,外面的引用沒(méi)半毛感覺(jué).而第二個(gè)例子就不一樣了,函數(shù)內(nèi)的引用指向的是可變對(duì)象,對(duì)它的操作就和定位了指針地址一樣,在內(nèi)存里進(jìn)行修改.
8.append的陷阱
import randomlis = [1,2,3,4]l = []for i in range(5): random.shuffle(lis) print(lis, hex(id(lis))) l.append(lis) #append()傳入list的引用print(l)([1, 3, 4, 2], '0x107227950')([3, 4, 1, 2], '0x107227950')([3, 2, 4, 1], '0x107227950')([2, 3, 4, 1], '0x107227950')([4, 3, 1, 2], '0x107227950')[[4, 3, 1, 2], [4, 3, 1, 2], [4, 3, 1, 2], [4, 3, 1, 2], [4, 3, 1, 2]]9.__new__和__init__的區(qū)別
按字面意思理解就可以。 __new__ 用于創(chuàng)建對(duì)象,而 __init__ 是在創(chuàng)建完成之后初始化對(duì)象狀態(tài)。Python 中只不過(guò)是把創(chuàng)建對(duì)象和初始化這兩個(gè)概念分開(kāi)了,而其他語(yǔ)言中直接用一個(gè)構(gòu)造函數(shù)解決。
class A(object): def __new__(cls, *args, **kwargs): print cls print hex(id(cls)) #cls就是類(lèi)對(duì)象A # print args # print kwargs print "===========================================" instance = object.__new__(cls, *args, **kwargs) #instance就是實(shí)例對(duì)象a1 print hex(id(instance)) return instance #若沒(méi)有return語(yǔ)句,則無(wú)法創(chuàng)建實(shí)例對(duì)象a1 # def __init__(self, a, b): # print "init gets called" # print "self is", self # self.a, self.b = a, ba1=A()print "==========================================="print Aprint hex(id(A))print "==========================================="print a1print hex(id(a1))Build Result:<class '__main__.A'>0x22163e0===========================================0x21d6a10===========================================<class '__main__.A'>0x22163e0===========================================<__main__.A object at 0x021D6A10>0x21d6a10
從上例看出,對(duì)象實(shí)例化的內(nèi)部過(guò)程:
當(dāng)執(zhí)行a1=A(),從上例看出,對(duì)象實(shí)例化的內(nèi)部過(guò)程:當(dāng)執(zhí)行a1=A(),首先調(diào)用__new__方法,由于__new__方法返回的是一個(gè)實(shí)例對(duì)象(a1),故相當(dāng)于創(chuàng)建了一個(gè)實(shí)例對(duì)象,再次調(diào)用__init__方法,來(lái)對(duì)變量初始化,再次調(diào)用__init__方法,來(lái)對(duì)變量初始化。
__new__方法默認(rèn)返回實(shí)例對(duì)象供__init__方法、實(shí)例方法使用。
__init__ 方法為初始化方法,為類(lèi)的實(shí)例提供一些屬性或完成一些動(dòng)作。
__new__方法創(chuàng)建實(shí)例對(duì)象供__init__方法使用,__init__方法定制實(shí)例對(duì)象。
__new__ 方法必須返回值,__init__方法不需要返回值。(如果返回非None值就報(bào)錯(cuò))
一般用不上__new__方法
10.__repr__定制類(lèi)的用法
通常情況下
In [1]: class A(object): ...: pass ...:In [2]: a=A()In [3]: print a<__main__.A object at 0x03CB0FD0>
我們可以修改輸出a的表達(dá)式,例如:
In [4]: from PIL import Image ...: ...: a=Image.Image() ...:In [5]: print a<PIL.Image.Image image mode= size=0x0 at 0x3CB2190>
查看Image類(lèi),發(fā)現(xiàn)__repre__方法被重寫(xiě)了
def __repr__(self): return "<%s.%s image mode=%s size=%dx%d at 0x%X>" % ( self.__class__.__module__, self.__class__.__name__, self.mode, self.size[0], self.size[1], id(self))11.類(lèi)屬性和實(shí)例屬性
一.類(lèi)屬性和實(shí)例屬性
當(dāng)有類(lèi)屬性時(shí),再定義實(shí)例屬性是因?yàn)閷?shí)例屬性的變量名指向了新的值,類(lèi)屬性并沒(méi)有被改變。說(shuō)到底就是類(lèi)對(duì)象和實(shí)例對(duì)象一般不共有同一屬性和方法。
class Person(object): """定義一個(gè)具有類(lèi)屬性的類(lèi)""" _die = True def __init__(self, age): self.age = age #age不是類(lèi)屬性pa = Person(20)print(Person._die, id(Person._die))#輸出值:True 1531278160print(pa._die, id(pa._die))#輸出值:True 1531278160pa._die = 1 print(pa._die, id(pa._die))#輸出值:1 1531459344del pa._dieprint(pa._die, id(pa._die))#輸出值:True 1531278160class Test(object): name = 'scolia'a = Test()a.name = 'scolia good' # 通過(guò)實(shí)例進(jìn)行修改,此時(shí)實(shí)例的name屬性是重新創(chuàng)建的,與類(lèi)的name屬性不是同一個(gè)print(Test.name) #scoliaprint(a.name) #scolia good
本質(zhì):當(dāng)函數(shù)內(nèi)訪(fǎng)問(wèn)變量時(shí),會(huì)先在函數(shù)內(nèi)部查詢(xún)有沒(méi)有這個(gè)變量,如果沒(méi)有,就到外層中找。這里的情況是我在實(shí)例中訪(fǎng)問(wèn)一個(gè)屬性,但是我實(shí)例中沒(méi)有,我就試圖去創(chuàng)建我的類(lèi)中尋找有沒(méi)有這個(gè)屬性。找到了,就有,沒(méi)找到,就拋出異常。當(dāng)我去賦值實(shí)例對(duì)象中沒(méi)有的變量時(shí),其實(shí)就是對(duì)該對(duì)象創(chuàng)建一個(gè)變量,這是由于python是動(dòng)態(tài)語(yǔ)言決定的。而當(dāng)我試圖用實(shí)例去修改一個(gè)在類(lèi)中不可變的屬性的時(shí)候,我實(shí)際上并沒(méi)有修改,而是在我的實(shí)例中創(chuàng)建了這個(gè)屬性。而當(dāng)我再次訪(fǎng)問(wèn)這個(gè)屬性的時(shí)候,我實(shí)例中有,就不用去類(lèi)中尋找了。
如果用一張圖來(lái)表示的話(huà):
image
關(guān)于類(lèi)與實(shí)例對(duì)象的深處理解:
創(chuàng)建的實(shí)例對(duì)象不包含任何的屬性(構(gòu)造函數(shù)除外),但是跟成員方法綁定。
訪(fǎng)問(wèn)的時(shí)候是訪(fǎng)問(wèn)類(lèi)對(duì)象中的屬性
賦值的時(shí)候,實(shí)例對(duì)象重新創(chuàng)建變量名
類(lèi)訪(fǎng)問(wèn)成員方法時(shí),必須帶上綁定的實(shí)例對(duì)象,即<類(lèi).方法(實(shí)例對(duì)象)>
類(lèi)方法(@classmethod)和成員方法的區(qū)別??? 類(lèi).方法()直接訪(fǎng)問(wèn)
In [25]: class A(object): ...: m=1 ...: n=2 ...:In [26]: a=A()In [27]: A.__dict__Out[27]:dict_proxy({'__dict__': <attribute '__dict__' of '__doc__': None, '__module__': '__main__', '__weakref__': <attribute '__weakref_ 'm': 1, 'n': 2})In [28]: a.__dict__Out[28]: {}In [29]: A.mOut[29]: 1In [30]: a.mOut[30]: 1In [31]: hex(id(A.m))Out[31]: '0x1e0a5d8'In [32]: hex(id(a.m))Out[32]: '0x1e0a5d8'In [33]: a.m=12 #賦值之后不在是同一個(gè)mIn [35]: hex(id(a.m))Out[35]: '0x1e0a554'In [36]: hex(id(A.m))Out[36]: '0x1e0a5d8'
兩種寫(xiě)法的區(qū)別:
In [53]: class Kls(object): ...: no_inst = 0 ...: def __init__(self): ...: Kls.no_inst = Kls.no_inst + 1 ...:In [54]: k1=Kls()In [55]: k1.__dict__Out[55]: {}In [56]: class Kls(object): ...: no_inst = 0 ...: def __init__(self): ...: self._inst = self.no_inst + 1 ...:In [57]: k1=Kls()In [58]: k1.__dict__Out[58]: {'_inst': 1}12.@classmethod和@staticmethod
一般來(lái)說(shuō),要使用某個(gè)類(lèi)的方法,需要先實(shí)例化一個(gè)對(duì)象再調(diào)用方法。而使用@staticmethod或@classmethod,就可以不需要實(shí)例化,直接類(lèi)名.方法名()來(lái)調(diào)用。這有利于組織代碼,把某些應(yīng)該屬于某個(gè)類(lèi)的函數(shù)給放到那個(gè)類(lèi)里去,同時(shí)有利于命名空間的整潔。
既然@staticmethod和@classmethod都可以直接類(lèi)名.方法名()來(lái)調(diào)用,那他們有什么區(qū)別呢?從它們的使用上來(lái)看
@staticmethod不需要表示自身對(duì)象的self和自身類(lèi)的cls參數(shù),就跟使用函數(shù)一樣。類(lèi)似于全局函數(shù)。
@classmethod也不需要self參數(shù),但第一個(gè)參數(shù)需要是表示自身類(lèi)的cls參數(shù)。
@classmethod 是一個(gè)函數(shù)修飾符,它表示接下來(lái)的是一個(gè)類(lèi)方法,而對(duì)于平常我們見(jiàn)到的則叫做實(shí)例方法。 類(lèi)方法的第一個(gè)參數(shù)cls,而實(shí)例方法的第一個(gè)參數(shù)是self,表示該類(lèi)的一個(gè)實(shí)例。普通對(duì)象方法至少需要一個(gè)self參數(shù),代表類(lèi)對(duì)象實(shí)例。
類(lèi)方法有類(lèi)變量cls傳入,從而可以用cls做一些相關(guān)的處理。并且有子類(lèi)繼承時(shí),調(diào)用該類(lèi)方法時(shí),傳入的類(lèi)變量cls是子類(lèi),而非父類(lèi)。 對(duì)于類(lèi)方法,可以通過(guò)類(lèi)來(lái)調(diào)用,就像Test.foo()
如果要調(diào)用@staticmethod方法,通過(guò)類(lèi)對(duì)象或?qū)ο髮?shí)例調(diào)用;而@classmethod因?yàn)槌钟衏ls參數(shù),可以來(lái)調(diào)用類(lèi)的屬性,類(lèi)的方法,實(shí)例化對(duì)象等,避免硬編碼。==@classmethod最常見(jiàn)的用途是定義備選構(gòu)造方法==
下面上代碼。
class A(object): bar = 1 #這是類(lèi)的屬性,而非實(shí)例的屬性 def foo(self): #這是實(shí)例的方法,注意只要帶有self就是實(shí)例的方法或者屬性 print 'foo' @staticmethod def static_foo(): print 'static_foo' print A.bar @classmethod def class_foo(cls): print 'class_foo' print cls.bar print cls().foo()A.static_foo()A.class_foo()============================================out:static_foo1class_foo1foo
@classmethod means: when this method is called, we pass the class as the first argument instead of the instance of that class (as we normally do with methods). This means you can use the class and its properties inside that method rather than a particular instance.
@staticmethod means: when this method is called, we don't pass an instance of the class to it (as we normally do with methods). This means you can put a function inside a class but you can't access the instance of that class (this is useful when your method does not use the instance).
13.assert的用法
在開(kāi)發(fā)一個(gè)程序時(shí)候,與其讓它運(yùn)行時(shí)崩潰,不如在它出現(xiàn)錯(cuò)誤條件時(shí)就崩潰(返回錯(cuò)誤)。這時(shí)候斷言assert 就顯得非常有用。
assert的語(yǔ)法:
assert expression1 [“,” expression2]
等價(jià)語(yǔ)句:
if not expression1: raise Exception(expression2)
example:
In [6]: x = 1In [7]: y = 2In [8]: assert x == y,'not equal’. #若x == y,則會(huì)繼續(xù)執(zhí)行下去----------------------------------------------------------AssertionError Traceback (most recent call last)<ipython-input-8-30354b60974a> in <module>()----> 1 assert x == y,'not equal'AssertionError: not equal14.format的用法
它通過(guò){}和:來(lái)代替%。
用法是:字符串.format(args)
1.通過(guò)位置
In [1]: '{0},{1}'.format('kzc',18) Out[1]: 'kzc,18'In [2]: '{},{}'.format('kzc',18) Out[2]: 'kzc,18'In [3]: '{1},{0},{1}'.format('kzc',18) Out[3]: '18,kzc,18'
字符串的format函數(shù)可以接受不限個(gè)參數(shù),位置可以不按順序,可以不用或者用多次,不過(guò)2.6不能為空{(diào)},2.7才可以。
2.通過(guò)關(guān)鍵字參數(shù)
In [5]: '{name},{age}'.format(age=18,name='kzc') Out[5]: 'kzc,18'
3.通過(guò)下標(biāo)
In [7]: p=['kzc',18]In [8]: '{0[0]},{0[1]}'.format(p)Out[8]: 'kzc,18'
4.格式限定符
它有著豐富的的“格式限定符”(語(yǔ)法是{}中帶:號(hào)),比如:
填充與對(duì)齊填充常跟對(duì)齊一起使用
^、<、>分別是居中、左對(duì)齊、右對(duì)齊,后面帶寬度
:號(hào)后面帶填充的字符,只能是一個(gè)字符,不指定的話(huà)默認(rèn)是用空格填充
比如
In [15]: '{:>8}'.format('189')Out[15]: ' 189'In [16]: '{:0>8}'.format('189')Out[16]: '00000189'In [17]: '{:a>8}'.format('189')Out[17]: 'aaaaa189'
精度與類(lèi)型f精度常跟類(lèi)型f一起使用
In [44]: '{:.2f}'.format(321.33345)Out[44]: '321.33'
其中.2表示長(zhǎng)度為2的精度,f表示float類(lèi)型。
其他類(lèi)型主要就是進(jìn)制了,b、d、o、x分別是二進(jìn)制、十進(jìn)制、八進(jìn)制、十六進(jìn)制。
In [54]: '{:b}'.format(17)Out[54]: '10001'In [55]: '{:d}'.format(17)Out[55]: '17'In [56]: '{:o}'.format(17)Out[56]: '21'In [57]: '{:x}'.format(17)Out[57]: '11'
用,號(hào)還能用來(lái)做金額的千位分隔符。
In [47]: '{:,}'.format(1234567890)Out[47]: '1,234,567,890'15.*和**收集參數(shù)以及解包的用法
在def函數(shù)時(shí),參數(shù)前的*和**:*收集其余的位置參數(shù),并返回一個(gè)元祖;**收集關(guān)鍵字參數(shù),并返回一個(gè)字典。關(guān)鍵字參數(shù)就是參數(shù)列表中有專(zhuān)門(mén)定義“參數(shù)=值”
例:
def print_params_2(title,*params1,**params2): print title print params1 print params2 print_params_2("params:",1,2,3,x=5,y=6)params:(1,2,3){‘x’:5,’y’:6}
解包:當(dāng)調(diào)用函數(shù)時(shí),將*args或**kwargs作為參數(shù)傳入
def foo(*args,**kwargs): print(args) print(kwargs) print(*args) print(*kwargs)foo(1,2,3,a=4,b=5)########################(1, 2, 3){'a': 4, 'b': 5}1 2 3a b
當(dāng)定義函數(shù)時(shí),*和**是用來(lái)收集參數(shù)
當(dāng)調(diào)用函數(shù)時(shí),*是用來(lái)解包參數(shù)
16.zip的用法
zip(list1,list2)返回一個(gè)list,元素是list1和list2組成的元組
>>> zip([1,2,3],[6,7,8])[(1, 6), (2, 7), (3, 8)]>>> zip((1,2,3),(6,7,8))[(1, 6), (2, 7), (3, 8)]17.__closure__之閉包
def outlayer(): a = 30 b = 15 c = 20 print(hex(id(b))) print(hex(id(c))) def inlayer(x): return 2*x+b+c #引用外部變量b和c,b和c有時(shí)又叫做環(huán)境變量 #只可以訪(fǎng)問(wèn)環(huán)境變量,若要修改,需要加nonlocal return inlayer # return a function objecttemp = outlayer()print('='*10)print(outlayer.__closure__) #outlayer函數(shù)的__closure__為空print('='*10)print(temp.__closure__) #只包含b和c環(huán)境變量,a對(duì)于inlayer不是,因?yàn)闆](méi)有調(diào)用0x100275d000x100275da0==========None==========0x100275d000x100275da0(<cell at 0x1006c8ca8: int object at 0x100275d00>,<cell at 0x1006c8ee8: int object at 0x100275da0>)
一個(gè)函數(shù)inlayer和它的環(huán)境變量b,c合在一起,就構(gòu)成了一個(gè)閉包(closure)。在Python中,所謂的閉包是一個(gè)包含有環(huán)境變量取值的函數(shù)對(duì)象。環(huán)境變量取值被保存在函數(shù)對(duì)象的__closure__屬性中。
18.nonlocal的用法
nonlocal關(guān)鍵字用來(lái)在函數(shù)或其他作用域中使用(修改)外層(非全局)變量,一般出現(xiàn)在閉包或者裝飾器中。
def outlayer(): c = 20 def inlayer(x): nonlocal c #聲明使用上層函數(shù)中的c,不可以使用global c = c+1 print('c is', c) return 2*x+c return inlayer temp = outlayer()print(temp(5))c is 213119.Counter()簡(jiǎn)單統(tǒng)計(jì)個(gè)數(shù)
Counter是collections模塊中的一個(gè)類(lèi),可以用來(lái)簡(jiǎn)單統(tǒng)計(jì)容器中數(shù)據(jù)的個(gè)數(shù)
In [1]: from collections import CounterIn [2]: b=[1,2,3,4,5,0,6,3,4,6,0]In [3]: c = Counter(b)In [4]: cOut[5]: Counter({0: 2, 1: 1, 2: 1, 3: 2, 4: 2, 5: 1, 6: 2})20.mro搜索順序
MRO(Method Resolution Order)
class Root: pass class A(Root): pass class B(Root): pass class C(A, B): pass print(C.mro) # 輸出結(jié)果為: # (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Root'>, <class 'object'>)
MRO實(shí)際上是對(duì)繼承樹(shù)做層序遍歷的結(jié)果,把一棵帶有結(jié)構(gòu)的樹(shù)變成了一個(gè)線(xiàn)性的表,所以沿著這個(gè)列表一直往上, 就可以無(wú)重復(fù)的遍歷完整棵樹(shù), 也就解決了多繼承中的Diamond問(wèn)題。
20.super的用法
super是一個(gè)類(lèi)。調(diào)用super()這個(gè)方法時(shí),只是返回一個(gè)super對(duì)象,并不做其他的操作。然后對(duì)這個(gè)super對(duì)象進(jìn)行方法調(diào)用時(shí),發(fā)生的事情如下:
找到第一個(gè)參數(shù)的__mro__列表中的下一個(gè)直接定義了該方法的類(lèi)
該(父)類(lèi)調(diào)用方法,參數(shù)綁定的對(duì)象為子類(lèi)對(duì)象
class Root: def __init__(self): print('Root')class A(Root): def __init__(self): print('A1') super().__init__() # 等同于super(A, self).__init__(self) print('A2')a = A()A1RootA2
在A的構(gòu)造方法中,先調(diào)用super()得到一個(gè)super對(duì)象,然后向這個(gè)對(duì)象調(diào)用__init__方法,這時(shí)super對(duì)象會(huì)搜索A的__mro__列表,找到第一個(gè)定義了__init__方法的類(lèi), 于是就找到了Root, 然后調(diào)用Root.__init__(self),這里的self是super()的第二個(gè)參數(shù),是編譯器自動(dòng)填充的,也就是A的__init__的第一個(gè)參數(shù),這樣就完成__init__方法調(diào)用的分配。
注意: 在許多語(yǔ)言的繼承中,==子類(lèi)必須調(diào)用父類(lèi)的構(gòu)造方法,就是為了保證子類(lèi)的對(duì)象能夠填充上父類(lèi)的屬性!==而不是初始化一個(gè)父類(lèi)對(duì)象…。Python中就好多了,所謂的調(diào)用父類(lèi)構(gòu)造方法, 就是明明白白地把self傳給父類(lèi)的構(gòu)造方法,
如果沒(méi)有多繼承, super其實(shí)和通過(guò)父類(lèi)來(lái)調(diào)用方法差不多. 但, super還有個(gè)好處: 當(dāng)B繼承自A, 寫(xiě)成了A.__init__, 如果根據(jù)需要進(jìn)行重構(gòu)全部要改成繼承自 E,那么全部都得改一次! 這樣很麻煩而且容易出錯(cuò)! 而使用super()就不用一個(gè)一個(gè)改了(只需類(lèi)定義中改一改就好了)
補(bǔ)充:對(duì)面向?qū)ο蟮睦斫?div style="height:15px;">
其實(shí)我覺(jué)得Python里面這樣的語(yǔ)法更容易理解面向?qū)ο蟮谋举|(zhì), 比Java中隱式地傳this更容易理解。所謂函數(shù),就是一段代碼,接受輸入,返回輸出。所謂方法,就是一個(gè)函數(shù)有了一個(gè)隱式傳遞的參數(shù)。所以方法就是一段代碼,是類(lèi)的所有實(shí)例共享, 唯一不同的是各個(gè)實(shí)例調(diào)用的時(shí)候傳給方法的this 或者self不一樣而已。
構(gòu)造方法是什么呢?其實(shí)也是一個(gè)實(shí)例方法啊,它只有在對(duì)象生成了之后才能調(diào)用,所以Python中__init__方法的參數(shù)是self啊。調(diào)用構(gòu)造方法時(shí)其實(shí)已經(jīng)為對(duì)象分配了內(nèi)存, 構(gòu)造方法只是起到初始化的作用,也就是為這段內(nèi)存里面賦點(diǎn)初值而已。
Java中所謂的靜態(tài)變量其實(shí)也就是類(lèi)的變量, 其實(shí)也就是為類(lèi)也分配了內(nèi)存,里面存了這些變量,所以Python中的類(lèi)對(duì)象我覺(jué)得是很合理的,也比Java要直觀(guān)。至于靜態(tài)方法,那就與對(duì)象一點(diǎn)關(guān)系都沒(méi)有了,本質(zhì)就是個(gè)獨(dú)立的函數(shù),只不過(guò)寫(xiě)在了類(lèi)里面而已。而Python中的classmethod其實(shí)也是一種靜態(tài)方法,不過(guò)它會(huì)依賴(lài)于cls對(duì)象,這個(gè)cls就是類(lèi)對(duì)象,但是只要想用這個(gè)方法,類(lèi)對(duì)象必然是存在的,不像實(shí)例對(duì)象一樣需要手動(dòng)的實(shí)例化,所以classmethod也可以看做是一種靜態(tài)變量。而staticmethod就是真正的靜態(tài)方法了,是獨(dú)立的函數(shù),不依賴(lài)任何對(duì)象。
Java中的實(shí)例方法是必須依賴(lài)于對(duì)象存在的, 因?yàn)橐[式的傳輸this,如果對(duì)象不存在這個(gè)this也沒(méi)法隱式了。所以在靜態(tài)方法中是沒(méi)有this指針的,也就沒(méi)法調(diào)用實(shí)例方法。而Python中的實(shí)例方法是可以通過(guò)類(lèi)名來(lái)調(diào)用的。只不過(guò)因?yàn)檫@時(shí)候self沒(méi)辦法隱式傳遞,所以必須得顯式地傳遞。
22.__call__回調(diào)函數(shù)
一般來(lái)說(shuō),魔法方法都是隱式調(diào)用的
__call__()放在類(lèi)中定義。
python中,函數(shù)是一個(gè)對(duì)象
>>> f = abs #絕對(duì)值函數(shù)>>> f.__name__'abs'>>> f(-10)10
由于 f 可以被調(diào)用,所以,f 被稱(chēng)為可調(diào)用對(duì)象。所有的函數(shù)都是可調(diào)用對(duì)象。
一個(gè)類(lèi)實(shí)例也可以變成一個(gè)可調(diào)用對(duì)象,只需要實(shí)現(xiàn)一個(gè)特殊方法__call__()。
我們把 Person 類(lèi)變成一個(gè)可調(diào)用對(duì)象:
class Person(object): def __init__(self, name, gender): self.name = name self.gender = gender def __call__(self, friend): print 'My name is %s...' % self.name print 'My friend is %s...' % friend
現(xiàn)在可以對(duì) Person 實(shí)例直接調(diào)用:
>>> person = Person('Bob', 'male')>>> person('Tim')My name is Bob...My friend is Tim...
單看 person('Tim') 你無(wú)法確定 p 是一個(gè)函數(shù)還是一個(gè)類(lèi)實(shí)例,所以,在Python中,函數(shù)也是對(duì)象,對(duì)象和函數(shù)的區(qū)別并不顯著。
23.dict的get函數(shù)
get函數(shù)相比較于通過(guò)下標(biāo)的方式獲取值得好處就是,即使字典中沒(méi)有想要的key,也會(huì)根據(jù)get后面的參數(shù)輸出結(jié)果,對(duì)用戶(hù)友好;而下標(biāo)方式直接報(bào)錯(cuò)
>>> a = {'a':1,'b':2}>>> a.get('a')1>>> a.get('c',"None")'None'24.hasattr() getattr() setattr() 函數(shù)使用方法詳解
hasattr(object, name)
判斷一個(gè)對(duì)象里面是否有name屬性或者name方法,返回BOOL值,有name特性返回True, 否則返回False。
需要注意的是name要用括號(hào)括起來(lái)
>>> class test():... name="xiaohua"... def run(self):... return "HelloWord"...>>> t=test()>>> hasattr(t, "name") #判斷對(duì)象有name屬性True>>> hasattr(t, "run") #判斷對(duì)象有run方法True
getattr(object, name[,default])
獲取對(duì)象object的屬性或者方法,如果存在打印出來(lái),如果不存在,打印出默認(rèn)值,默認(rèn)值可選。
需要注意的是,如果是返回的對(duì)象的方法,返回的是方法的內(nèi)存地址,如果需要運(yùn)行這個(gè)方法,
可以在后面添加一對(duì)括號(hào)。
>>> class test():... name="xiaohua"... def run(self):... return "HelloWord"...>>> t=test()>>> getattr(t, "name") #獲取name屬性,存在就打印出來(lái)。'xiaohua'>>> getattr(t, "run") #獲取run方法,存在就打印出方法的內(nèi)存地址。<bound method test.run of <__main__.test instance at 0x0269C878>>>>> getattr(t, "run"()) #獲取run方法,后面加括號(hào)可以將這個(gè)方法運(yùn)行。'HelloWord'>>> getattr(t, "age") #獲取一個(gè)不存在的屬性。Traceback (most recent call last): File "<stdin>", line 1, in <module>AttributeError: test instance has no attribute 'age'>>> getattr(t, "age","18") #若屬性不存在,返回一個(gè)默認(rèn)值。'18'
setattr(object, name, values)
給對(duì)象的屬性賦值,若屬性不存在,先創(chuàng)建再賦值。
>>> class test():... name="xiaohua"... def run(self):... return "HelloWord"...>>> t=test()>>> hasattr(t, "age") #判斷屬性是否存在False>>> setattr(t, "age", "18") #為屬相賦值,并沒(méi)有返回值>>> hasattr(t, "age") #屬性存在了True
一種綜合的用法是:判斷一個(gè)對(duì)象的屬性是否存在,若不存在就添加該屬性。
>>> class test():... name="xiaohua"... def run(self):... return "HelloWord"...>>> t=test()>>> getattr(t, "age") #age屬性不存在Traceback (most recent call last): File "<stdin>", line 1, in <module>AttributeError: test instance has no attribute 'age'>>> getattr(t, "age", setattr(t, "age", "18")) #age屬性不存在時(shí),設(shè)置該屬性'18'>>> getattr(t, "age") #可檢測(cè)設(shè)置成功'18'25.object,type和metaclass的關(guān)系
type類(lèi)是元類(lèi),用來(lái)創(chuàng)建類(lèi)(對(duì)象)的,事實(shí)是調(diào)用了type的__new__創(chuàng)建的類(lèi)(對(duì)象)
__new__是用來(lái)創(chuàng)建實(shí)例對(duì)象的,如果沒(méi)有復(fù)寫(xiě)__new__方法,事實(shí)上是調(diào)用了object的__new__
metaclass關(guān)鍵字是用來(lái)定制元類(lèi)的,這樣子后續(xù)我們定義的類(lèi)就可以用定制的元類(lèi)來(lái)創(chuàng)建。經(jīng)典的運(yùn)用就是ORM
type也是繼承了object,并對(duì)object的__new__進(jìn)行了復(fù)寫(xiě)
父類(lèi)(對(duì)象)先創(chuàng)建,之后才是子類(lèi)(對(duì)象)
26.淺拷貝和深拷貝
普通的賦值在python中是淺拷貝,及傳遞的是對(duì)象的引用;若要深拷貝,必須導(dǎo)入copy模塊
copy.copy 淺拷貝 只拷貝父對(duì)象,不會(huì)拷貝對(duì)象的內(nèi)部的子對(duì)象。
copy.deepcopy 深拷貝 拷貝對(duì)象及其子對(duì)象
import copya = [1, 2, 3, 4, ['a', 'b']] #原始對(duì)象 b = a #賦值,傳對(duì)象的引用c = copy.copy(a) #對(duì)象拷貝,淺拷貝d = copy.deepcopy(a) #對(duì)象拷貝,深拷貝 a.append(5) #修改對(duì)象aa[4].append('c') #修改對(duì)象a中的['a', 'b']數(shù)組對(duì)象 print 'a = ', aprint 'b = ', bprint 'c = ', cprint 'd = ', d輸出結(jié)果: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']]
深拷貝在拷貝的時(shí)候,若拷貝對(duì)象里包含引用,不僅拷貝引用,而且把引用的內(nèi)容也再?gòu)?fù)制一份。理解深的含義。
27.私有屬性是可以訪(fǎng)問(wèn)的
python中定義私有屬性的方法是在變量名前加'_'或者'__'
_foo:一種約定,用來(lái)指定變量私有。程序員用來(lái)指定私有變量的一種方式。但是外部仍可直接訪(fǎng)問(wèn)
__foo:這個(gè)有真正的意義:解析器用_classname__foo來(lái)代替這個(gè)名字,以區(qū)別和其他類(lèi)相同的命名。
但是我們可以訪(fǎng)問(wèn)私有屬性,因?yàn)閜ython中私有屬性是通過(guò)名字重整的機(jī)制實(shí)現(xiàn)的,改變了私有屬性名,從而報(bào)出name defined異常。
In [19]: class A(): ...: def __init__(self): ...: self.__num=100 ...: In [20]: a=A()In [21]: a.__num---------------------------------------------------------------AttributeError: 'A' object has no attribute '__num'In [22]: dir(a)Out[22]: ['_A__num', '__class__',... '__weakref__']In [23]: a._A__numOut[23]: 10028.屬性攔截器__getattribute__
當(dāng)訪(fǎng)問(wèn)對(duì)象的屬性時(shí),其實(shí)先調(diào)用對(duì)象中的魔法方法__getattribute__
class Itcast(object): def __init__(self,subject1): self.subject1 = subject1 self.subject2 = 'cpp' #屬性訪(fǎng)問(wèn)時(shí)攔截器,打log def __getattribute__(self,obj): if obj == 'subject1': print('log subject1') return 'redirect python' else: #測(cè)試時(shí)注釋掉這2行,將找不到subject2 return object.__getattribute__(self,obj) def show(self): print('this is Itcast')s = Itcast("python")print(s.subject1)print(s.subject2)log subject1redirect pythoncpp29.對(duì)象中都是屬性,方法只不過(guò)是屬性的引用
In [24]: class A(): ...: pass ...: In [25]: a=A()In [26]: a.f---------------------------------------------------------------AttributeError: 'A' object has no attribute 'f'In [27]: a.f()---------------------------------------------------------------AttributeError: 'A' object has no attribute 'f'
所以當(dāng)調(diào)用a.f()的時(shí)候也會(huì)默認(rèn)執(zhí)行類(lèi)中__getattribute__方法
30.GIL全局解釋器鎖
從名字上看能告訴我們很多東西,很顯然,這是一個(gè)加在解釋器上的全局(從解釋器的角度看)鎖(從互斥或者類(lèi)似角度看)。GIL使得同一時(shí)刻只有一個(gè)線(xiàn)程在一個(gè)CPU上執(zhí)行字節(jié)碼,無(wú)法保證多個(gè)線(xiàn)程映射到多個(gè)CPU上執(zhí)行。對(duì)于任何Python程序,不管有多少的處理器,任何時(shí)候都總是只有一個(gè)線(xiàn)程在執(zhí)行。事實(shí)上,線(xiàn)程并不是完全運(yùn)行完成后釋放GIL,所以線(xiàn)程安全也是相對(duì)的
在Python多線(xiàn)程下,每個(gè)線(xiàn)程的執(zhí)行方式:
1.獲取GIL
2.執(zhí)行代碼直到sleep或者是python虛擬機(jī)將其掛起(虛擬機(jī)會(huì)根據(jù)執(zhí)行的字節(jié)碼行數(shù)以及時(shí)間片釋放GIL)或者遇到IO操作
3.釋放GIL
可見(jiàn),某個(gè)線(xiàn)程想要執(zhí)行,必須先拿到GIL,我們可以把GIL看作是“通行證”,并且在一個(gè)python進(jìn)程中,GIL只有一個(gè)。拿不到通行證的線(xiàn)程,就不允許進(jìn)入CPU執(zhí)行
解決辦法就是多進(jìn)程和協(xié)程(協(xié)程也只是單CPU,單線(xiàn)程但是能減小切換代價(jià)提升性能)。
31.當(dāng)函數(shù)的參數(shù)是list引發(fā)的問(wèn)題
==比較的是值的大??;is是比較地址???
32.上下文管理 __enter__,__exit__
應(yīng)用場(chǎng)景:
文件的讀寫(xiě)
數(shù)據(jù)庫(kù)的讀寫(xiě)操作
Flask的上下文管理
上下文管理協(xié)議:當(dāng)使用with語(yǔ)句時(shí),解釋器會(huì)自動(dòng)調(diào)用 __enter__,__exit__
class Sample: def __enter__(self): print('enter') #進(jìn)入資源 return self def __exit__(self, exc_type, exc_val, exc_tb): print('exit') #釋放資源 def do_something(self): print('do something')with Sample() as sample: sample.do_something()
輸出
enterdo somethingexit
進(jìn)入with語(yǔ)句,調(diào)用__enter__;退出with語(yǔ)句,調(diào)用__exit__
事實(shí)上sample并不是sample=Sample(),而是__enter__返回的對(duì)象,即如果__enter__沒(méi)有return,sample將為None。
exc_type:異常類(lèi)型;exc_val:異常值;exc_tb:traceback。如果with語(yǔ)句中有異常發(fā)生,__exit__中會(huì)收集這些異常信息。
33.__slots__
Python是一門(mén)動(dòng)態(tài)語(yǔ)言,可以在運(yùn)行過(guò)程中,修改實(shí)例的屬性和增刪方法。一般,任何類(lèi)的實(shí)例包含一個(gè)字典dict,Python通過(guò)這個(gè)字典可以將任意屬性綁定到實(shí)例上。有時(shí)候我們只想使用固定的屬性,而不想任意綁定屬性,這時(shí)候我們可以定義一個(gè)屬性名稱(chēng)集合,只有在這個(gè)集合里的名稱(chēng)才可以綁定。slots就是完成這個(gè)功能的。
class test_slots(object): __slots__='x','y' def printHello(self): print 'hello!' class test(object): def printHello(self): print 'hello' print dir(test_slots) #可以看到test_slots類(lèi)結(jié)構(gòu)里面包含__slots__,x,y print dir(test)#test類(lèi)結(jié)構(gòu)里包含__dict__ print '**************************************' ts=test_slots() t=test() print dir(ts) #可以看到ts實(shí)例結(jié)構(gòu)里面包含__slots__,x,y,不能任意綁定屬性 print dir(t) #t實(shí)例結(jié)構(gòu)里包含__dict__,可以任意綁定屬性 print '***************************************' ts.x=11 #只能綁定__slots__名稱(chēng)集合里的屬性 t.x=12 #可以任意綁定屬性 print ts.x,t.x ts.y=22 #只能綁定__slots__名稱(chēng)集合里的屬性 t.y=23 #可以任意綁定屬性 print ts.y,t.y #ts.z=33 #無(wú)法綁定__slots__集合之外的屬性(AttributeError: 'test_slots' object has no attribute 'z') t.z=34 #可以任意綁定屬性 print t.z
正如上面所說(shuō)的,默認(rèn)情況下,Python的新式類(lèi)和經(jīng)典類(lèi)的實(shí)例都有一個(gè)dict來(lái)存儲(chǔ)實(shí)例的屬性。這在一般情況下還不錯(cuò),而且非常靈活,乃至在程序中可以隨意設(shè)置新的屬性。但是,對(duì)一些在”編譯”前就知道有幾個(gè)固定屬性的小class來(lái)說(shuō),這個(gè)dict就有點(diǎn)浪費(fèi)內(nèi)存了。當(dāng)需要?jiǎng)?chuàng)建大量實(shí)例的時(shí)候,這個(gè)問(wèn)題變得尤為突出。一種解決方法是在新式類(lèi)中定義一個(gè)slots屬性。slots聲明中包含若干實(shí)例變量,并為每個(gè)實(shí)例預(yù)留恰好足夠的空間來(lái)保存每個(gè)變量;這樣Python就不會(huì)再使用dict,從而節(jié)省空間。
34.__unicode__,__str__
__unicode__,__str__類(lèi)似于java的toString方法。在java中,當(dāng)打印對(duì)象的時(shí)候,其實(shí)是調(diào)用了對(duì)象的toString方法。同樣,我們可以定制對(duì)象打印輸出的格式
unicode是用在python2里,而str是用在python3里
#不復(fù)寫(xiě)__str__方法,調(diào)用的是object的方法class A: def __init__(self,name,age): self.name=name self.age=agea=A('lu',25)print(a) #<__main__.A object at 0x101978cf8>#復(fù)寫(xiě)__str__方法,調(diào)用的是object的方法class A: def __init__(self,name,age): self.name=name self.age=age def __str__(self): return self.name + ' is ' + str(self.age) + ' years old'a=A('lu',25)print(a) #lu is 25 years old35.文件緩沖
36.for不可對(duì)可迭代對(duì)象進(jìn)行修改
在使用python的可迭代對(duì)象時(shí)有一條定律,那就是永遠(yuǎn)都不要對(duì)迭代對(duì)象進(jìn)行數(shù)據(jù)的修改。 我們可以用copy(),生成一個(gè)副本,對(duì)這個(gè)副本進(jìn)行遍歷。
In [10]: l = ['a','b','c','d']In [11]: for index,key in enumerate(l): ...: if key == 'c': ...: l.pop(index)In [12]: l['a', 'b', 'c'] ########推薦 for index,key in enumerate(l.copy()): ...: if key == 'c': ...: l.pop(index)37.__getitem__和 __setitem__方法來(lái)實(shí)現(xiàn)“[ ]”符號(hào)的使用
class Map: def __init__(self): self.Q_key = [] self.Q_value = [] def put(self, key, value): self.Q_key.append(key) self.Q_value.append(value) def get(self, key, default=None): for index,k in enumerate(self.Q_key): if k == key: return self.Q_value[index] return default def __getitem__(self, key): return self.get(key) def __setitem__(self, key, value): self.put(key, value)m = Map()m['a']=1m['b']=2print(m['b'])38.list的倒序切片
In [1]: l=[1,2,3,4,5,6]In [7]: l[-1:1:-1]Out[7]: [6, 5, 4, 3]In [8]: l[-1:0:-1]Out[8]: [6, 5, 4, 3, 2]In [9]: l[-1::-1]Out[9]: [6, 5, 4, 3, 2, 1]39.字節(jié)流與字符流
40.__name__的含義
#a.pyprint(__name__)
運(yùn)行a.py,執(zhí)行結(jié)果為_(kāi)_main__。
但如果對(duì)于一個(gè)b.py作為模塊在a中使用,b的__name__將變成對(duì)應(yīng)b的模塊名
#b.pyprint(__name__)#a.pyimport b
運(yùn)行a.py(如果運(yùn)行b.py,結(jié)果扔是main),執(zhí)行結(jié)果為b
所以為了測(cè)試模塊的相應(yīng)功能,而避免在主程序中運(yùn)行,通常我們需要在執(zhí)行的代碼中這樣寫(xiě)
if __name__ == '__main__': do something
這樣子就能很好的控制模塊的有效輸出
41.為什么不能用可變對(duì)象作為函數(shù)的默認(rèn)參數(shù)值
先來(lái)看一道題目:
>>> def func(numbers=[], num=1):... numbers.append(num)... return numbers>>> func()[1]>>> func()[1, 1]>>> func()[1, 1, 1]
我們似乎發(fā)現(xiàn)了一個(gè)Bug,每次用相同的方式調(diào)用函數(shù) func() 時(shí),返回結(jié)果竟然不一樣,而且每次返回的列表在不斷地變長(zhǎng)。
>>> id(func())4330472840>>> id(func())4330472840
從上面可以看出,函數(shù)的返回值其實(shí)是同一個(gè)列表對(duì)象,因?yàn)樗麄兊膇d值是一樣的,只不過(guò)是列表中的元素在變化。為什么會(huì)這樣呢?
這要從函數(shù)的特性說(shuō)起,在 Python 中,函數(shù)是第一類(lèi)對(duì)象(function is the first class object),換而言之,函數(shù)也是對(duì)象,跟整數(shù)、字符串一樣可以賦值給變量、當(dāng)做參數(shù)傳遞、還可以作為返回值。函數(shù)也有自己的屬性,比如函數(shù)的名字、函數(shù)的默認(rèn)參數(shù)列表。
# 函數(shù)的名字>>> func.__name__ 'func'# 函數(shù)的默認(rèn)參數(shù)列表>>> func.__defaults__ ([1, 1, 1, 1, 1], 1)
def是一條可執(zhí)行語(yǔ)句,Python 解釋器執(zhí)行 def 語(yǔ)句時(shí),就會(huì)在內(nèi)存中就創(chuàng)建了一個(gè)函數(shù)對(duì)象(此時(shí),函數(shù)里面的代碼邏輯并不會(huì)執(zhí)行,因?yàn)檫€沒(méi)調(diào)用嘛),在全局命名空間,有一個(gè)函數(shù)名(變量叫 func)會(huì)指向該函數(shù)對(duì)象,記住,至始至終,不管該函數(shù)調(diào)用多少次,函數(shù)對(duì)象只有一個(gè),就是function object,不會(huì)因?yàn)檎{(diào)用多次而出現(xiàn)多個(gè)函數(shù)對(duì)象。
image
函數(shù)對(duì)象生成之后,它的屬性:名字和默認(rèn)參數(shù)列表都將初始化完成。
image
初始化完成時(shí),屬性 __default__ 中的第一個(gè)默認(rèn)參數(shù) numbers 指向一個(gè)空列表。
當(dāng)函數(shù)第一次被調(diào)用時(shí),就是第一次執(zhí)行 func()時(shí),開(kāi)始執(zhí)行函數(shù)里面的邏輯代碼(此時(shí)函數(shù)不再需要初始化了),代碼邏輯就是往numbers中添加一個(gè)值為1的元素
image
第二次調(diào)用 func(),繼續(xù)往numbers中添加一個(gè)元素
image
第三次、四次依此類(lèi)推。
所以現(xiàn)在你應(yīng)該明白為什么調(diào)用同一個(gè)函數(shù),返回值確每次都不一樣了吧。因?yàn)樗麄児蚕淼氖峭粋€(gè)列表(numbers)對(duì)象,只是每調(diào)用一次就往該列表中增加了一個(gè)元素
如果我們顯示地指定 numbers 參數(shù),結(jié)果截然不同。
>>> func(numbers=[10, 11])[10, 11, 1]
image
因?yàn)閚umbers被重新賦值了,它不再指向原來(lái)初始化時(shí)的那個(gè)列表了,而是指向了我們傳遞過(guò)去的那個(gè)新列表對(duì)象,因此返回值變成了 [10, 11, 1]
那么我們應(yīng)該如何避免前面那種情況發(fā)生呢?就是不要用可變對(duì)象作為參數(shù)的默認(rèn)值。
正確方式:
>>> def func(numbers=None, num=1):... if numbers is None:... numbers = [num]... else:... numbers.append(num)... return numbers...>>> func()[1]>>> func()[1]>>> func()[1]
如果調(diào)用時(shí)沒(méi)有指定參數(shù),那么調(diào)用方法時(shí),默認(rèn)參數(shù) numbers 每次都被重新賦值了,所以,每次調(diào)用的時(shí)候numbers都將指向一個(gè)新的對(duì)象。這就是與前者的區(qū)別所在。
那么,是不是說(shuō)我們永遠(yuǎn)都不應(yīng)該用可變對(duì)象來(lái)作為參數(shù)的默認(rèn)值了嗎?并不是,既然Python有這樣的語(yǔ)法,就一定有他的應(yīng)用場(chǎng)景,就像 for … else 語(yǔ)法一樣。我們可以用可變對(duì)象來(lái)做緩存功能
例如:計(jì)算一個(gè)數(shù)的階乘時(shí)可以用一個(gè)可變對(duì)象的字典當(dāng)作緩存值來(lái)實(shí)現(xiàn)緩存,緩存中保存計(jì)算好的值,第二次調(diào)用的時(shí)候就無(wú)需重復(fù)計(jì)算,直接從緩存中拿。
def factorial(num, cache={}): if num == 0: return 1 if num not in cache: print('xxx') cache[num] = factorial(num - 1) * num return cache[num]print(factorial(4))print("-------")print(factorial(4))
輸出:
---第一次調(diào)用---xxxxxxxxxxxx24---第二次調(diào)用---24
第二次調(diào)用的時(shí)候,直接從 cache 中拿了值,所以,你說(shuō)用可變對(duì)象作為默認(rèn)值是 Python 的缺陷嗎?也并不是,對(duì)吧!你還是當(dāng)作一種特性來(lái)使用。
函數(shù)的基本注意點(diǎn)
1.函數(shù)基礎(chǔ)之globals()和locals()
函數(shù)名.__doc__,返回函數(shù)的解釋?zhuān)瑢?duì)于函數(shù)定義時(shí),可以將解釋通過(guò)字符串寫(xiě)到執(zhí)行語(yǔ)句中。
def abs(a=3) “this is new function“>>>abs.__doc__“this is new function“函數(shù)體執(zhí)行到return語(yǔ)句就結(jié)束,不管后面是否還有語(yǔ)句。
當(dāng)def一個(gè)函數(shù)時(shí),其實(shí)就是創(chuàng)建了一個(gè)函數(shù)對(duì)象,并將函數(shù)對(duì)象返回給函數(shù)名。
函數(shù)定義時(shí)的參數(shù)(a=2)可以通過(guò)函數(shù)對(duì)象的__defaults__查看
每次對(duì)函數(shù)的調(diào)用都創(chuàng)建了一個(gè)新的本地作用域。
作用域(命名空間),一個(gè)隱藏的字典(鍵為函數(shù)名,值為函數(shù)對(duì)象的地址)
globals()函數(shù)返回全局變量的字典;locals()函數(shù)返回局部變量的字典(根據(jù)當(dāng)前上下文來(lái)判斷,若處在全局的位置,等同于globals(),見(jiàn)下例)。locals()函數(shù)只有在函數(shù)執(zhí)行過(guò)程在才用意義,因?yàn)楹瘮?shù)調(diào)用完成后,局部變量會(huì)從棧中刪除,所以再用locals(),沒(méi)有局部變量的字典
>>> def test(arg): z = 1 print locals()>>> test(4){'z': 1, 'arg': 4}>>> test('doulaixuexi'){'z': 1, 'arg': 'doulaixuexi'}>>> print(locals())# 輸出與globals()一樣的結(jié)果>>> print(globals())在函數(shù)的定義中可以訪(fǎng)問(wèn)全局變量(依據(jù)LEGB原則搜索變量),但不可以修改全局變量,除非使用global聲明變量
a = 3def test(arg): z = 1 global a a = 100test(4)print(a)#100vars()函數(shù)返回在當(dāng)前一個(gè)作用域(命名空間)的字典
補(bǔ)充:
Python使用叫做名字空間的東西來(lái)記錄變量的軌跡。名字空間只是一個(gè) 字典,它的鍵字就是變量名,字典的值就是那些變量的值。實(shí)際上,名字空間可以象Python的字典一樣進(jìn)行訪(fǎng)問(wèn),一會(huì)我們就會(huì)看到。
在一個(gè)Python程序中的任何一個(gè)地方,都存在幾個(gè)可用的名字空間。每個(gè)函數(shù)都有著自已的名字空間,叫做局部名字空間,它記錄了函數(shù)的變量,包括 函數(shù)的參數(shù)和局部定義的變量。每個(gè)模塊擁有它自已的名字空間,叫做全局名字空間,它記錄了模塊的變量,包括函數(shù)、類(lèi)、其它導(dǎo)入的模塊、模塊級(jí)的變量和常 量。還有就是內(nèi)置名字空間,任何模塊均可訪(fǎng)問(wèn)它,它存放著內(nèi)置的函數(shù)和異常。
2.LEGB命名空間
當(dāng)一行代碼要使用變量x的值時(shí),Python會(huì)到所有可用的名字空間去查找變量,按照如下順序:
1. Local:局部名字空間特指當(dāng)前函數(shù)或類(lèi)的方法。如果函數(shù)定義了一個(gè)局部變量 x ,Python將使用這個(gè)變量,然后停止搜索。2. Enclosing:外部嵌套函數(shù)的命名空間(見(jiàn)閉包)3. Global:全局名字空間特指當(dāng)前的模塊。如果模塊定義了一個(gè)名為 x 的變量,函數(shù)或類(lèi),Python將使用這個(gè)變量然后停止搜索。4. Builtin:內(nèi)置名字空間對(duì)每個(gè)模塊都是全局的。作為最后的嘗試,Python將假設(shè) x 是內(nèi)置函數(shù)或變量。
如果Python在這些名字空間找不到x,它將放棄查找并引發(fā)一個(gè) NameError 的異常,同時(shí)傳遞There is no variable named 'x' 這樣一條信息。
像Python中的許多事情一樣,名字空間在運(yùn)行時(shí)直接可以訪(fǎng)問(wèn)。特別地,局部名字空間可以通過(guò)內(nèi)置的 locals 函數(shù)來(lái)訪(fǎng)問(wèn)。全局(模塊級(jí)別)名字空間可以通過(guò) globals 函數(shù)來(lái)訪(fǎng)問(wèn)。
變量名解析:LEGB
Python中一切都是對(duì)象。每個(gè)對(duì)象都有一個(gè)名字,名字位于名字空間中,用名字變量引用對(duì)象。對(duì)象存在于內(nèi)存中一段空間。每個(gè)對(duì)象在內(nèi)存中都有地址。
在Python里類(lèi)型本身是對(duì)象,和實(shí)例對(duì)象一樣儲(chǔ)存在堆中,對(duì)于解釋器來(lái)說(shuō)類(lèi)對(duì)象和實(shí)例對(duì)象沒(méi)有根本上的別。
所謂“定義一個(gè)函數(shù)”,實(shí)際上也就是生成一個(gè)函數(shù)對(duì)象。而“定義一個(gè)方法”就是生成一個(gè)函數(shù)對(duì)象,并把這個(gè)對(duì)象放在一個(gè)類(lèi)的__dict__中。
函數(shù)對(duì)象結(jié)構(gòu)定義:
typedef struct { PyObject_HEAD PyObject *func_code; // PyCodeObject PyObject *func_globals; // 所在模塊的全局名字空間 PyObject *func_defaults; // 參數(shù)默認(rèn)值列表 PyObject *func_closure; // 閉包列表 PyObject *func_doc; // __doc__ PyObject *func_name; // __name__ PyObject *func_dict; // __dict__ PyObject *func_weakreflist; // 弱引用鏈表 PyObject *func_module; // 所在 Module} PyFunctionObject;對(duì)象基本上可以看做數(shù)據(jù)(特性)以及由一系列可以存取,操作這些數(shù)據(jù)的方法所組成的集合
所有的對(duì)象都屬于某一個(gè)類(lèi),稱(chēng)為類(lèi)的實(shí)例
self指的是實(shí)例對(duì)象本身
查看a是否是b的子類(lèi),可以使用issubclass(子類(lèi),超類(lèi));查看a的基類(lèi)a.__bases__
匿名函數(shù)lambda 用法lambda argument1,argument2:expression using argument.功能類(lèi)似于def,只是不需要再去定義一個(gè)函數(shù)名。結(jié)果返回的是一個(gè)函數(shù)對(duì)象。
def add1(a,b): return a+badd1(2,3)=> 5g = lambda a,b:a+bg(2,3)=>53.閉包
內(nèi)層函數(shù)引用了外層函數(shù)的變量(包括它的參數(shù)),然后返回內(nèi)層函數(shù)的情況,這就是閉包。形式上,在函數(shù)的內(nèi)部定義函數(shù),并引用外部變量。
函數(shù)對(duì)象是使用def語(yǔ)句定義的,函數(shù)對(duì)象的作用域與def所在的層級(jí)相同。比如下面代碼,我們?cè)趌ine_conf函數(shù)的隸屬范圍內(nèi)定義的函數(shù)line,就只能在line_conf的隸屬范圍內(nèi)調(diào)用。
def line_conf(): def line(x): return 2*x+1 print(line(5)) # within the scopeline_conf() #結(jié)果11print(line(5)) # 錯(cuò)誤,不能再外部調(diào)用內(nèi)部函數(shù)line
可以在內(nèi)部函數(shù)line()的定義中引用(非修改)外部的變量
def line_conf(): b = 15 def line(x): return 2*x+b #引用外部變量b,b有時(shí)又叫做環(huán)境變量 return line # return a function objectmy_line = line_conf()print(my_line(5)) #結(jié)果為25print(line_conf()(5)) #另一種引用方式,結(jié)果為25
一個(gè)函數(shù)line和它的環(huán)境變量b合在一起,就構(gòu)成了一個(gè)閉包(closure)。在Python中,所謂的閉包是一個(gè)包含有環(huán)境變量取值的函數(shù)對(duì)象。環(huán)境變量取值被保存在函數(shù)對(duì)象的__closure__屬性中
在python2.X中不能對(duì)外部變量賦值,而在python3中增加了nonlocal關(guān)鍵字
def Fun1(): x=5 def Fun2(): x=x*x return x return Fun2()Fun1() #錯(cuò)誤
為什么需要閉包?
def line_conf(a, b): def line(x): return ax + b return lineline1 = line_conf(1, 1)line2 = line_conf(4, 5)print(line1(5), line2(5))
這個(gè)例子中,函數(shù)line與環(huán)境變量a,b構(gòu)成閉包。在創(chuàng)建閉包的時(shí)候,我們通過(guò)line_conf的參數(shù)a,b說(shuō)明了這兩個(gè)環(huán)境變量的取值,這樣,我們就確定了函數(shù)的最終形式(y = x + 1和y = 4x + 5)。我們只需要變換參數(shù)a,b,就可以獲得不同的直線(xiàn)表達(dá)函數(shù)。由此,我們可以看到,閉包也具有提高代碼可復(fù)用性的作用。
如果沒(méi)有閉包,我們需要每次創(chuàng)建直線(xiàn)函數(shù)的時(shí)候同時(shí)說(shuō)明a,b,x。這樣,我們就需要更多的參數(shù)傳遞,也減少了代碼的可移植性。利用閉包,我們實(shí)際上創(chuàng)建了泛函。line函數(shù)定義一種廣泛意義的函數(shù)。這個(gè)函數(shù)的一些方面已經(jīng)確定(必須是直線(xiàn)),但另一些方面(比如a和b參數(shù)待定)。隨后,我們根據(jù)line_conf傳遞來(lái)的參數(shù),通過(guò)閉包的形式,將最終函數(shù)確定下來(lái)。