遞歸
def fact(n):
if n==1:
return 1
return n * fact(n - 1)
使用遞歸函數(shù)需要注意防止棧溢出允跑。在計算機(jī)中,函數(shù)調(diào)用是通過棧(stack)這種數(shù)據(jù)結(jié)構(gòu)實現(xiàn)的搪柑,每當(dāng)進(jìn)入一個函數(shù)調(diào)用聋丝,棧就會加一層棧幀,每當(dāng)函數(shù)返回拌屏,棧就會減一層棧幀潮针。由于棧的大小不是無限的,所以倚喂,遞歸調(diào)用的次數(shù)過多每篷,會導(dǎo)致棧溢出。
棧
迭代
Python內(nèi)置的enumerate函數(shù)可以把一個list變成索引-元素對端圈,這樣就可以在for循環(huán)中同時迭代索引和元素本身:
>>> for i, value in enumerate(['A', 'B', 'C']):
... print(i, value)
...
0 A
1 B
2 C
閉包
在高階函數(shù)中焦读,內(nèi)部的函數(shù)可以引用外部函數(shù)的參數(shù)及局部變量,并且返回內(nèi)部函數(shù)舱权,而不是函數(shù)的值矗晃,這種情況,稱為閉包宴倍。
def count(): # 這種形式要避免张症!
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count() # 結(jié)果f1,f2,f3 等于9, 9, 9 . 且不能直接寫 f4 = count() 這樣的賦值式,必須3個變量一起
全部都是 9!原因就在于返回的函數(shù)引用了變量 i鸵贬,但它并非立刻執(zhí)行俗他。 等到 3 個函數(shù)都返回時,它們所引用的變量 i 已經(jīng)變成了 3阔逼,因此最終 結(jié)果為 9兆衅。
返回閉包時牢記的一點就是:返回函數(shù)不要引用任何循環(huán)變量,或者后 續(xù)會發(fā)生變化的變量嗜浮。
模塊
在計算機(jī)程序的開發(fā)過程中羡亩,隨著程序代碼越寫越多,在一個文件里代 碼就會越來越長危融,越來越不容易維護(hù)畏铆。
為了編寫可維護(hù)的代碼,我們把很多函數(shù)分組吉殃,分別放到不同的文件里及志, 這樣片排,每個文件包含的代碼就相對較少,很多編程語言都采用這種組織 代碼的方式速侈。在 Python 中率寡,一個.py
文件就稱之為一個模塊(Module)。
使用模塊有什么好處?
最大的好處是大大提高了代碼的可維護(hù)性倚搬。其次冶共,編寫代碼不必從零開 始。當(dāng)一個模塊編寫完畢每界,就可以被其他地方引用捅僵。我們在編寫程序的 時候,也經(jīng)常引用其他模塊眨层,包括 Python 內(nèi)置的模塊和來自第三方的 模塊庙楚。
使用模塊還可以避免函數(shù)名和變量名沖突。
舉個例子趴樱,一個abc.py
的文件就是一個名字叫abc
的模塊馒闷,一個xyz.py
的文件就是一個名字叫xyz
的模塊。
現(xiàn)在叁征,假設(shè)我們的abc
和xyz
這兩個模塊名字與其他模塊沖突了纳账,于是我們可以通過包來組織模塊,避免沖突捺疼。方法是選擇一個頂層包名疏虫,比如mycompany
,按照如下目錄存放:
mycompany
├─ init.py
├─ abc.py
└─ xyz.py
引入了包以后啤呼,只要頂層的包名不與別人沖突卧秘,那所有模塊都不會與別人沖突。現(xiàn)在官扣,abc.py
模塊的名字就變成了mycompany.abc
翅敌,類似的,xyz.py
的模塊名變成了mycompany.xyz
醇锚。(??有時候的模塊引用出現(xiàn)大量xx.xx.xx.xx的形式可能是模塊多重頂層包名,而不要想成是函數(shù)調(diào)用xxx.xxx)
像列表一樣去讀取斐波那契數(shù)列坯临,用面向?qū)ο蟊硎?/h3>
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
>>> f = Fib()
>>> f[0]
1
>>> f[1]
1
>>> f[2]
2
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
>>> f = Fib()
>>> f[0]
1
>>> f[1]
1
>>> f[2]
2
定制類__getattr__
正常情況下焊唬,當(dāng)我們調(diào)用類的方法或?qū)傩詴r,如果不存在看靠,就會報錯赶促。
比如定義 Student 類:
class Student(object):
def __init__(self):
self.name = 'Michael'
# 調(diào)用 name 屬性,沒問題挟炬,但是鸥滨,調(diào)用不存在的 score 屬性嗦哆,就有問題了:
>>> s = Student()
>>> print(s.name)
Michael
>>> print(s.score)
Traceback (most recent call last):
...
AttributeError: 'Student' object has no attribute 'score'
要避免這個錯誤,除了可以加上一個 score 屬性外婿滓,Python 還有另一個 機(jī)制老速,那就是寫一個__getattr__()
方法,動態(tài)返回一個屬性凸主。修改如下:
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr=='score':
return 99
# 當(dāng)調(diào)用不存在的屬性時橘券,比如 score,Python 解釋器會試圖調(diào)用 __getattr__(self, 'score')來嘗試獲得屬性卿吐,這樣旁舰,我們就有機(jī)會返回 score 的值:
>>> s = Student()
>>> s.name
'Michael'
>>> s.score
99
返回函數(shù)也是完全可以的:
class Student(object):
def __getattr__(self, attr):
if attr=='age':
return lambda: 25 # 只是調(diào)用方式要變?yōu)?
>>> s.age()
25
注意,只有在沒有找到屬性的情況下嗡官,才調(diào)用__getattr__
箭窜,已有的屬性,比如 name衍腥,不會在__getattr__
中查找磺樱。
此外,注意到任意調(diào)用如 s.abc
都會返回 None紧阔,這是因為我們定義的 __getattr__
默認(rèn)返回就是 None坊罢。要讓 class 只響應(yīng)特定的幾個屬性,我 們就要按照約定擅耽,拋出 AttributeError
的錯誤
這實際上可以把一個類的所有屬性和方法調(diào)用全部動態(tài)化處理了活孩,不需 要任何特殊手段。
這種完全動態(tài)調(diào)用的特性有什么實際作用呢?作用就是乖仇,可以針對完全 動態(tài)的情況作調(diào)用憾儒。
現(xiàn)在很多網(wǎng)站都搞 REST API,比如新浪微博乃沙、豆瓣啥的起趾,調(diào)用 API 的
URL 類似:
? http://api.server/user/friends
? http://api.server/user/timeline/list
如果要寫 SDK,給每個 URL 對應(yīng)的 API 都寫一個方法警儒,那得累死训裆,而
且,API 一旦改動蜀铲,SDK 也要改边琉。
利用完全動態(tài)的__getattr__
,我們可以寫出一個鏈?zhǔn)秸{(diào)用:
class Chain(object):
def __init__(self, path=''):
self._path = path
def __getattr__(self, path):
return Chain('%s/%s' % (self._path, path))
def __str__(self):
return self._path
__repr__ = __str__ 試試:
>>> Chain().status.user.timeline.list
'/status/user/timeline/list'
# 這里結(jié)果第一個 / 前應(yīng)該有個值记劝,是 self._path,但是Chain()沒有傳參变姨,默認(rèn)path = ' ',空的了