第十三天 函數(shù)編程和正則表達(dá)式
今天計(jì)劃學(xué)習(xí)Python的高階函數(shù)及函數(shù)編程,學(xué)習(xí)項(xiàng)目及練習(xí)源碼地址:
GitHub源碼
高階函數(shù)
在Python中,函數(shù)名其實(shí)也是變量,既然變量可以指向函數(shù),函數(shù)的參數(shù)能接收變量,那么一個(gè)函數(shù)就可以接收另一個(gè)函數(shù)作為參數(shù),這種函數(shù)就稱之為高階函數(shù)。
一個(gè)最簡(jiǎn)單的高階函數(shù):
def add(x, y, f):
return f(x) + f(y)
add(5,6,abs)# abs是系統(tǒng)自帶的函數(shù)
常用內(nèi)置高階函數(shù)介紹
-
map()
map()函數(shù)接收兩個(gè)參數(shù),一個(gè)是函數(shù),一個(gè)是Iterable(可迭代的,如:序列),map將傳入的函數(shù)依次作用到序列的每個(gè)元素,并把結(jié)果作為新的Iterator返回。
舉例說(shuō)明,比如我們有一個(gè)函數(shù)f(x)=x*x,要把這個(gè)函數(shù)作用在一個(gè)list [1, 2, 3, 4, 5, 6, 7, 8, 9]上,就可以用map()實(shí)現(xiàn)如下:
def fn(x): return x**2 res = map(fn,[1,2,3,4,5]) print(list(res))map()傳入的第一個(gè)參數(shù)是fn,即函數(shù)對(duì)象本身。由于結(jié)果res是一個(gè)Iterator,Iterator是惰性序列,因此通過(guò)list()函數(shù)讓它把整個(gè)序列都計(jì)算出來(lái)并返回一個(gè)list。
所以,map()作為高階函數(shù),事實(shí)上它把運(yùn)算規(guī)則抽象了,因此,我們不但可以計(jì)算簡(jiǎn)單的f(x)=x2,還可以計(jì)算任意復(fù)雜的函數(shù),比如,把這個(gè)list所有數(shù)字轉(zhuǎn)為字符串:
a = [1,2,3,4,5] b = list(map(str,a)) #['1','2','3','4','5'] -
reduce()
reduce把一個(gè)函數(shù)作用在一個(gè)序列[x1, x2, x3, ...]上,這個(gè)函數(shù)必須接收兩個(gè)參數(shù),reduce把結(jié)果繼續(xù)和序列的下一個(gè)元素做累積計(jì)算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)舉例一個(gè)序列求和:
from functools import reduce def fn(x,y): return x+y res = reduce(fn,[1,2,3,4,5]) print(res) # 1+2+3+4+5的值 和sum()一樣 -
filter()
和map()類似,filter()也接收一個(gè)函數(shù)和一個(gè)序列。和map()不同的是,filter()把傳入的函數(shù)依次作用于每個(gè)元素,然后根據(jù)返回值是True還是False決定保留還是丟棄該元素。
在一個(gè)list中,刪掉偶數(shù),只保留奇數(shù),可以這么寫(xiě):def is_odd(n): return n % 2 == 1 list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15])) # 結(jié)果: [1, 5, 9, 15]注意到filter()函數(shù)返回的是一個(gè)Iterator,也就是一個(gè)惰性序列,所以要強(qiáng)迫f(wàn)ilter()完成計(jì)算結(jié)果,需要用list()函數(shù)獲得所有結(jié)果并返回list。
-
sorted()
排序也是在程序中經(jīng)常用到的算法。無(wú)論使用冒泡排序還是快速排序,排序的核心是比較兩個(gè)元素的大小。如果是數(shù)字,我們可以直接比較,但如果是字符串或者兩個(gè)dict呢?直接比較數(shù)學(xué)上的大小是沒(méi)有意義的,因此,比較的過(guò)程必須通過(guò)函數(shù)抽象出來(lái)。Python內(nèi)置的sorted()函數(shù)就可以對(duì)list進(jìn)行排序:
sorted([36, 5, -12, 9, -21]) # [-21, -12, 5, 9, 36]此外,sorted()函數(shù)也是一個(gè)高階函數(shù),它還可以接收一個(gè)key函數(shù)來(lái)實(shí)現(xiàn)自定義的排序,例如按絕對(duì)值大小排序:
sorted([36, 5, -12, 9, -21], key=abs) # [5, 9, -12, -21, 36]默認(rèn)情況下,對(duì)字符串排序,是按照ASCII的大小比較的,由于'Z' < 'a',結(jié)果,大寫(xiě)字母Z會(huì)排在小寫(xiě)字母a的前面。
要進(jìn)行反向排序,不必改動(dòng)key函數(shù),可以傳入第三個(gè)參數(shù)reverse=True'
小結(jié)
從上述例子可以看出,高階函數(shù)的抽象能力是非常強(qiáng)大的,而且,核心代碼可以保持得非常簡(jiǎn)潔。
函數(shù)作為返回值
高階函數(shù)除了可以接受函數(shù)作為參數(shù)外,還可以把函數(shù)作為結(jié)果值返回。
def lazy_sum(*args): def sum(): ax = 0 for n in args: ax = ax + n return ax return sum f = lazy_sum(1, 3, 5, 7, 9) f() f1 = lazy_sum(1, 3) f2 = lazy_sum(1, 3) f1 == f2 # False 每次調(diào)用都是返回的一個(gè)新函數(shù) -
閉包
相關(guān)參數(shù)和變量都保存在返回的函數(shù)中,這種稱為“閉包(Closure)”的程序結(jié)構(gòu)擁有極大的威力。
返回閉包時(shí)牢記一點(diǎn):返回函數(shù)不要引用任何循環(huán)變量,或者后續(xù)會(huì)發(fā)生變化的變量。
如果一定要引用循環(huán)變量怎么辦?方法是再創(chuàng)建一個(gè)函數(shù),用該函數(shù)的參數(shù)綁定循環(huán)變量當(dāng)前的值,無(wú)論該循環(huán)變量后續(xù)如何更改,已綁定到函數(shù)參數(shù)的值不變:def count(): def f(j): def g(): return j*j return g fs = [] for i in range(1, 4): fs.append(f(i)) # f(i)立刻被執(zhí)行,因此i的當(dāng)前值被傳入f() return fs缺點(diǎn)是代碼較長(zhǎng),可利用lambda函數(shù)縮短代碼。
匿名函數(shù)
就是lambda函數(shù)
裝飾器
由于函數(shù)也是一個(gè)對(duì)象,而且函數(shù)對(duì)象可以被賦值給變量,所以,通過(guò)變量也能調(diào)用該函數(shù)。
函數(shù)對(duì)象有一個(gè)name屬性,可以拿到函數(shù)的名字:now.__name__。在代碼運(yùn)行期間動(dòng)態(tài)增加功能的方式,稱之為“裝飾器”(Decorator)。本質(zhì)上,decorator就是一個(gè)返回函數(shù)的高階函數(shù)。所以,我們要定義一個(gè)能打印日志的decorator,可以定義如下:
def log(func): def wrapper(*args, **kw): print('call %s():' % func.__name__) return func(*args, **kw) return wrapper @log def now(): print('Now time is 00:00')觀察上面的log,因?yàn)樗且粋€(gè)decorator,所以接受一個(gè)函數(shù)作為參數(shù),并返回一個(gè)函數(shù)。借助Python的@語(yǔ)法,把decorator置于函數(shù)now()的定義處.
如果decorator本身需要傳入?yún)?shù),那就需要編寫(xiě)一個(gè)返回decorator的高階函數(shù),寫(xiě)出來(lái)會(huì)更復(fù)雜。比如,要自定義log的文本:
def log(text): def decorator(func): def wrapper(*args, **kw): print('%s %s():' % (text, func.__name__)) return func(*args, **kw) return wrapper return decorator @log("debug") def now(): print('Now time is 00:00')
在面向?qū)ο螅∣OP)的設(shè)計(jì)模式中,decorator被稱為裝飾模式。OOP的裝飾模式需要通過(guò)繼承和組合來(lái)實(shí)現(xiàn),而Python除了能支持OOP的decorator外,直接從語(yǔ)法層次支持decorator。Python的decorator可以用函數(shù)實(shí)現(xiàn),也可以用類實(shí)現(xiàn)。
decorator可以增強(qiáng)函數(shù)的功能,定義起來(lái)雖然有點(diǎn)復(fù)雜,但使用起來(lái)非常靈活和方便。
再次強(qiáng)調(diào)arg與*kwargs參數(shù)的用法
在python中,當(dāng)*和**符號(hào)出現(xiàn)在函數(shù)定義的參數(shù)中時(shí),表示任意數(shù)目參數(shù)收集。*arg表示任意多個(gè)無(wú)名參數(shù),類型為tuple;**kwargs表示關(guān)鍵字參數(shù),為dict,使用時(shí)需將*arg放在**kwargs之前,否則會(huì)有“SyntaxError: non-keyword arg after keyword arg”的語(yǔ)法錯(cuò)誤.
- *允許你傳入0個(gè)或任意個(gè)參數(shù),這些可變參數(shù)在函數(shù)調(diào)用時(shí)自動(dòng)組裝為一個(gè)tuple。
- **關(guān)鍵字參數(shù)允許你傳入0個(gè)或任意個(gè)含參數(shù)名的參數(shù),這些關(guān)鍵字參數(shù)在函數(shù)內(nèi)部自動(dòng)組裝為一個(gè)dict。
- 在函數(shù)混合使用以及*,命名參數(shù)進(jìn)入**, 其他進(jìn)入*
上面是在函數(shù)定義的時(shí)候?qū)懙?和**形式,那反過(guò)來(lái),如果*和**語(yǔ)法出現(xiàn)在函數(shù)調(diào)用中又會(huì)如何呢?
他會(huì)解包參數(shù)的集合。例如,我們?cè)谡{(diào)用函數(shù)時(shí)能夠使用*語(yǔ)法,在這種情況下,它與函數(shù)定義的意思相反,他會(huì)解包參數(shù)的集合,而不是創(chuàng)建參數(shù)的集合。
#通過(guò)一個(gè)元組給一個(gè)函數(shù)傳遞四個(gè)參數(shù),并且讓python將它們解包成不同的參數(shù)。
def func(a,b,c,d):
print(a,b,c,d)
a = (1,2,3,4)
func(*a)
# 如果已經(jīng)有一個(gè)元祖,在參數(shù)前加*,函數(shù)會(huì)把元祖中的元素一個(gè)一個(gè)傳到函數(shù)里面
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
print(sum)
num = (1,2,3,4)
calc(*num)
#如果已經(jīng)有一個(gè)dict,在參數(shù)前面加**,函數(shù)會(huì)把dict中所有鍵值對(duì)轉(zhuǎn)換為關(guān)鍵字參數(shù)傳進(jìn)去
def person(name,age,**kw):
print('name:',name,'age:',age,'other:',kw)
extra = {'city': 'Beijing', 'job': 'Engineer'}
person('Jack', 24, **extra)
知識(shí)點(diǎn):
在函數(shù)調(diào)用時(shí),*會(huì)以單個(gè)元素的形式解包一個(gè)元祖,使其成為獨(dú)立的參數(shù)。
在函數(shù)調(diào)用時(shí),**會(huì)以鍵/值對(duì)的形式解包一個(gè)字典,使其成為獨(dú)立的關(guān)鍵字參數(shù)。
偏函數(shù)
Python的functools模塊提供了很多有用的功能,其中一個(gè)就是偏函數(shù)(Partial function)。要注意,這里的偏函數(shù)和數(shù)學(xué)意義上的偏函數(shù)不一樣。
在介紹函數(shù)參數(shù)的時(shí)候,我們講到,通過(guò)設(shè)定參數(shù)的默認(rèn)值,可以降低函數(shù)調(diào)用的難度。而偏函數(shù)也可以做到這一點(diǎn)。舉例如下:
int()函數(shù)可以把字符串轉(zhuǎn)換為整數(shù),當(dāng)僅傳入字符串時(shí),int()函數(shù)默認(rèn)按十進(jìn)制轉(zhuǎn)換:
int('12345')
但int()函數(shù)還提供額外的base參數(shù),默認(rèn)值為10。如果傳入base參數(shù),就可以做N進(jìn)制的轉(zhuǎn)換:
int('12345', base=8)
int('12345', 16)
假設(shè)要轉(zhuǎn)換大量的二進(jìn)制字符串,每次都傳入int(x, base=2)非常麻煩,于是,我們想到,可以定義一個(gè)int2()的函數(shù),默認(rèn)把base=2傳進(jìn)去:
def int2(x, base=2):
return int(x, base)
int2(111)
functools.partial就是幫助我們創(chuàng)建一個(gè)偏函數(shù)的,不需要我們自己定義int2(),可以直接使用下面的代碼創(chuàng)建一個(gè)新的函數(shù)int2:
import functools
int2 = functools.partial(int, base=2)
int2('1000000')
int2('1010101'
簡(jiǎn)單總結(jié)functools.partial的作用就是,把一個(gè)函數(shù)的某些參數(shù)給固定?。ㄒ簿褪窃O(shè)置默認(rèn)值),返回一個(gè)新的函數(shù),調(diào)用這個(gè)新函數(shù)會(huì)更簡(jiǎn)單。
正則表達(dá)式
這個(gè)不得不說(shuō)下,因?yàn)槭褂玫臅r(shí)候太多了。
正則表達(dá)式是一種用來(lái)匹配字符串的強(qiáng)有力的武器。它的設(shè)計(jì)思想是用一種描述性的語(yǔ)言來(lái)給字符串定義一個(gè)規(guī)則,凡是符合規(guī)則的字符串,我們就認(rèn)為它“匹配”了,否則,該字符串就是不合法的。
re模塊
Python提供re模塊,包含所有正則表達(dá)式的功能。
s = 'ABC\\-001' # Python的字符串
# 對(duì)應(yīng)的正則表達(dá)式字符串變成:
# 'ABC\-001'
因此我們強(qiáng)烈建議使用Python的r前綴,就不用考慮轉(zhuǎn)義的問(wèn)題了:
s = r'ABC\-001' # Python的字符串
# 對(duì)應(yīng)的正則表達(dá)式字符串不變:
# 'ABC\-001'
如何判斷正則表達(dá)式是否匹配:
import re
re.match(r'^\d{3}\-\d{3,8}$', '010-12345')
re.match(r'^\d{3}\-\d{3,8}$', '010 12345')
match()方法判斷是否匹配,如果匹配成功,返回一個(gè)Match對(duì)象,否則返回None。常見(jiàn)的判斷方法就是:
test = '用戶輸入的字符串'
if re.match(r'正則表達(dá)式', test):
print('ok')
else:
print('failed')
切分字符串
re.split(r'\s+', 'a b c')
分組
除了簡(jiǎn)單地判斷是否匹配之外,正則表達(dá)式還有提取子串的強(qiáng)大功能。用()表示的就是要提取的分組(Group)。
^(\d{3})-(\d{3,8})$分別定義了兩個(gè)組,可以直接從匹配的字符串中提取出區(qū)號(hào)和本地號(hào)碼:
m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345')
m.group(0)
m.group(1)
m.group(2)
編譯
當(dāng)我們?cè)赑ython中使用正則表達(dá)式時(shí),re模塊內(nèi)部會(huì)干兩件事情:
編譯正則表達(dá)式,如果正則表達(dá)式的字符串本身不合法,會(huì)報(bào)錯(cuò);
用編譯后的正則表達(dá)式去匹配字符串。
如果一個(gè)正則表達(dá)式要重復(fù)使用幾千次,出于效率的考慮,我們可以預(yù)編譯該正則表達(dá)式,接下來(lái)重復(fù)使用時(shí)就不需要編譯這個(gè)步驟了,直接匹配:
import re
re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$') # 編譯
re_telephone.match('010-12345').groups() #('010','12345')
re_telephone.match('010-8086').groups()
Python的web編程
WSGI接口
最簡(jiǎn)單的Web應(yīng)用就是先把HTML用文件保存好,用一個(gè)現(xiàn)成的HTTP服務(wù)器軟件,接收用戶請(qǐng)求,從文件中讀取HTML,返回。Apache、Nginx、Lighttpd等這些常見(jiàn)的靜態(tài)服務(wù)器就是干這件事情的。
如果要?jiǎng)討B(tài)生成HTML,就需要把上述步驟自己來(lái)實(shí)現(xiàn)。不過(guò),接受HTTP請(qǐng)求、解析HTTP請(qǐng)求、發(fā)送HTTP響應(yīng)都是苦力活,如果我們自己來(lái)寫(xiě)這些底層代碼,還沒(méi)開(kāi)始寫(xiě)動(dòng)態(tài)HTML呢,就得花個(gè)把月去讀HTTP規(guī)范。
正確的做法是底層代碼由專門的服務(wù)器軟件實(shí)現(xiàn),我們用Python專注于生成HTML文檔。因?yàn)槲覀儾幌M佑|到TCP連接、HTTP原始請(qǐng)求和響應(yīng)格式,所以,需要一個(gè)統(tǒng)一的接口,讓我們專心用Python編寫(xiě)Web業(yè)務(wù)。
這個(gè)接口就是WSGI:Web Server Gateway Interface。
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'<h1>Hello, web!</h1>']
上面的application()函數(shù)就是符合WSGI標(biāo)準(zhǔn)的一個(gè)HTTP處理函數(shù),它接收兩個(gè)參數(shù):
- environ:一個(gè)包含所有HTTP請(qǐng)求信息的dict對(duì)象;
- start_response:一個(gè)發(fā)送HTTP響應(yīng)的函數(shù)。
在application()函數(shù)中,調(diào)用:
start_response('200 OK', [('Content-Type', 'text/html')])
就發(fā)送了HTTP響應(yīng)的Header,注意Header只能發(fā)送一次,也就是只能調(diào)用一次start_response()函數(shù)。start_response()函數(shù)接收兩個(gè)參數(shù),一個(gè)是HTTP響應(yīng)碼,一個(gè)是一組list表示的HTTP Header,每個(gè)Header用一個(gè)包含兩個(gè)str的tuple表示。
通常情況下,都應(yīng)該把Content-Type頭發(fā)送給瀏覽器。其他很多常用的HTTP Header也應(yīng)該發(fā)送。
然后,函數(shù)的返回值b'<h1>Hello, web!</h1>'將作為HTTP響應(yīng)的Body發(fā)送給瀏覽器。
有了WSGI,我們關(guān)心的就是如何從environ這個(gè)dict對(duì)象拿到HTTP請(qǐng)求信息,然后構(gòu)造HTML,通過(guò)start_response()發(fā)送Header,最后返回Body。
整個(gè)application()函數(shù)本身沒(méi)有涉及到任何解析HTTP的部分,也就是說(shuō),底層代碼不需要我們自己編寫(xiě),我們只負(fù)責(zé)在更高層次上考慮如何響應(yīng)請(qǐng)求就可以了。
不過(guò),等等,這個(gè)application()函數(shù)怎么調(diào)用?如果我們自己調(diào)用,兩個(gè)參數(shù)environ和start_response我們沒(méi)法提供,返回的bytes也沒(méi)法發(fā)給瀏覽器。
所以application()函數(shù)必須由WSGI服務(wù)器來(lái)調(diào)用。有很多符合WSGI規(guī)范的服務(wù)器,我們可以挑選一個(gè)來(lái)用。但是現(xiàn)在,我們只想盡快測(cè)試一下我們編寫(xiě)的application()函數(shù)真的可以把HTML輸出到瀏覽器,所以,要趕緊找一個(gè)最簡(jiǎn)單的WSGI服務(wù)器,把我們的Web應(yīng)用程序跑起來(lái)。
好消息是Python內(nèi)置了一個(gè)WSGI服務(wù)器,這個(gè)模塊叫wsgiref,它是用純Python編寫(xiě)的WSGI服務(wù)器的參考實(shí)現(xiàn)。所謂“參考實(shí)現(xiàn)”是指該實(shí)現(xiàn)完全符合WSGI標(biāo)準(zhǔn),但是不考慮任何運(yùn)行效率,僅供開(kāi)發(fā)和測(cè)試使用。
運(yùn)行WSGI服務(wù)
我們先編寫(xiě)hello.py,實(shí)現(xiàn)Web應(yīng)用程序的WSGI處理函數(shù):
# hello.py
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'<h1>Hello, web!</h1>']
然后,再編寫(xiě)一個(gè)server.py,負(fù)責(zé)啟動(dòng)WSGI服務(wù)器,加載application()函數(shù):
# server.py
# 從wsgiref模塊導(dǎo)入:
from wsgiref.simple_server import make_server
# 導(dǎo)入我們自己編寫(xiě)的application函數(shù):
from hello import application
# 創(chuàng)建一個(gè)服務(wù)器,IP地址為空,端口是8000,處理函數(shù)是application:
httpd = make_server('', 8000, application)
print('Serving HTTP on port 8000...')
# 開(kāi)始監(jiān)聽(tīng)HTTP請(qǐng)求:
httpd.serve_forever()
確保以上兩個(gè)文件在同一個(gè)目錄下,然后在命令行輸入python server.py來(lái)啟動(dòng)WSGI服務(wù)器:
wsgiref-start
注意:如果8000端口已被其他程序占用,啟動(dòng)將失敗,請(qǐng)修改成其他端口。
啟動(dòng)成功后,打開(kāi)瀏覽器,輸入http://localhost:8000/,就可以看到結(jié)果了:
hello-web
在命令行可以看到wsgiref打印的log信息:
wsgiref-log
按Ctrl+C終止服務(wù)器。
如果你覺(jué)得這個(gè)Web應(yīng)用太簡(jiǎn)單了,可以稍微改造一下,從environ里讀取PATH_INFO,這樣可以顯示更加動(dòng)態(tài)的內(nèi)容:
# hello.py
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
body = '<h1>Hello, %s!</h1>' % (environ['PATH_INFO'][1:] or 'web')
return [body.encode('utf-8')]
你可以在地址欄輸入用戶名作為URL的一部分,將返回Hello, xxx!:
小結(jié)
無(wú)論多么復(fù)雜的Web應(yīng)用程序,入口都是一個(gè)WSGI處理函數(shù)。HTTP請(qǐng)求的所有輸入信息都可以通過(guò)environ獲得,HTTP響應(yīng)的輸出都可以通過(guò)start_response()加上函數(shù)返回值作為Body。
復(fù)雜的Web應(yīng)用程序,光靠一個(gè)WSGI函數(shù)來(lái)處理還是太底層了,我們需要在WSGI之上再抽象出Web框架,進(jìn)一步簡(jiǎn)化Web開(kāi)發(fā)。
Web框架
因?yàn)閃SGI提供的接口雖然比HTTP接口高級(jí)了不少,但和Web App的處理邏輯比,還是比較低級(jí),我們需要在WSGI接口之上能進(jìn)一步抽象,讓我們專注于用一個(gè)函數(shù)處理一個(gè)URL,至于URL到函數(shù)的映射,就需要交給Web框架來(lái)做。
Python開(kāi)發(fā)一個(gè)Web框架十分容易,所以Python有上百個(gè)開(kāi)源的Web框架。
常見(jiàn)的web框架有:
Flask:
Django:全能型Web框架;
web.py:一個(gè)小巧的Web框架;
Bottle:和Flask類似的Web框架;
Tornado:Facebook的開(kāi)源異步Web框架。
使用模板
Web框架把我們從WSGI中拯救出來(lái)了。現(xiàn)在,我們只需要不斷地編寫(xiě)函數(shù),帶上URL,就可以繼續(xù)Web App的開(kāi)發(fā)了。
但是,Web App不僅僅是處理邏輯,展示給用戶的頁(yè)面也非常重要。在函數(shù)中返回一個(gè)包含HTML的字符串,簡(jiǎn)單的頁(yè)面還可以,但是,想想新浪首頁(yè)的6000多行的HTML,你確信能在Python的字符串中正確地寫(xiě)出來(lái)么?反正我是做不到。
俗話說(shuō)得好,不懂前端的Python工程師不是好的產(chǎn)品經(jīng)理。有Web開(kāi)發(fā)經(jīng)驗(yàn)的同學(xué)都明白,Web App最復(fù)雜的部分就在HTML頁(yè)面。HTML不僅要正確,還要通過(guò)CSS美化,再加上復(fù)雜的JavaScript腳本來(lái)實(shí)現(xiàn)各種交互和動(dòng)畫(huà)效果??傊?,生成HTML頁(yè)面的難度很大。
由于在Python代碼里拼字符串是不現(xiàn)實(shí)的,所以,模板技術(shù)出現(xiàn)了。
使用模板,我們需要預(yù)先準(zhǔn)備一個(gè)HTML文檔,這個(gè)HTML文檔不是普通的HTML,而是嵌入了一些變量和指令,然后,根據(jù)我們傳入的數(shù)據(jù),替換后,得到最終的HTML,發(fā)送給用戶。
至此整個(gè)從框架到模板就是MVC:Model-View-Controller,中文名“模型-視圖-控制器”。
Python處理URL的函數(shù)就是C:Controller,Controller負(fù)責(zé)業(yè)務(wù)邏輯,比如檢查用戶名是否存在,取出用戶信息等等;
包含變量{{ name }}的模板就是V:View,View負(fù)責(zé)顯示邏輯,通過(guò)簡(jiǎn)單地替換一些變量,View最終輸出的就是用戶看到的HTML。
MVC中的Model在哪?Model是用來(lái)傳給View的,這樣View在替換變量的時(shí)候,就可以從Model中取出相應(yīng)的數(shù)據(jù)。
Python 常用模板處理工具有:
Jinja2:
Mako:用<% ... %>和${xxx}的一個(gè)模板;
Cheetah:也是用<% ... %>和${xxx}的一個(gè)模板;
Django:Django是一站式框架,內(nèi)置一個(gè)用{% ... %}和{{ xxx }}的模板。
用Python做一些算法和數(shù)據(jù)結(jié)構(gòu)的練習(xí)
算法
算法的五大特征:
(1) 輸入性:有零個(gè)或多個(gè)外部量作為算法的輸入
(2) 輸出性: 算法至少有一個(gè)量作為輸出
(3) 確定性:算法中每條指令清晰,無(wú)歧義
(4) 有窮性:算法中每條指令的執(zhí)行次數(shù)有限,執(zhí)行每條指令時(shí)間也有限
(5) 可行性:算法原則上能夠精確的運(yùn)行,而且人們用紙和筆做有限次運(yùn)算后即可
完成
算法有時(shí)間復(fù)雜度和空間復(fù)雜度
時(shí)間復(fù)雜度的大小關(guān)系:
O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)