第 6 章 抽象
1.給函數(shù)編寫文檔
給函數(shù)編寫文檔,以確保其他人能夠理解,可添加注釋(以#打頭的內(nèi)容)。還有另一種編寫注釋的方式,就是添加獨立的字符串。放在函數(shù)開頭的字符串稱為文檔字符串(docstring),將作為函數(shù)的一部分存儲起來。
例子:
def square(x):
'計算給定x的平方值'
return x * x
print(square(10))
print(square.__doc__)
#out
100
計算給定x的平方值
數(shù)學(xué)意義上的函數(shù)總是返回根據(jù)參數(shù)計算得到的結(jié)果。在Python中,有些函數(shù)什么都不返回。但在Python中,函數(shù)就是函數(shù),即使它嚴(yán)格來說并非函數(shù)。什么都不返回的函數(shù)不包含return語句,或者包含return語句,但沒 有在return后面指定值。
def test():
print('This is printed')
return #return語句只是為了結(jié)束函數(shù)
print('This is not')
x = test()
print(x)
#輸出 直接跳過第二個print
This is printed
None
#python中所有的函數(shù)都返回值,如果沒有指定返回什么,就將返回None
2.參數(shù)魔法
這里只介紹收集參數(shù)和分配參數(shù)
2.1 收集參數(shù)
python允許向函數(shù)提供任意數(shù)量的參數(shù),只需遵循下面的定義:
def print_params(*params):
print(params)
print_params('bozhou')
print_params('bozhou', 'anyi', 'zzh')
#輸出
('bozhou',) #括號里面有個逗號,說明返回的結(jié)果為一個元組類型結(jié)果
('bozhou', 'anyi', 'zzh') #這個結(jié)果就更明顯了
進(jìn)一步示例:
def print_params_2(want, *params):
print(want)
print(params)
print_params_2('hello', 5, 2, 1)
#輸出
hello
(5, 2, 1) #參數(shù)前帶星號,意味著將余下的值收集為一個元組
進(jìn)一步
def in_the_middle(x, *y, z):
print(x, y, z)
in_the_middle(1, 2, 3, 4, z=10) #也可以放在其他位置,就像本例一樣要指定后續(xù)參數(shù)名稱以便區(qū)分,否則報錯
#輸出
1 (2, 3, 4) 10
收集關(guān)鍵字參數(shù),可使用兩個星號
def print_params_3(**params):
print(params)
print_params_3(x=1, y=2, z=3)
#輸出一個字典而不是元組
{'z': 3, 'x': 1, 'y': 2}
進(jìn)一步可結(jié)合使用,如:
def print_params_4(x, y, z=3, *pospar, **keypar):
print(x, y, z)
print(pospar)
print(keypar)
print_params_4(1, 2, 3, 4, 5, a=10, b=20)
#輸出
1 2 3
(4, 5)
{'a': 10, 'b': 20}
2.2 分配參數(shù)
執(zhí)行與收集參數(shù)相反的操作,用例子來說明什么是相反的操作:
def add(x, y):
return x + y
params = (1, 2)
print(add(*params))
#輸出
3
進(jìn)一步
#本例是只在調(diào)用的時候使用了星號
def fun(name, age):
print('name:', name)
print('age:', age)
params = {'name': 'zzh', 'age': 25}
fun(**params)
#輸出
name: zzh
age: 25
#函數(shù)定義和調(diào)用都使用星號
def fun1(**params):
print('name:', params['name'])
print('age:', params['age'])
fun1(**params)
#輸出
name: zzh
age: 25
#普通傳參
def fun1(params):
print('name:', params['name'])
print('age:', params['age'])
fun1(params)
#輸出
name: zzh
age: 25
本小節(jié)中,星號被稱為拆分運算符。
3 作用域
作用域:變量存儲在作用域(也叫命名空間)中。在Python中,作用域分兩大類:全局作用域和局部作用域。作用域可以嵌套。
變量到底是什么呢?可將其視為指向值的名稱。因此,執(zhí)行賦值語句x = 1后,名稱x指向值1。這幾乎與使用字典時一樣(字典中的鍵指向值),只是你使用的是“看不見”的字典。實際上,這種解釋已經(jīng)離真相不遠(yuǎn)。有一個名為vars的內(nèi)置函數(shù),它返回這個不可見的字典:
x = 1
scope = vars()
print(type(scope))
print(scope['x'])
#輸出
<class 'dict'>
1
#這種“看不見的字典”稱為命名空間或作用域
讀取全局變量的值通常不會有問題,但還是存在出現(xiàn)問題的可能性。如果有一個局部
變量或參數(shù)與你要訪問的全局變量同名,就無法直接訪問全局變量,因為它被局部變量遮
住了。
如果需要,可使用函數(shù)globals來訪問全局變量。這個函數(shù)類似于vars,返回一個包含全局變量的字典。(locals返回一個包含局部變量的字典)。當(dāng)函數(shù)內(nèi)外有兩個變量名稱相同時,必要時可以使用以下方式在函數(shù)內(nèi)部訪問函數(shù)外的那個全局變量:
globals()['parameter']
待續(xù)```````````````````
第 7 章 再談抽象
開篇定義
多態(tài):可對不同類型的對象執(zhí)行相同的操作
封裝:對外部隱藏有關(guān)對象工作原理的細(xì)節(jié)
繼承:可基于通用類創(chuàng)建出專用類
1 對象魔法
1.1 多態(tài)
多態(tài),即多種形態(tài),即便你不知道變量(參數(shù)變量)指向的是哪種對象,也能夠?qū)ζ鋱?zhí)行操作,且操作的行為將隨對象所屬的類型(類)而異
示例:
#可假設(shè)本例為獲取狗狗的體重
def get_something(object):
if isinstance(object, tuple): #當(dāng)以元組的形式傳入時,如('哈士奇', 50)
return object[1]
#元組的形式過于固定,因為小狗的體重是變化的
#可以字典的形式傳入,將體重單獨存在名稱為“體重”的key下,這樣更加靈活
elif isinstance(object, dict):
return int(object['height'])
#可以看到一個功能函數(shù),對應(yīng)兩個甚至多個不同類型的對象都可以進(jìn)行操作
進(jìn)一步,還有更多的例子:
>>> 'abc'.count('a')
1
>>> [1, 2, 'a'].count('a')
1
#無需知道它是字符串還是列表就能調(diào)用方法count:只要你向這個方法
提供一個字符作為參數(shù),它就能正常運行。
不僅僅是函數(shù),python中的運算符也體現(xiàn)出多態(tài)的特點,如:
>>> 1 + 2
3
>>> 'zzh' + 'bozhou'
'zzhbozhou'
這里討論的多態(tài)形式是Python編程方式的核心,有時稱為鴨子類型。這個術(shù)語源自如下說法:“如果走起來像鴨子,叫起來像鴨子,那么它就是鴨子。
1.2 封裝
在程序設(shè)計中,封裝(Encapsulation)是對具體對象的一種抽象,即將某些部分隱藏起來,在程序外部看不到,其含義是其他程序無法調(diào)用。
封裝數(shù)據(jù)的主要原因是:保護(hù)隱私(把不想別人知道的東西封裝起來)
封裝方法的主要原因是:隔離復(fù)雜度
封裝其實分為兩個層面,但無論哪種層面的封裝,都要對外界提供好訪問你內(nèi)部隱藏內(nèi)容的接口
第一個層面的封裝(什么都不用做):創(chuàng)建類和對象會分別創(chuàng)建二者的名稱空間,我們只能用類名.或者obj.的方式去訪問里面的名字,這本身就是一種封裝
第二層次的封裝:是指把類中的屬性和方法私有化,只供內(nèi)部使用,也可稱為隱藏,但不是真正的隱藏
以上兩點下面會提到
1.3 繼承
對一個通用類進(jìn)行拓展產(chǎn)生子類,但程序員不想把代碼再寫一遍,所以對類繼承財產(chǎn)并發(fā)揚(yáng)光大的功能就產(chǎn)生了
2 類
2.1 隱藏
要讓方法或?qū)傩猿蔀樗接械模ú荒軓耐獠吭L問),只需讓其名稱以兩個下劃線打頭即可:
class Secretive:
def __inaccessible(self):
print("Bet you can't see me ...")
def accessible(self):
print("The secret message is:")
self.__inaccessible()
o = Secretive()
o.accessible()
#輸出
The secret message is:
Bet you can't see me ...
#可以看到仍然是可以用其他方式訪問的,并不是真正意義上的隱藏
2.2 繼承
至于什么是繼承,怎么繼承這里就不多敘述了..............
我們來看看如何查看一個類是否是另一個類的子類,python已經(jīng)提供了內(nèi)置方法,例子如下:
#類定義就不寫了
>>> issubclass(son, father)
True
#再者如果想查看一個類的基類,可訪問其特殊屬性__bases__
son.__bases__
要確定一個對象是否是特定類的實例,可使用isinstance,用法同上
進(jìn)一步
x = son()
#所有son類的對象實例都是類father的對象實例,因為son是father的子類,
這時候使用isinstance可能不是精準(zhǔn)的,下面小節(jié)中會講到抽象基類。
2.3 多個超類
一個類的父類可以有多個,如何繼承語法形式如下:
#多重繼承
class son(father1, father2):
pass
需要注意的是:
使用多重繼承時,有一點務(wù)必注意:如果多個超類以不同的方式實現(xiàn)了同一個方法(即有多個同名方法),必須class語句中小心排列這些超類,因為位于前面的類的方法將覆蓋位于后面在的類的方法
2.4 接口和內(nèi)省
類中的方法屬性都是類的協(xié)議接口,多態(tài)和封裝的思想使我們只關(guān)心怎樣使用接口,并不關(guān)系其中的細(xì)節(jié),但python也提供了了解接口的方法,如查看、獲取、設(shè)置:
class son:
temp = 10
def fun(self, a):
self.name = a
print('my name is: ', self.name)
o = son()
print(hasattr(o, 'fun'))
print(getattr(o, 'fun'))
print(getattr(o, 'fun1', None))
setattr(o, 'temp', 7)
print(o.temp)
#輸出
True
<bound method son.fun of <__main__.son object at 0x000001CFCF6B1AC8>>
None
7
2.5 抽象基類
一般而言,抽象類是不能(至少是不應(yīng)該)實例化的類,其職責(zé)是定義子類應(yīng)實現(xiàn)或者說必要的一組抽象方法,示例如下:
Python通過引入模塊abc提供了官方解決方案,這個模塊為所謂的抽象基類提供了支持。
from abc import ABC, abstractmethod
class Talker(ABC):
@abstractmethod #將方法標(biāo)記為抽象的——在子類中必須實現(xiàn)的方法
def talk(self):
pass
抽象類(即包含抽象方法的類)最重要的特征是不能實例化
如果繼承的子類沒有重寫talk方法,則該子類也是抽象類,不能進(jìn)行實例化否則報錯!
#正確做法
class Knigget(Talker):
def talk(self):
print("Ni!")
現(xiàn)在可以進(jìn)行實例化,而且只有在這種情形下使用isinstance才是比較精確的,
這種情況下,我們可以相信這個實例有對應(yīng)的方法(如本例talk方法),相對
于手工檢測方法抽象基類是個更好的選擇
因為在處理編程和對象時,強(qiáng)調(diào)構(gòu)成問題而不是身份問題,強(qiáng)調(diào)hasattr函數(shù)而不是isinstance函數(shù)(而前者是更精確的)
第 9 章 魔法方法、特性和迭代器
1 元素訪問
在Python中,協(xié)議通常指的是規(guī)范行為的規(guī)則。協(xié)議指定應(yīng)實現(xiàn)哪些方法以及這些方法應(yīng)做什么,類似于接口。序列和映射基本上是元素(item)的集合。
- __len__(self):這個方法應(yīng)返回集合包含的項數(shù),對序列來說為元素個數(shù),對映射來說為鍵-值對數(shù)
- __getitem__(self, key):這個方法應(yīng)返回與指定鍵相關(guān)聯(lián)的值。對序列來說,鍵應(yīng)該是0~n-1的整數(shù),其中n為序列的長度
- __setitem__(self, key, value):這個方法應(yīng)以與鍵相關(guān)聯(lián)的方式存儲值,以便以后能夠使用__getitem__來獲取
- __delitem__(self, key):這個方法在對對象的組成部分使用__del__語句時被調(diào)用,應(yīng)刪除與key相關(guān)聯(lián)的值
class temp:
def __init__(self, value):
self.value = value
self.seq = {}
def __len__(self):
return len(self.seq)
def __getitem__(self, key):
print('i am getitem!')
return self.seq[key]
def __setitem__(self, key, value):
print('i am setitem!')
self.seq[key] = self.value
b = temp(100)
b[1] = 10 #對self.[key]進(jìn)行賦值操作時調(diào)用__getitem__
print(b[1]) #以self[key]的方式獲取值時調(diào)用__setitem__
print(len(b))
#輸出、由輸出可看到調(diào)用了哪個方法
i am setitem!
i am getitem!
100
1
函數(shù) property
示例:
class Rectangle:
def __init__ (self):
self.width = 0
self.height = 0
def set_size(self, size):
self.width, self.height = size
def get_size(self):
return self.width, self.height
r = Rectangle()
r.width = 10
r.height = 5
print(r.get_size())
print(r.set_size((10, 20)))
print(r.width)
#輸出
(10, 5)
None
10
使用property函數(shù)
通過調(diào)用函數(shù)property并將存取方法作為參數(shù)(獲取方法在前,設(shè)置方法在后)
創(chuàng)建了一個新特性(如果我們需要這個特性的話,property給我提供了一個方便
的實現(xiàn),否則我們還要重新寫類方法),然后將名稱size關(guān)聯(lián)到這個特性;
class Rectangle:
def __init__ (self):
self.width = 0
self.height = 0
def set_size(self, size):
self.width, self.height = size
def get_size(self):
return self.width, self.height
size = property(get_size, set_size)
r = Rectangle()
r.width = 10
r.height = 5
print(r.size) #r.size會觸發(fā)get_size方法
r.size = 150, 100 #賦值會觸發(fā)set_size方法
print(r.width)
#輸出
(10, 5)
150
property形式
class property([fget[, fset[, fdel[, doc]]]])
fdel參數(shù) -- 刪除屬性值函數(shù)
doc參數(shù) -- 屬性描述信息

訪問控制
__getattr__(self, name)
當(dāng)用戶試圖訪問一個根本不存在(或者暫時不存在)的屬性時,你可以通過這個魔
法方法來定義類的行為。這個可以用于捕捉錯誤的拼寫并且給出指引,使用廢棄屬
性時給出警告(如果你愿意,仍然可以計算并且返回該屬性),以及靈活地處理屬
性錯誤。只有當(dāng)試圖訪問不存在的屬性時它才會被調(diào)用,所以這不能算是一個真正
的封裝的辦法
------------------------------------------------------------------------
__setattr__(self, name, value)
和 __getattr__ 不同, __setattr__ 可以用于真正意義上的封裝。它允許你自定義某
個屬性的賦值行為,不管這個屬性存在與否,也就是說你可以對任意屬性的任何變
化都定義自己的規(guī)則。然后,一定要小心使用 __setattr__ ,這個列表最后的例子
中會有所展示
-------------------------------------------------------------------------
__getattribute__(self, name):在屬性被訪問時自動調(diào)用
-------------------------------------------------------------------------
__delattr__(self, name):試圖刪除屬性時自動調(diào)用
2 迭代器
迭代(iterate)意味著重復(fù)多次,就像循環(huán)那樣。已知的for循環(huán)迭代序列和字典,但實際上也可迭代其他對象:實現(xiàn)了方法__ iter__的對象;
方法__ iter__返回一個迭代器(可迭代對象),前提是該對象包含方法__ next__ ;當(dāng)你調(diào)用方法__ next__時,迭代器應(yīng)返回其下一個值。如果迭代器沒有可供返回的值,應(yīng)引發(fā)StopIteration異常;
使用 iter() 函數(shù)調(diào)用,以及在類似 for x in container: 的循環(huán)時被調(diào)用
class Fibs:
def __init__(self):
self.a = 0
self.b = 1
def __next__(self):
print("b")
self.a, self.b = self.b, self.a + self.b
return self.a
def __iter__(self):
print("a")
return self
b = Fibs()
print("start!")
for f in b:
print("d")
if f > 2:
print("mark")
print(f)
break
#輸出,通過輸出字母的順便來看是方法觸發(fā)的順序
start!
a
b
d
b
d
b
d
b
d
mark
3
迭代與使用列表相比,在某些情況下,更簡潔;列表可能會占有過多的內(nèi)存,而有
時候我們想逐個的取值,再者如上例所示斐波那契數(shù)列是無窮大的,使用列表不合
適;
3 生成器
生成器是一種使用普通函數(shù)語法定義的迭代,當(dāng)Python 函數(shù)不用return 返回值,用yield關(guān)鍵字的時候,函數(shù)的返回值為生成器對象;生成器不是使用return返回一個值,而是可以生成多個值,每次一個。
創(chuàng)建生成器
def fun():
yield 10
print("z~~~~~~~")
yield 20
a = fun()
print(a)
print(a.__next__())
print(next(a))
# print(next(a)) #報錯,沒有可供返回的值了
#輸出
<generator object fun at 0x0000026AC6162780>
10
z~~~~~~~
20
另一種創(chuàng)建形式,生成器表達(dá)式
g = ((i + 2) ** 2 for i in range(2, 27))
print(next(g))
print(next(g))
#輸出
16
25