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}復雜用法
>>> [(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ǔ)句if 1 == 1: name = 'wupeiqi'else: name = 'alex' # 三元運算name = 'wupeiqi' if 1 == 1 else 'alex'3.map,reduce,filter,sorted函數的用法
map()函數接收兩個(gè)參數,一個(gè)是函數名,一個(gè)是序列,map將傳入的函數依次作用到序列的每個(gè)元素,并把結果作為新的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結合使用,寫(xiě)出精致而高效的語(yǔ)句
reduce()把一個(gè)函數作用在一個(gè)序列[x1, x2, x3...]上,這個(gè)函數必須接收兩個(gè)參數,reduce把結果繼續和序列的下一個(gè)元素做累積計算,結果返回的是一個(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è)函數名和一個(gè)序列。和map()不同的是,filter()把傳入的函數依次作用于每個(gè)元素,然后根據返回值是True還是False決定保留還是丟棄該元素。
例如,在一個(gè)list中,刪掉偶數,只保留奇數,可以這么寫(xiě):
def is_odd(n): return n % 2 == 1filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15])# 結果: [1, 5, 9, 15]sorted(iterable[,cmp,[,key[,reverse=True]]])
iterable包括字符串,序列,字典等可迭代對象,默認情況下reverse=True(升序),reverse=False為降序
sorted與sort的區別???
用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]
綜合練習
num = input('輸入一個(gè)整數:')#將整數轉換成字符串s = str(num)#定義map參數函數def f(s): #字符與數字字典 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參數函數def add(x,y): return x + y#調用map()函數,將字符串轉換成對應數字序列,并打印s = map(f,s)print "輸入整數%d的組成數字為%s"%(num,s),#調用reduce函數,對數字序列求和,并打印Sum = reduce(add,s)print "其和為:%d"%Sum4.lambda匿名函數
lambda arg:expression# 定義函數(普通方式)def func(arg): return arg + 1# 執行函數result = func(123)# ###################### lambda ####################### 定義函數(lambda表達式)my_lambda = lambda arg : arg + 1# 執行函數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è)同名對象存在,這個(gè)對象是被覆蓋的。
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中變量名里存儲的是對象的地址(引用),這區別于c語(yǔ)言
a=10時(shí)的內存示意圖
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] #賦值相當于重新綁定對象 | 變量m,當把a傳給m時(shí),m引用了a | 的內容,但是對m賦值的時(shí)候,ma = [4,5] | 指向了新值,不影響a對應的值。f(a) |print(a) #[4,5] |def f(m): m.append(4)a = [4,5]f(a)print(a) #[4,5,4]
python函數參數傳遞是傳對象或者說(shuō)是傳對象的引用。函數參數在傳遞的過(guò)程中將整個(gè)對象傳入,對可變對象的修改在函數外部以及內部都可見(jiàn),調用者和被調用者之間共享這個(gè)對象,而對于不可變對象,由于并不能真正被修改,因此,修改往往是通過(guò)生成一個(gè)新對象然后賦值實(shí)現的?!?1條建議之31》
當一個(gè)引用傳遞給函數的時(shí)候,函數自動(dòng)復制一份引用,這個(gè)函數里的引用和外邊的引用沒(méi)有半毛關(guān)系了.所以第一個(gè)例子里函數把引用指向了一個(gè)不可變對象,當函數返回的時(shí)候,外面的引用沒(méi)半毛感覺(jué).而第二個(gè)例子就不一樣了,函數內的引用指向的是可變對象,對它的操作就和定位了指針地址一樣,在內存里進(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__的區別
按字面意思理解就可以。 __new__ 用于創(chuàng )建對象,而 __init__ 是在創(chuàng )建完成之后初始化對象狀態(tài)。Python 中只不過(guò)是把創(chuàng )建對象和初始化這兩個(gè)概念分開(kāi)了,而其他語(yǔ)言中直接用一個(gè)構造函數解決。
class A(object): def __new__(cls, *args, **kwargs): print cls print hex(id(cls)) #cls就是類(lèi)對象A # print args # print kwargs print "===========================================" instance = object.__new__(cls, *args, **kwargs) #instance就是實(shí)例對象a1 print hex(id(instance)) return instance #若沒(méi)有return語(yǔ)句,則無(wú)法創(chuàng )建實(shí)例對象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
從上例看出,對象實(shí)例化的內部過(guò)程:
當執行a1=A(),從上例看出,對象實(shí)例化的內部過(guò)程:當執行a1=A(),首先調用__new__方法,由于__new__方法返回的是一個(gè)實(shí)例對象(a1),故相當于創(chuàng )建了一個(gè)實(shí)例對象,再次調用__init__方法,來(lái)對變量初始化,再次調用__init__方法,來(lái)對變量初始化。
__new__方法默認返回實(shí)例對象供__init__方法、實(shí)例方法使用。
__init__ 方法為初始化方法,為類(lèi)的實(shí)例提供一些屬性或完成一些動(dòng)作。
__new__方法創(chuàng )建實(shí)例對象供__init__方法使用,__init__方法定制實(shí)例對象。
__new__ 方法必須返回值,__init__方法不需要返回值。(如果返回非None值就報錯)
一般用不上__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的表達式,例如:
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ā)現__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í)例屬性
當有類(lèi)屬性時(shí),再定義實(shí)例屬性是因為實(shí)例屬性的變量名指向了新的值,類(lèi)屬性并沒(méi)有被改變。說(shuō)到底就是類(lèi)對象和實(shí)例對象一般不共有同一屬性和方法。
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ì):當函數內訪(fǎng)問(wèn)變量時(shí),會(huì )先在函數內部查詢(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)找到,就拋出異常。當我去賦值實(shí)例對象中沒(méi)有的變量時(shí),其實(shí)就是對該對象創(chuàng )建一個(gè)變量,這是由于python是動(dòng)態(tài)語(yǔ)言決定的。而當我試圖用實(shí)例去修改一個(gè)在類(lèi)中不可變的屬性的時(shí)候,我實(shí)際上并沒(méi)有修改,而是在我的實(shí)例中創(chuàng )建了這個(gè)屬性。而當我再次訪(fǎng)問(wèn)這個(gè)屬性的時(shí)候,我實(shí)例中有,就不用去類(lèi)中尋找了。
如果用一張圖來(lái)表示的話(huà):
image
關(guān)于類(lèi)與實(shí)例對象的深處理解:
創(chuàng )建的實(shí)例對象不包含任何的屬性(構造函數除外),但是跟成員方法綁定。
訪(fǎng)問(wèn)的時(shí)候是訪(fǎng)問(wèn)類(lèi)對象中的屬性
賦值的時(shí)候,實(shí)例對象重新創(chuàng )建變量名
類(lèi)訪(fǎng)問(wèn)成員方法時(shí),必須帶上綁定的實(shí)例對象,即<類(lèi).方法(實(shí)例對象)>
類(lèi)方法(@classmethod)和成員方法的區別??? 類(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ě)法的區別:
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è)對象再調用方法。而使用@staticmethod或@classmethod,就可以不需要實(shí)例化,直接類(lèi)名.方法名()來(lái)調用。這有利于組織代碼,把某些應該屬于某個(gè)類(lèi)的函數給放到那個(gè)類(lèi)里去,同時(shí)有利于命名空間的整潔。
既然@staticmethod和@classmethod都可以直接類(lèi)名.方法名()來(lái)調用,那他們有什么區別呢?從它們的使用上來(lái)看
@staticmethod不需要表示自身對象的self和自身類(lèi)的cls參數,就跟使用函數一樣。類(lèi)似于全局函數。
@classmethod也不需要self參數,但第一個(gè)參數需要是表示自身類(lèi)的cls參數。
@classmethod 是一個(gè)函數修飾符,它表示接下來(lái)的是一個(gè)類(lèi)方法,而對于平常我們見(jiàn)到的則叫做實(shí)例方法。 類(lèi)方法的第一個(gè)參數cls,而實(shí)例方法的第一個(gè)參數是self,表示該類(lèi)的一個(gè)實(shí)例。普通對象方法至少需要一個(gè)self參數,代表類(lèi)對象實(shí)例。
類(lèi)方法有類(lèi)變量cls傳入,從而可以用cls做一些相關(guān)的處理。并且有子類(lèi)繼承時(shí),調用該類(lèi)方法時(shí),傳入的類(lèi)變量cls是子類(lèi),而非父類(lèi)。 對于類(lèi)方法,可以通過(guò)類(lèi)來(lái)調用,就像Test.foo()
如果要調用@staticmethod方法,通過(guò)類(lèi)對象或對象實(shí)例調用;而@classmethod因為持有cls參數,可以來(lái)調用類(lèi)的屬性,類(lèi)的方法,實(shí)例化對象等,避免硬編碼。==@classmethod最常見(jiàn)的用途是定義備選構造方法==
下面上代碼。
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í)候,與其讓它運行時(shí)崩潰,不如在它出現錯誤條件時(shí)就崩潰(返回錯誤)。這時(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ì )繼續執行下去----------------------------------------------------------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函數可以接受不限個(gè)參數,位置可以不按順序,可以不用或者用多次,不過(guò)2.6不能為空{},2.7才可以。
2.通過(guò)關(guān)鍵字參數
In [5]: '{name},{age}'.format(age=18,name='kzc') Out[5]: 'kzc,18'
3.通過(guò)下標
In [7]: p=['kzc',18]In [8]: '{0[0]},{0[1]}'.format(p)Out[8]: 'kzc,18'
4.格式限定符
它有著(zhù)豐富的的“格式限定符”(語(yǔ)法是{}中帶:號),比如:
填充與對齊填充常跟對齊一起使用
^、<、>分別是居中、左對齊、右對齊,后面帶寬度
:號后面帶填充的字符,只能是一個(gè)字符,不指定的話(huà)默認是用空格填充
比如
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表示長(chá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'
用,號還能用來(lái)做金額的千位分隔符。
In [47]: '{:,}'.format(1234567890)Out[47]: '1,234,567,890'15.*和**收集參數以及解包的用法
在def函數時(shí),參數前的*和**:*收集其余的位置參數,并返回一個(gè)元祖;**收集關(guān)鍵字參數,并返回一個(gè)字典。關(guān)鍵字參數就是參數列表中有專(zhuān)門(mén)定義“參數=值”
例:
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}
解包:當調用函數時(shí),將*args或**kwargs作為參數傳入
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
當定義函數時(shí),*和**是用來(lái)收集參數
當調用函數時(shí),*是用來(lái)解包參數
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函數的__closure__為空print('='*10)print(temp.__closure__) #只包含b和c環(huán)境變量,a對于inlayer不是,因為沒(méi)有調用0x100275d000x100275da0==========None==========0x100275d000x100275da0(<cell at 0x1006c8ca8: int object at 0x100275d00>,<cell at 0x1006c8ee8: int object at 0x100275da0>)
一個(gè)函數inlayer和它的環(huán)境變量b,c合在一起,就構成了一個(gè)閉包(closure)。在Python中,所謂的閉包是一個(gè)包含有環(huán)境變量取值的函數對象。環(huán)境變量取值被保存在函數對象的__closure__屬性中。
18.nonlocal的用法
nonlocal關(guān)鍵字用來(lái)在函數或其他作用域中使用(修改)外層(非全局)變量,一般出現在閉包或者裝飾器中。
def outlayer(): c = 20 def inlayer(x): nonlocal c #聲明使用上層函數中的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)單統計個(gè)數
Counter是collections模塊中的一個(gè)類(lèi),可以用來(lái)簡(jiǎn)單統計容器中數據的個(gè)數
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) # 輸出結果為: # (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Root'>, <class 'object'>)
MRO實(shí)際上是對繼承樹(shù)做層序遍歷的結果,把一棵帶有結構的樹(shù)變成了一個(gè)線(xiàn)性的表,所以沿著(zhù)這個(gè)列表一直往上, 就可以無(wú)重復的遍歷完整棵樹(shù), 也就解決了多繼承中的Diamond問(wèn)題。
20.super的用法
super是一個(gè)類(lèi)。調用super()這個(gè)方法時(shí),只是返回一個(gè)super對象,并不做其他的操作。然后對這個(gè)super對象進(jìn)行方法調用時(shí),發(fā)生的事情如下:
找到第一個(gè)參數的__mro__列表中的下一個(gè)直接定義了該方法的類(lèi)
該(父)類(lèi)調用方法,參數綁定的對象為子類(lèi)對象
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(yíng)的構造方法中,先調用super()得到一個(gè)super對象,然后向這個(gè)對象調用__init__方法,這時(shí)super對象會(huì )搜索A的__mro__列表,找到第一個(gè)定義了__init__方法的類(lèi), 于是就找到了Root, 然后調用Root.__init__(self),這里的self是super()的第二個(gè)參數,是編譯器自動(dòng)填充的,也就是A的__init__的第一個(gè)參數,這樣就完成__init__方法調用的分配。
注意: 在許多語(yǔ)言的繼承中,==子類(lèi)必須調用父類(lèi)的構造方法,就是為了保證子類(lèi)的對象能夠填充上父類(lèi)的屬性!==而不是初始化一個(gè)父類(lèi)對象…。Python中就好多了,所謂的調用父類(lèi)構造方法, 就是明明白白地把self傳給父類(lèi)的構造方法,
如果沒(méi)有多繼承, super其實(shí)和通過(guò)父類(lèi)來(lái)調用方法差不多. 但, super還有個(gè)好處: 當B繼承自A, 寫(xiě)成了A.__init__, 如果根據需要進(jìn)行重構全部要改成繼承自 E,那么全部都得改一次! 這樣很麻煩而且容易出錯! 而使用super()就不用一個(gè)一個(gè)改了(只需類(lèi)定義中改一改就好了)
補充:對面向對象的理解
其實(shí)我覺(jué)得Python里面這樣的語(yǔ)法更容易理解面向對象的本質(zhì), 比Java中隱式地傳this更容易理解。所謂函數,就是一段代碼,接受輸入,返回輸出。所謂方法,就是一個(gè)函數有了一個(gè)隱式傳遞的參數。所以方法就是一段代碼,是類(lèi)的所有實(shí)例共享, 唯一不同的是各個(gè)實(shí)例調用的時(shí)候傳給方法的this 或者self不一樣而已。
構造方法是什么呢?其實(shí)也是一個(gè)實(shí)例方法啊,它只有在對象生成了之后才能調用,所以Python中__init__方法的參數是self啊。調用構造方法時(shí)其實(shí)已經(jīng)為對象分配了內存, 構造方法只是起到初始化的作用,也就是為這段內存里面賦點(diǎn)初值而已。
Java中所謂的靜態(tài)變量其實(shí)也就是類(lèi)的變量, 其實(shí)也就是為類(lèi)也分配了內存,里面存了這些變量,所以Python中的類(lèi)對象我覺(jué)得是很合理的,也比Java要直觀(guān)。至于靜態(tài)方法,那就與對象一點(diǎn)關(guān)系都沒(méi)有了,本質(zhì)就是個(gè)獨立的函數,只不過(guò)寫(xiě)在了類(lèi)里面而已。而Python中的classmethod其實(shí)也是一種靜態(tài)方法,不過(guò)它會(huì )依賴(lài)于cls對象,這個(gè)cls就是類(lèi)對象,但是只要想用這個(gè)方法,類(lèi)對象必然是存在的,不像實(shí)例對象一樣需要手動(dòng)的實(shí)例化,所以classmethod也可以看做是一種靜態(tài)變量。而staticmethod就是真正的靜態(tài)方法了,是獨立的函數,不依賴(lài)任何對象。
Java中的實(shí)例方法是必須依賴(lài)于對象存在的, 因為要隱式的傳輸this,如果對象不存在這個(gè)this也沒(méi)法隱式了。所以在靜態(tài)方法中是沒(méi)有this指針的,也就沒(méi)法調用實(shí)例方法。而Python中的實(shí)例方法是可以通過(guò)類(lèi)名來(lái)調用的。只不過(guò)因為這時(shí)候self沒(méi)辦法隱式傳遞,所以必須得顯式地傳遞。
22.__call__回調函數
一般來(lái)說(shuō),魔法方法都是隱式調用的
__call__()放在類(lèi)中定義。
python中,函數是一個(gè)對象
>>> f = abs #絕對值函數>>> f.__name__'abs'>>> f(-10)10
由于 f 可以被調用,所以,f 被稱(chēng)為可調用對象。所有的函數都是可調用對象。
一個(gè)類(lèi)實(shí)例也可以變成一個(gè)可調用對象,只需要實(shí)現一個(gè)特殊方法__call__()。
我們把 Person 類(lèi)變成一個(gè)可調用對象:
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
現在可以對 Person 實(shí)例直接調用:
>>> person = Person('Bob', 'male')>>> person('Tim')My name is Bob...My friend is Tim...
單看 person('Tim') 你無(wú)法確定 p 是一個(gè)函數還是一個(gè)類(lèi)實(shí)例,所以,在Python中,函數也是對象,對象和函數的區別并不顯著(zhù)。
23.dict的get函數
get函數相比較于通過(guò)下標的方式獲取值得好處就是,即使字典中沒(méi)有想要的key,也會(huì )根據get后面的參數輸出結果,對用戶(hù)友好;而下標方式直接報錯
>>> a = {'a':1,'b':2}>>> a.get('a')1>>> a.get('c',"None")'None'24.hasattr() getattr() setattr() 函數使用方法詳解
hasattr(object, name)
判斷一個(gè)對象里面是否有name屬性或者name方法,返回BOOL值,有name特性返回True, 否則返回False。
需要注意的是name要用括號括起來(lái)
>>> class test():... name="xiaohua"... def run(self):... return "HelloWord"...>>> t=test()>>> hasattr(t, "name") #判斷對象有name屬性True>>> hasattr(t, "run") #判斷對象有run方法True
getattr(object, name[,default])
獲取對象object的屬性或者方法,如果存在打印出來(lái),如果不存在,打印出默認值,默認值可選。
需要注意的是,如果是返回的對象的方法,返回的是方法的內存地址,如果需要運行這個(gè)方法,
可以在后面添加一對括號。
>>> class test():... name="xiaohua"... def run(self):... return "HelloWord"...>>> t=test()>>> getattr(t, "name") #獲取name屬性,存在就打印出來(lái)。'xiaohua'>>> getattr(t, "run") #獲取run方法,存在就打印出方法的內存地址。<bound method test.run of <__main__.test instance at 0x0269C878>>>>> getattr(t, "run"()) #獲取run方法,后面加括號可以將這個(gè)方法運行。'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è)默認值。'18'
setattr(object, name, values)
給對象的屬性賦值,若屬性不存在,先創(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è)對象的屬性是否存在,若不存在就添加該屬性。
>>> 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í),設置該屬性'18'>>> getattr(t, "age") #可檢測設置成功'18'25.object,type和metaclass的關(guān)系
type類(lèi)是元類(lèi),用來(lái)創(chuàng )建類(lèi)(對象)的,事實(shí)是調用了type的__new__創(chuàng )建的類(lèi)(對象)
__new__是用來(lái)創(chuàng )建實(shí)例對象的,如果沒(méi)有復寫(xiě)__new__方法,事實(shí)上是調用了object的__new__
metaclass關(guān)鍵字是用來(lái)定制元類(lèi)的,這樣子后續我們定義的類(lèi)就可以用定制的元類(lèi)來(lái)創(chuàng )建。經(jīng)典的運用就是ORM
type也是繼承了object,并對object的__new__進(jìn)行了復寫(xiě)
父類(lèi)(對象)先創(chuàng )建,之后才是子類(lèi)(對象)
26.淺拷貝和深拷貝
普通的賦值在python中是淺拷貝,及傳遞的是對象的引用;若要深拷貝,必須導入copy模塊
copy.copy 淺拷貝 只拷貝父對象,不會(huì )拷貝對象的內部的子對象。
copy.deepcopy 深拷貝 拷貝對象及其子對象
import copya = [1, 2, 3, 4, ['a', 'b']] #原始對象 b = a #賦值,傳對象的引用c = copy.copy(a) #對象拷貝,淺拷貝d = copy.deepcopy(a) #對象拷貝,深拷貝 a.append(5) #修改對象aa[4].append('c') #修改對象a中的['a', 'b']數組對象 print 'a = ', aprint 'b = ', bprint 'c = ', cprint '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']]
深拷貝在拷貝的時(shí)候,若拷貝對象里包含引用,不僅拷貝引用,而且把引用的內容也再復制一份。理解深的含義。
27.私有屬性是可以訪(fǎng)問(wèn)的
python中定義私有屬性的方法是在變量名前加'_'或者'__'
_foo:一種約定,用來(lái)指定變量私有。程序員用來(lái)指定私有變量的一種方式。但是外部仍可直接訪(fǎng)問(wèn)
__foo:這個(gè)有真正的意義:解析器用_classname__foo來(lái)代替這個(gè)名字,以區別和其他類(lèi)相同的命名。
但是我們可以訪(fǎng)問(wèn)私有屬性,因為python中私有屬性是通過(guò)名字重整的機制實(shí)現的,改變了私有屬性名,從而報出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__
當訪(fǎng)問(wèn)對象的屬性時(shí),其實(shí)先調用對象中的魔法方法__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: #測試時(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.對象中都是屬性,方法只不過(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'
所以當調用a.f()的時(shí)候也會(huì )默認執行類(lèi)中__getattribute__方法
30.GIL全局解釋器鎖
從名字上看能告訴我們很多東西,很顯然,這是一個(gè)加在解釋器上的全局(從解釋器的角度看)鎖(從互斥或者類(lèi)似角度看)。GIL使得同一時(shí)刻只有一個(gè)線(xiàn)程在一個(gè)CPU上執行字節碼,無(wú)法保證多個(gè)線(xiàn)程映射到多個(gè)CPU上執行。對于任何Python程序,不管有多少的處理器,任何時(shí)候都總是只有一個(gè)線(xiàn)程在執行。事實(shí)上,線(xiàn)程并不是完全運行完成后釋放GIL,所以線(xiàn)程安全也是相對的
在Python多線(xiàn)程下,每個(gè)線(xiàn)程的執行方式:
1.獲取GIL
2.執行代碼直到sleep或者是python虛擬機將其掛起(虛擬機會(huì )根據執行的字節碼行數以及時(shí)間片釋放GIL)或者遇到IO操作
3.釋放GIL
可見(jiàn),某個(gè)線(xiàn)程想要執行,必須先拿到GIL,我們可以把GIL看作是“通行證”,并且在一個(gè)python進(jìn)程中,GIL只有一個(gè)。拿不到通行證的線(xiàn)程,就不允許進(jìn)入CPU執行
解決辦法就是多進(jìn)程和協(xié)程(協(xié)程也只是單CPU,單線(xiàn)程但是能減小切換代價(jià)提升性能)。
31.當函數的參數是list引發(fā)的問(wèn)題
==比較的是值的大??;is是比較地址???
32.上下文管理 __enter__,__exit__
應用場(chǎng)景:
文件的讀寫(xiě)
數據庫的讀寫(xiě)操作
Flask的上下文管理
上下文管理協(xié)議:當使用with語(yǔ)句時(shí),解釋器會(huì )自動(dòng)調用 __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ǔ)句,調用__enter__;退出with語(yǔ)句,調用__exit__
事實(shí)上sample并不是sample=Sample(),而是__enter__返回的對象,即如果__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ǔ)言,可以在運行過(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)結構里面包含__slots__,x,y print dir(test)#test類(lèi)結構里包含__dict__ print '**************************************' ts=test_slots() t=test() print dir(ts) #可以看到ts實(shí)例結構里面包含__slots__,x,y,不能任意綁定屬性 print dir(t) #t實(shí)例結構里包含__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ō)的,默認情況下,Python的新式類(lèi)和經(jīng)典類(lèi)的實(shí)例都有一個(gè)dict來(lái)存儲實(shí)例的屬性。這在一般情況下還不錯,而且非常靈活,乃至在程序中可以隨意設置新的屬性。但是,對一些在”編譯”前就知道有幾個(gè)固定屬性的小class來(lái)說(shuō),這個(gè)dict就有點(diǎn)浪費內存了。當需要創(chuàng )建大量實(shí)例的時(shí)候,這個(gè)問(wèn)題變得尤為突出。一種解決方法是在新式類(lèi)中定義一個(gè)slots屬性。slots聲明中包含若干實(shí)例變量,并為每個(gè)實(shí)例預留恰好足夠的空間來(lái)保存每個(gè)變量;這樣Python就不會(huì )再使用dict,從而節省空間。
34.__unicode__,__str__
__unicode__,__str__類(lèi)似于java的toString方法。在java中,當打印對象的時(shí)候,其實(shí)是調用了對象的toString方法。同樣,我們可以定制對象打印輸出的格式
unicode是用在python2里,而str是用在python3里
#不復寫(xiě)__str__方法,調用的是object的方法class A: def __init__(self,name,age): self.name=name self.age=agea=A('lu',25)print(a) #<__main__.A object at 0x101978cf8>#復寫(xiě)__str__方法,調用的是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不可對可迭代對象進(jìn)行修改
在使用python的可迭代對象時(shí)有一條定律,那就是永遠都不要對迭代對象進(jìn)行數據的修改。 我們可以用copy(),生成一個(gè)副本,對這個(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í)現“[ ]”符號的使用
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.字節流與字符流
40.__name__的含義
#a.pyprint(__name__)
運行a.py,執行結果為_(kāi)_main__。
但如果對于一個(gè)b.py作為模塊在a中使用,b的__name__將變成對應b的模塊名
#b.pyprint(__name__)#a.pyimport b
運行a.py(如果運行b.py,結果扔是main),執行結果為b
所以為了測試模塊的相應功能,而避免在主程序中運行,通常我們需要在執行的代碼中這樣寫(xiě)
if __name__ == '__main__': do something
這樣子就能很好的控制模塊的有效輸出
41.為什么不能用可變對象作為函數的默認參數值
先來(lái)看一道題目:
>>> def func(numbers=[], num=1):... numbers.append(num)... return numbers>>> func()[1]>>> func()[1, 1]>>> func()[1, 1, 1]
我們似乎發(fā)現了一個(gè)Bug,每次用相同的方式調用函數 func() 時(shí),返回結果竟然不一樣,而且每次返回的列表在不斷地變長(cháng)。
>>> id(func())4330472840>>> id(func())4330472840
從上面可以看出,函數的返回值其實(shí)是同一個(gè)列表對象,因為他們的id值是一樣的,只不過(guò)是列表中的元素在變化。為什么會(huì )這樣呢?
這要從函數的特性說(shuō)起,在 Python 中,函數是第一類(lèi)對象(function is the first class object),換而言之,函數也是對象,跟整數、字符串一樣可以賦值給變量、當做參數傳遞、還可以作為返回值。函數也有自己的屬性,比如函數的名字、函數的默認參數列表。
# 函數的名字>>> func.__name__ 'func'# 函數的默認參數列表>>> func.__defaults__ ([1, 1, 1, 1, 1], 1)
def是一條可執行語(yǔ)句,Python 解釋器執行 def 語(yǔ)句時(shí),就會(huì )在內存中就創(chuàng )建了一個(gè)函數對象(此時(shí),函數里面的代碼邏輯并不會(huì )執行,因為還沒(méi)調用嘛),在全局命名空間,有一個(gè)函數名(變量叫 func)會(huì )指向該函數對象,記住,至始至終,不管該函數調用多少次,函數對象只有一個(gè),就是function object,不會(huì )因為調用多次而出現多個(gè)函數對象。
image
函數對象生成之后,它的屬性:名字和默認參數列表都將初始化完成。
image
初始化完成時(shí),屬性 __default__ 中的第一個(gè)默認參數 numbers 指向一個(gè)空列表。
當函數第一次被調用時(shí),就是第一次執行 func()時(shí),開(kāi)始執行函數里面的邏輯代碼(此時(shí)函數不再需要初始化了),代碼邏輯就是往numbers中添加一個(gè)值為1的元素
image
第二次調用 func(),繼續往numbers中添加一個(gè)元素
image
第三次、四次依此類(lèi)推。
所以現在你應該明白為什么調用同一個(gè)函數,返回值確每次都不一樣了吧。因為他們共享的是同一個(gè)列表(numbers)對象,只是每調用一次就往該列表中增加了一個(gè)元素
如果我們顯示地指定 numbers 參數,結果截然不同。
>>> func(numbers=[10, 11])[10, 11, 1]
image
因為numbers被重新賦值了,它不再指向原來(lái)初始化時(shí)的那個(gè)列表了,而是指向了我們傳遞過(guò)去的那個(gè)新列表對象,因此返回值變成了 [10, 11, 1]
那么我們應該如何避免前面那種情況發(fā)生呢?就是不要用可變對象作為參數的默認值。
正確方式:
>>> def func(numbers=None, num=1):... if numbers is None:... numbers = [num]... else:... numbers.append(num)... return numbers...>>> func()[1]>>> func()[1]>>> func()[1]
如果調用時(shí)沒(méi)有指定參數,那么調用方法時(shí),默認參數 numbers 每次都被重新賦值了,所以,每次調用的時(shí)候numbers都將指向一個(gè)新的對象。這就是與前者的區別所在。
那么,是不是說(shuō)我們永遠都不應該用可變對象來(lái)作為參數的默認值了嗎?并不是,既然Python有這樣的語(yǔ)法,就一定有他的應用場(chǎng)景,就像 for … else 語(yǔ)法一樣。我們可以用可變對象來(lái)做緩存功能
例如:計算一個(gè)數的階乘時(shí)可以用一個(gè)可變對象的字典當作緩存值來(lái)實(shí)現緩存,緩存中保存計算好的值,第二次調用的時(shí)候就無(wú)需重復計算,直接從緩存中拿。
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))
輸出:
---第一次調用---xxxxxxxxxxxx24---第二次調用---24
第二次調用的時(shí)候,直接從 cache 中拿了值,所以,你說(shuō)用可變對象作為默認值是 Python 的缺陷嗎?也并不是,對吧!你還是當作一種特性來(lái)使用。
函數的基本注意點(diǎn)
1.函數基礎之globals()和locals()
函數名.__doc__,返回函數的解釋?zhuān)瑢τ诤瘮刀x時(shí),可以將解釋通過(guò)字符串寫(xiě)到執行語(yǔ)句中。
def abs(a=3) “this is new function“>>>abs.__doc__“this is new function“函數體執行到return語(yǔ)句就結束,不管后面是否還有語(yǔ)句。
當def一個(gè)函數時(shí),其實(shí)就是創(chuàng )建了一個(gè)函數對象,并將函數對象返回給函數名。
函數定義時(shí)的參數(a=2)可以通過(guò)函數對象的__defaults__查看
每次對函數的調用都創(chuàng )建了一個(gè)新的本地作用域。
作用域(命名空間),一個(gè)隱藏的字典(鍵為函數名,值為函數對象的地址)
globals()函數返回全局變量的字典;locals()函數返回局部變量的字典(根據當前上下文來(lái)判斷,若處在全局的位置,等同于globals(),見(jiàn)下例)。locals()函數只有在函數執行過(guò)程在才用意義,因為函數調用完成后,局部變量會(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()一樣的結果>>> print(globals())在函數的定義中可以訪(fǎng)問(wèn)全局變量(依據LEGB原則搜索變量),但不可以修改全局變量,除非使用global聲明變量
a = 3def test(arg): z = 1 global a a = 100test(4)print(a)#100vars()函數返回在當前一個(gè)作用域(命名空間)的字典
補充:
Python使用叫做名字空間的東西來(lái)記錄變量的軌跡。名字空間只是一個(gè) 字典,它的鍵字就是變量名,字典的值就是那些變量的值。實(shí)際上,名字空間可以象Python的字典一樣進(jìn)行訪(fǎng)問(wèn),一會(huì )我們就會(huì )看到。
在一個(gè)Python程序中的任何一個(gè)地方,都存在幾個(gè)可用的名字空間。每個(gè)函數都有著(zhù)自已的名字空間,叫做局部名字空間,它記錄了函數的變量,包括 函數的參數和局部定義的變量。每個(gè)模塊擁有它自已的名字空間,叫做全局名字空間,它記錄了模塊的變量,包括函數、類(lèi)、其它導入的模塊、模塊級的變量和常 量。還有就是內置名字空間,任何模塊均可訪(fǎng)問(wèn)它,它存放著(zhù)內置的函數和異常。
2.LEGB命名空間
當一行代碼要使用變量x的值時(shí),Python會(huì )到所有可用的名字空間去查找變量,按照如下順序:
1. Local:局部名字空間特指當前函數或類(lèi)的方法。如果函數定義了一個(gè)局部變量 x ,Python將使用這個(gè)變量,然后停止搜索。2. Enclosing:外部嵌套函數的命名空間(見(jiàn)閉包)3. Global:全局名字空間特指當前的模塊。如果模塊定義了一個(gè)名為 x 的變量,函數或類(lèi),Python將使用這個(gè)變量然后停止搜索。4. Builtin:內置名字空間對每個(gè)模塊都是全局的。作為最后的嘗試,Python將假設 x 是內置函數或變量。
如果Python在這些名字空間找不到x,它將放棄查找并引發(fā)一個(gè) NameError 的異常,同時(shí)傳遞There is no variable named 'x' 這樣一條信息。
像Python中的許多事情一樣,名字空間在運行時(shí)直接可以訪(fǎng)問(wèn)。特別地,局部名字空間可以通過(guò)內置的 locals 函數來(lái)訪(fǎng)問(wèn)。全局(模塊級別)名字空間可以通過(guò) globals 函數來(lái)訪(fǎng)問(wèn)。
變量名解析:LEGB
Python中一切都是對象。每個(gè)對象都有一個(gè)名字,名字位于名字空間中,用名字變量引用對象。對象存在于內存中一段空間。每個(gè)對象在內存中都有地址。
在Python里類(lèi)型本身是對象,和實(shí)例對象一樣儲存在堆中,對于解釋器來(lái)說(shuō)類(lèi)對象和實(shí)例對象沒(méi)有根本上的別。
所謂“定義一個(gè)函數”,實(shí)際上也就是生成一個(gè)函數對象。而“定義一個(gè)方法”就是生成一個(gè)函數對象,并把這個(gè)對象放在一個(gè)類(lèi)的__dict__中。
函數對象結構定義:
typedef struct { PyObject_HEAD PyObject *func_code; // PyCodeObject PyObject *func_globals; // 所在模塊的全局名字空間 PyObject *func_defaults; // 參數默認值列表 PyObject *func_closure; // 閉包列表 PyObject *func_doc; // __doc__ PyObject *func_name; // __name__ PyObject *func_dict; // __dict__ PyObject *func_weakreflist; // 弱引用鏈表 PyObject *func_module; // 所在 Module} PyFunctionObject;對象基本上可以看做數據(特性)以及由一系列可以存取,操作這些數據的方法所組成的集合
所有的對象都屬于某一個(gè)類(lèi),稱(chēng)為類(lèi)的實(shí)例
self指的是實(shí)例對象本身
查看a是否是b的子類(lèi),可以使用issubclass(子類(lèi),超類(lèi));查看a的基類(lèi)a.__bases__
匿名函數lambda 用法lambda argument1,argument2:expression using argument.功能類(lèi)似于def,只是不需要再去定義一個(gè)函數名。結果返回的是一個(gè)函數對象。
def add1(a,b): return a+badd1(2,3)=> 5g = lambda a,b:a+bg(2,3)=>53.閉包
內層函數引用了外層函數的變量(包括它的參數),然后返回內層函數的情況,這就是閉包。形式上,在函數的內部定義函數,并引用外部變量。
函數對象是使用def語(yǔ)句定義的,函數對象的作用域與def所在的層級相同。比如下面代碼,我們在line_conf函數的隸屬范圍內定義的函數line,就只能在line_conf的隸屬范圍內調用。
def line_conf(): def line(x): return 2*x+1 print(line(5)) # within the scopeline_conf() #結果11print(line(5)) # 錯誤,不能再外部調用內部函數line
可以在內部函數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)) #結果為25print(line_conf()(5)) #另一種引用方式,結果為25
一個(gè)函數line和它的環(huán)境變量b合在一起,就構成了一個(gè)閉包(closure)。在Python中,所謂的閉包是一個(gè)包含有環(huán)境變量取值的函數對象。環(huán)境變量取值被保存在函數對象的__closure__屬性中
在python2.X中不能對外部變量賦值,而在python3中增加了nonlocal關(guān)鍵字
def Fun1(): x=5 def Fun2(): x=x*x return x return Fun2()Fun1() #錯誤
為什么需要閉包?
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è)例子中,函數line與環(huán)境變量a,b構成閉包。在創(chuàng )建閉包的時(shí)候,我們通過(guò)line_conf的參數a,b說(shuō)明了這兩個(gè)環(huán)境變量的取值,這樣,我們就確定了函數的最終形式(y = x + 1和y = 4x + 5)。我們只需要變換參數a,b,就可以獲得不同的直線(xiàn)表達函數。由此,我們可以看到,閉包也具有提高代碼可復用性的作用。
如果沒(méi)有閉包,我們需要每次創(chuàng )建直線(xiàn)函數的時(shí)候同時(shí)說(shuō)明a,b,x。這樣,我們就需要更多的參數傳遞,也減少了代碼的可移植性。利用閉包,我們實(shí)際上創(chuàng )建了泛函。line函數定義一種廣泛意義的函數。這個(gè)函數的一些方面已經(jīng)確定(必須是直線(xiàn)),但另一些方面(比如a和b參數待定)。隨后,我們根據line_conf傳遞來(lái)的參數,通過(guò)閉包的形式,將最終函數確定下來(lái)。