Python學(xué)習(xí)筆記-第13天:函數(shù)編程和正則表達(dá)式

第十三天 函數(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ò)誤.

  1. *允許你傳入0個(gè)或任意個(gè)參數(shù),這些可變參數(shù)在函數(shù)調(diào)用時(shí)自動(dòng)組裝為一個(gè)tuple。
  2. **關(guān)鍵字參數(shù)允許你傳入0個(gè)或任意個(gè)含參數(shù)名的參數(shù),這些關(guān)鍵字參數(shù)在函數(shù)內(nèi)部自動(dòng)組裝為一個(gè)dict。
  3. 在函數(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)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容