第九篇:python函數

什麼是函數

組織好的、可重複使用的、用戶實現單一或者關聯功能的代碼段。函數可以提升應用的模塊性和代碼的重複利用率。使得python代碼的組織結構清晰,可讀性強,解決代碼冗餘,實現某功能的代碼可統一管理且易於維護。Python提供了不少內置的函數,好比len()、print()等等,另外也能夠根據本身的業務需求進行用戶自定義函數的建立。html

定義函數

函數代碼塊以 def 關鍵詞開頭,後接函數標識符名稱和圓括號()。python

任何傳入參數和自變量必須放在圓括號中間。數組

函數的第一行語句能夠選擇性地使用文檔字符串---用於存放函數說明。數據結構

函數內容以冒號起始,而且縮進。閉包

語法:def 函數名(用逗號隔開的形參):app

     '''函數說明'''ide

     要封裝的代碼塊函數

a = 1
print(a.__class__)
print(issubclass(a.__class__, object))   # all objects in Python inherit from a common baseclass
def foo():
    pass
print(foo.__class__)
print(issubclass(foo.__class__, object))

'''
輸出結果:
        <class 'int'>
        True
        <class 'function'>
        True
'''

 能夠看到foo和變量a同樣,都是頂級父類object的子類。a是一個int變量,foo是一個函數。因此,函數和python的其餘東西同樣,都屬於對象,其父類是object。這意味着,函數能夠被引用,能夠看成參數傳遞,能夠看成返回值,能夠看成容器類型的元素。學習

調用函數

函數使用的原則:先定義,再調用,調用一個函數須要知道函數的名稱和函數的參數。函數名後面接着圓括號(),須要傳入的參數寫在圓括號()裏。函數名其實就是指向一個函數對象的引用,徹底能夠把函數名賦值給一個變量,至關於給這個函數起了一個別名。優化

語法:函數名(用逗號隔開的實參)

#定義函數
def xAddY(x,y):
    print('x + y = {}'.format(x+y))

#調用函數
str1 = 'abc'
str2 = 'def'
xAddY(str1,str2)

a = xAddY
a(2,3)
函數在定義時,只檢測語法,不執行代碼。函數在被調用時,纔會執行被它封裝的代碼塊。也就說。語法錯誤在函數定義階段就會被檢測出來,而代碼的邏輯錯誤在執行時纔會知道。
#定義函數
def xAddY():
    x = 1
    y = '2'
    print('x + y = {}'.format(x+y))  #整數和字符串相加是代碼邏輯的錯誤。
print('函數xAddY()不被調用,運行代碼不會報錯,若調用函數xAddY(),函數裏的x和y不是同類型數據,執行到x+y時就會報錯。')
# xAddY()

函數參數

形參與實參

形參變量只有在函數被調用時才分配內存單元,調用結束時,馬上釋放所分配的內存單元,所以,形參只在函數內部有效。

實參能夠是常量、變量、表達式、函數等、不管實參是何種類型的量,在進行函數調用時,它們都必須有肯定的值,以便把這些值傳遞給形參。

形參即變量名,實參即變量值,函數調用時,將值綁定到變量名上,函數調用結束,解除綁定。

 函數參數分類

必備參數(位置參數,關鍵字參數)

函數定義中容許擁有多個形參。函數在定義時按照從左到右的順序定義沒有默認值的參數,在調用的時,就必須傳入多少個實參,這樣的形參就是必備參數。

向函數傳遞參數的方式有不少,經過實參和形參的順序對應,這樣的傳參方式叫作位置傳參。必選參數就是位置形參,按照位置給形參傳值的實參就是位置實參。只有位置一致,才能被正確匹配。位置實參是最簡單也最經常使用的關聯方式。

按照key=value的形式定義的實參,叫作關鍵字實參,這種傳參方式就是關鍵字傳參,無需按照位置給形參傳值。關鍵字實參是傳遞給函數的名稱-值對。直接在實參中將名稱和值關聯起來,所以向函數傳遞實參時不會混淆。函數調用使用關鍵字參數來肯定傳入的值。使用關鍵字參數容許函數調用時參數的順序與聲明時不一致,由於Python解釋器可以用參數名匹配參數值。

注意:在給函數傳參時,關鍵字實參必須在位置實參右邊,同一個形參不能重複傳值。

#函數定義
def test(left,centre,right):
    print('{}在左邊,{}在中間,{}在右邊。'.format(left,centre,right))

#位置傳參
test('小貓','小豬','小狗')

#關鍵字傳參
test(right='小狗',left='小貓',centre='小豬')

#位置傳參+關鍵字傳參
test('小貓',right='小狗',centre='小豬') #關鍵字實參在位置實參右邊,位置實參對象的形參,不能再經過關鍵字傳值。

#錯誤的傳參
# test(left='小貓','小豬',right='小狗')
# test('小貓',left='xiaomao',centre='小豬',right='小狗')

默認參數 

函數定義的時候,設置的參數是形參。那麼也能夠給每一個形參指定一個默認值。當調用函數時,若是沒有傳入實參,就使用形參的默認值。若是調用的時候傳入了實參,那麼程序將使用傳入的實參。

注意:函數在定義時,默認參數在必備參數的右邊。

#函數定義
def example(left,right,centre='小雞'):  #必備參數在默認參數左邊。
    print('{}在左邊,{}在中間,{}在右邊。'.format(left,centre,right))
example('小貓','小狗')  #未給默認參數傳值
example('小貓','小狗','小豬')  #經過位置實參傳值給默認參數

不定長參數(參數組)

 你可能須要一個函數能處理比當初聲明時更多的參數,即傳入實參不固定。這些參數叫作不定長參數。定義函數時加了星號(*)的變量名會存放全部未命名的變量參數(一般使用*agrs)。加了(**)會存放全部命名的變量參數(一般使用**kwargs)。在元祖或列表前加*做爲參數傳值,叫作未命名的可變長參數,未命名的可變長參數經過位置傳參給必備參數和默認參數傳參,多餘的參數存放在函數里加了*的形參。字典前加**做爲參數傳值,叫作命名的可變長參數,命名的可變長參數經過關鍵字傳參,字典的鍵對應形參名,而後給形參參值,多餘的參數存放在函數里加了**的形參裏。

 關於不定長參數:*args在左邊,**kwargs在右邊。

#函數定義
def foo(x,*var): #不定長參必須在必備參數的右邊
print('必備參數x:{}'.format(x))
print('其餘傳入的參數:{}'.format(var),end='\n'*2)
def bar(y,z=None,*var,**kwvar): #命名的不定長參必須在默認參數的右邊
print('必備參數y:{}'.format(y))
print('默認參數z:{}'.format(z))
print('其餘傳入的未命名參數:{}'.format(var))
print('其餘傳入的命名實參:{}'.format(kwvar),end='\n'*2)

#不定長參的傳值方式一
foo(3,4,5,6,'sf')
bar(3,a=2,b=3)

#不定長參的傳值方式二:給未命名不定長參傳入一個前面加*的列表,給命名不定長參傳入一個前面加**字典
foo(3,*[4,5,6,'sf'])
bar(*(3,4,5,6),**{'a':2,'b':3})

不管是定義函數,仍是調用函數,作好的參數順序寫法是(位置參數,*args,默認參數 | 關鍵字實參,**kwargs)。

函數參數--可變對象與不可變對象(傳遞的時候)

 不可變類型數據與可變類型數據傳遞參數的區別可參考:引用傳遞與值傳遞

不可變類型:如 整數、字符串、元組。如fun(a),傳遞的只是a的值,沒有影響a對象自己。好比在 fun(a)內部修改 a 的值,只是修改另外一個複製的對象,不會影響 a 自己。

可變類型:如 列表,字典。如 fun(la),則是將 la 真正的傳過去,修改後fun外部的la也會受影響。

#函數定義
def test(var):
    if isinstance(var,str):
        var += 'str'
    elif isinstance(var,list):
        var += [5,68,9]
str1 = 'abc'  #不可變類型的表明
list1 = [1,2,3]  #可變類型的表明
test(str1)
test(list1)
print(str1)
print(list1)

函數返回值

函數並不是老是將結果直接輸出,相反,函數的調用者須要函數提供一些經過函數處理事後的一個或者一組數據,只有調用者擁有了這個數據,纔可以作一些其餘的操做。那麼這個時候,就須要函數返回給調用者數據,這個就被稱之爲返回值,想要在函數中把結果返回給調用者,須要在函數中使用return。

 return語句:用於退出函數(中斷函數執行),選擇性的向調用者返回一個表達式。直接return的語句返回None。return還能夠返回多個值,值與值之間用逗號隔開。return返回的對象是臨時對象,若沒有變量或對象引用它,將被垃圾回收機制銷燬。咱們能夠將函數的返回值保存在變量中。

Return和print區別:print是將結果輸出到控制檯,return語句結束函數的調用,並將結果返回給調用者,且返回的結果不能輸出到控制檯(也就是不能直接打印出來)須要經過print才能打印出來。

def example(x,y):
    add = x+y
    sub = x-y
    return add,sub  #返回兩個數值
num,num2 = example(6,3) #用變量接受返回值。

#打印返回值
print(num,num2)
print(example(9,2))
print(type(example(9,2)))

def test(min,max):
    list1 = []
    while True:
        if min % 2 == 0:
            list1.append(min)
        elif min >= max:
            return list1 #中斷函數執行
        min += 1
print(test(10,50)) #返回兩個之間的偶數

注意:

1.函數裏若是沒有return,會默認 return None。

2.函數若是return多個對象,那麼python會幫咱們把多個對象封裝成一個元祖返回。

函數類型

經過以上學習,咱們對函數的主要分爲四種類型:

無參數,無返回值的函數
無參數,有返回值的函數
有參數,無返回值的函數
有參數,有返回值的函數

嵌套函數與閉包

python是容許建立嵌套函數的,也就是說咱們能夠在函數內部定義一個函數,也就是內嵌函數。這些函數都遵循各自的做用域和生命週期規則。在最外層函數叫主函數,屬於全局做用域,定義了就能夠被調用,內嵌函數只能在定義它的函數中直接調用,不能在全局做用域或者其餘函數中調用。

x = 1111111111111111
def f1():
    #x=1
    print('------>f1 ',x)
    def f2():
        #x = 2
        print('---->f2 ',x)
        def f3():
            x=3
            print('-->f3 ',x)
        f3()     
    f2()
f1()
def f1():
    print('------>f1 ',x)
    def f2():
        print('---->f2 ',x)
        def f3():
            print('-->f3 ',x)
        return f3
    return f2
print(f1())
f1()()
fun = f1()
fun()()

python中的閉包

閉包是由函數及其相關的引用環境組合而成的實體(即:閉包=函數+引用環境),有了嵌套函數這種結構,便會產生閉包問題,閉包函數是嵌套函數的特殊實現。在函數式語言中,函數能夠做爲另外一個函數的參數或返回值,當內嵌函數體內引用到體外的變量(除了全局變量),將會把定義時涉及到的引用環境和函數體打包成一個總體(閉包)返回。通常狀況下,在咱們認知當中,若是一個函數結束,函數的內部全部東西都會釋放掉,還給內存,局部變量都會消失。可是閉包是一種特殊狀況,若是外函數在結束的時候發現有本身的臨時變量未來會在內部函數中用到,就把這個臨時變量綁定給了內部函數,而後本身再結束。

閉包函數的必要條件:

  外函數返回了內函數的引用

  內函數引用外函數的局部變量或參數。

# NO.1
def line_conf(a, b):
    def line(x):
        print(x)
        return a * x + b
    return line

# NO.2
def line_con():
    a = 1
    b = 2
    def line(x):
        print(a * x + b)
    return line

# NO.3
def _line_(a, b):
    def line_c(c):
        def line(x):
            return a * (x ** 2) + b * x + c
        return line
    return line_c
print(line_conf(3,5)(9))
line_con()(6)
示例

閉包函數對象必定有__closure__屬性,__closure__屬性返回的是一個元組對象,包含了閉包引用的外部變量。若是不是閉包函數對象__closure__屬性永遠爲None或不存在__closure__屬性。

def out():
    a = 1
    b = 2
    def inner(x):
        print(x)  #<<<------若主函數內的閉包不引用外部變量,就不存在閉包,主函數的_closure__屬性永遠爲None
    return inner
print(out().__closure__) # None
for i in out().__closure__: #拋出異常
    print(i.cell_contents)

def line_conf():
    a = 1
    b = 2
    def line(x):
        print(a * x + b)
    return a + b  # <<<------若主函數沒有return子函數,就不存在閉包,主函數不存在_closure__屬性
print(line_conf().__closure__)  # 拋出異常  

#閉包函數
def outer():
    a = 1
    b = 2
    def inner(x):
        print(x,a,b)  #<<<------主函數內的閉包引用外部變量
    return inner
print(outer().__closure__) # None
for i in outer().__closure__:
    print(i.cell_contents)
示例

還有一點須要注意:使用閉包的過程當中,一旦外函數被調用一次返回了內函數的引用,雖然每次調用內函數,是開啓一個函數執行事後消亡,可是閉包變量實際上只有一份,每次開啓內函數都在使用同一份閉包變量。

def outer(x):
    def inner(y):
        nonlocal x
        x+=y
        return x
    return inner
a = outer(10)
print(a(1)) #11
print(a(3)) #14
示例

閉包能夠保存運行環境

_list = []
for i in range(3):
    def func(a):
        return i+a
    _list.append(func)
for f in _list:
    print(f(1))

'''
在Python中,循環體內定義的函數是沒法保存循環執行過程當中的不停變化的外部變量的,即普通函數沒法保存運行環境!
想要讓上面的代碼輸出1, 2, 3並不難,「術業有專攻」,這種事情該讓閉包來:
'''

_list = []
for i in range(3):
    def func(i):
        def f_closure(a):  # <<<---
            return i + a
        return f_closure
    _list.append(func(i))  # <<<---
for f in _list:
    print(f(1))
示例
'''不能保存運行環境'''
flist = []
for i in range(3):
    def foo(x): print(x + i)
    flist.append(foo)
for f in flist:
    f(1)

'''修改以後'''
flist = []
for i in range(3):
    def foo(x,y=i): print(x + y)
    flist.append(foo)
for f in flist:
    f(1)
View Code

閉包函數的應用:

裝飾器、面向對象、單例模式

'''
我以一個相似棋盤遊戲的例子來講明。假設棋盤大小爲50*50,左上角爲座標系原點(0,0),我須要一個函數,接收2個參數,
分別爲方向(direction),步長(step),該函數控制棋子的運動。棋子運動的新的座標除了依賴於方向和步長之外,
固然還要根據原來所處的座標點,用閉包就能夠保持住這個棋子原來所處的座標。'''
# origin = [0, 0]  # 座標系統原點
# legal_x = [0, 50]  # x軸方向的合法座標
# legal_y = [0, 50]  # y軸方向的合法座標
# def create(pos=origin):
#     def player(direction, step):
#         # 這裏應該首先判斷參數direction,step的合法性,好比direction不能斜着走,step不能爲負等
#         # 而後還要對新生成的x,y座標的合法性進行判斷處理,這裏主要是想介紹閉包,就不詳細寫了。
#         new_x = pos[0] + direction[0] * step
#         new_y = pos[1] + direction[1] * step
#         pos[0] = new_x
#         pos[1] = new_y
#         # 注意!此處不能寫成 pos = [new_x, new_y],沒有關鍵字的聲明,不能局部做用域對外部做用域的變量進行從新賦值。
#         return pos
#     return player
#
# player = create()  # 建立棋子player,起點爲原點
# print(player([1, 0], 10))  # 向x軸正方向移動10步
# print(player([0, 1], 20))  # 向y軸正方向移動20步
# print(player([-1, 0], 10))   # 向x軸負方向移動10步

'''取得文件"result.txt"中含有"pass"關鍵字的行'''
# def make_filter(keep):
#     def the_filter(file_name):
#         file = open(file_name)
#         lines = file.readlines()
#         file.close()
#         filter_doc = [i for i in lines if keep in i]
#         return filter_doc
#     return the_filter
# filter = make_filter("pass")
# filter_result = filter("result.txt")


'''面向對象'''
# def who(name):
#     def do(what):
#         print(name, 'say:', what)
#     return do
# lucy = who('lucy')
# john = who('john')
# lucy('i want drink!')
# lucy('i want eat !')
# lucy('i want play !')
# john('i want play basketball')
# john('i want to sleep with U,do U?')
# lucy("you just like a fool, but i got you!")


'''閉包實現快速給不一樣項目記錄日誌,只需在你想要logging的位置添加一行代碼便可。'''
# import logging
# def log_header(logger_name):
#     logging.basicConfig(level=logging.DEBUG, format='%(asctime)s [%(name)s] %(levelname)s  %(message)s',
#                         datefmt='%Y-%m-%d %H:%M:%S')
#     logger = logging.getLogger(logger_name)
#     def _logging(something, level):
#         if level == 'debug':
#             logger.debug(something)
#         elif level == 'warning':
#             logger.warning(something)
#         elif level == 'error':
#             logger.error(something)
#         else:
#             raise Exception("I dont know what you want to do?")
#     return _logging
# project_1_logging = log_header('project_1')
# project_2_logging = log_header('project_2')
# def project_1():
#     # do something
#     project_1_logging('this is a debug info', 'debug')
#     # do something
#     project_1_logging('this is a warning info', 'warning')
#     # do something
#     project_1_logging('this is a error info', 'error')
# def project_2():
#     # do something
#     project_2_logging('this is a debug info', 'debug')
#     # do something
#     project_2_logging('this is a warning info', 'warning')
#     # do something
#     project_2_logging('this is a critical info', 'error')
# project_1()
# project_2()


'''單例模式:所謂單例,是指一個類的實例從始至終只能被建立一次。python中的使用@語法實現的單例模式就是利用閉包實現的,
只不過用了@做爲語法糖(裝飾器),使寫法更簡潔,閉包函數將函數的惟一實例保存在它內部的__closure__屬性中,在再次建立函數實例時,
閉包檢查該函數實例已存在本身的屬性中,不會再讓他建立新的實例,而是將現有的實例返給它。'''
# def Singleton(cls):
#     _instance = {}
#     def _singleton(*args, **kargs):
#         if cls not in _instance:
#             _instance[cls] = cls(*args, **kargs)
#         return _instance[cls]
#     return _singleton
# 
# @Singleton
# class A(object):
#     a = 1
#     def __init__(self, x=0):
#         self.x = x
# a1 = A(2)
# a2 = A(3)
示例

 全局變量與局部變量

名稱空間:Python全部有關命名的操做都是在操做名稱空間,例如變量名,函數名。即存放名字的地方,三種名稱空間(如x=1,1存放於內存中,那名字x存放在哪裏呢?名稱空間正是存放名字x與1綁定關係(引用)的地方)。

一、內置名稱空間:Python解釋器提供好的功能,解釋器啓動跟着一塊兒啓動,是全局做用域(即內置在解釋器中的名稱)。

二、全局名稱空間:Python中頂行寫的,不在函數內部定義的,都是全局名稱空間,在運行的時候會產生名稱空間,是全局做用域(通常寫的頂頭名稱) 。

三、局部名稱空間:在一個小範圍定義,只能當前範圍及其子空間內運行,例如在函數內部定義的,是局部做用域。

python執行語句找一個名稱的查找順序:先在局部名稱空間找,再到全局名稱空間找,再到內置名稱空間( locals -> enclosing function(閉包) -> globals -> __builtins__)。

須要注意的是:在全局沒法查看局部的,在局部能夠查看全局的。

import builtins
a = (8,9)
def f1():
    b = 9
    print(locals())  #查看局部名稱空間的內容。
    def f2():
        print(b)
    return f2
print(dir(builtins)) #查看內建名稱空間的內容。
print(globals())  #查看全局名稱空間的內容。
print(f1().__closure__) #查看閉包保存引用外層函數的局部變量或參數。

 變量的做用域

一、做用域即範圍:
  - 全局範圍(內置名稱空間與全局名稱空間屬於該範圍):全局存活,全局有效。
  - 局部範圍(局部名稱空間屬於該範圍):臨時存活,局部有效(函數有本身的做用域,在函數外不能訪問函數裏的定義的變量)。
二、做用域關係是在函數定義階段就已經固定的,與函數的調用位置無關。

全局變量是聲明在函數外部的變量,定義在函數外的擁有全局做用域。通常命名規則,全局變量名爲大寫,局部變量名爲小寫。

局部變量,就是在函數內部定義的變量。

不一樣的函數,能夠定義相同的名字的局部變量,可是各用個的不會產生影響。

Python中函數的做用域由def關鍵字界定,函數內的代碼訪問變量的方式是從其所在層級由內向外的。

 一個程序的全部的變量並非在哪一個位置均可以訪問的。訪問權限決定於這個變量是在哪裏賦值的。局部變量是定義在函數內部的變量,擁有一個局部做用域,只能在該局部做用域裏訪問它。全局變量是定義在函數外的變量,擁有全局做用域,在整個程序裏均可以訪問它。

咱們瞭解變量的做用域後,知道在函數裏有本身的做用域,還能夠訪問全局變量,函數體裏若是訪問全局變量後,再定義與全局變量同名的局部變量就會出現報錯,以下例:

var = 10
def fun():
    print(var)
    var = 5

def fun():
    var += 5

def fun():
    var = var + 1

print(var) #會報錯:UnboundLocalError: local variable 'var' referenced before assignment

報錯分析:

函數是python第一類對象,定義好後就造成了本身的做用域,有本身的局部名稱空間,並做爲對象的形式存在內存,而按照python裏查找名稱的順序,先從局部名稱空間查找,再到全局名稱空間查找,在上面的例子裏函數定義了變量var,因此變量var在局部名稱空間是能找到的,函數體要訪問的變量var都是局部變量的var,當函數被調用,執行到要訪問var時,在局部名稱空間能找到變量var,但執行到此步以前,函數體裏沒有執行過定義var變量的語句。因此拋出了語句順序的邏輯錯誤(UnboundLocalError: local variable 'var' referenced before assignment)。而不是找不到變量名的錯誤(NameError: name 'var' is not defined)。

global關鍵字與nonlocal關鍵字

要想在函數內部給全局變量從新賦值或修改不可變類型,須要使用grobal關鍵字聲明。global關鍵字修飾變量後標識該變量是全局變量,對該變量進行修改就是修改全局變量。

若是函數的內容無global關鍵字,能讀取全局變量,在函數裏給全局變量賦值則是定義與其同名的局部變量,沒法對全局變量從新賦值,可是對可變類型,能夠對內部元素進行操做。若函數定義了與全局變量同名的局部變量,則優先讀取局部變量。

若是函數中有global關鍵字,聲明引用全局變量,變量本質上是全局的哪一個變量,可讀取可賦值。

在函數嵌套中,內嵌函數想要給外層函數定義的變量從新賦值或修改不可變類型,須要使用nonlocal關鍵字,nonlocal關鍵字修飾變量後標識該變量是外層函數中的局部變量,若是嵌套該函數的外層函數中都不存在該局部變量,nonlocal位置會發生錯誤(主函數使用nonlocal修飾變量一定會報錯)。

NAME = '小明'
def fun():
    name = '小薇'
    def test():
        global NAME
        nonlocal name
        print('內嵌函數訪問到的name是:{}'.format(name))
        name = '小欣'
        NAME = '小威'
    print('調用函數test前的局部變量name的值:{}'.format(name))
    test()
    print('調用函數test後的局部變量name的值:{}'.format(name))
print('調用函數fun前的全局變量NAME的值:{}'.format(NAME))
fun()
print('調用函數fun後的全局變量NAME的值:{}'.format(NAME))

經常使用高階函數

知足兩個特性任意一個即爲高階函數:

1.函數接受的參數是個函數名。

2.返回值是個函數名。

list1 = [2,3,5,6,8]
list2 = [1,2,3,8,9]
list3 = [3,2,1,8,9]
def myMap(func,*iterable):
    for e in range(len(iterable[0])):
        element = []
        for i in range(len(iterable)):
            element.append(iterable[i][e])
        res = func(*element)
        yield res

for i in myMap(lambda x,y,z:x+y+z,list1,list2,list3):
    print(i)
print(list(myMap(lambda x,y,z:x+y+z,list1,list2,list3)))

def myMap(func,*iterable):
    result = []
    for e in range(len(iterable[0])):
        element = []
        for i in range(len(iterable)):
            element.append(iterable[i][e])
        res = func(*element)
        result.append(res)
    return result

python內置高階函數:

def myFilter(func,iterable):
    for e in iterable:
        if func(e):
            yield e
def myReduce(func,iterable,init=None):
    if init==None:
        result = iterable[0]
        for i in range(1, len(iterable)):
            result = func(result, iterable[i])
        return result
    else:
        result = init
        for i in range(len(iterable)):
            result = func(result, iterable[i])
        return result

from functools import reduce

list1 = ['sub','start','print','min','str']
list2 = (2,3,6,8,2)
print(map(lambda x:x*2,list1))
print(list(map(lambda x:x*2,list1)))
print(filter(lambda x:x.startswith('s'),list1))
print(list(filter(lambda x:x.startswith('s'),list1)))
print(reduce(lambda x,y:x+y,list2))

裝飾器

裝飾器:裝飾器就是閉包函數的一種應用場景,本質就是函數,功能是爲其餘函數添加附加功能。

裝飾器原則:1.不修改被修飾函數的源代碼。2.不修改被修飾函數的調用方式。

實現裝飾器的知識儲備:裝飾器 = 高階函數 + 閉包

語法糖(@):@decorator   至關於  modified_function = decorator(modified_function)

實現裝飾器返回被裝飾函數的返回值。

import time

#添加打印函數運行時間的功能
def decorator(func):
    def inner():
        start_time = time.time()
        func()
        # result = func()
        stop_time = time.time()
        print('函數對象{}的運行時間是:{}'.format(func.__name__,stop_time-start_time))
        # return result
    return inner

@decorator  #至關於  modified_function = decorator(modified_function)
def modified_function():
    time.sleep(1)
    print('在modified_function函數裏的輸出語句')
    return '函數modified_function的返回值'

test = modified_function()  # 至關於 test = inner()
print(test)

裝飾器實現驗證功能:

def verification(fun):
    names = {'張三':'123','李四':'456'}
    def inner(*args,**kwargs):
        name = input('用戶名:')
        pswd = input('密碼:')
        if names[name] == pswd:
            result = fun(*args,**kwargs)
            return result
        else:
            print('用戶名或密碼錯誤')
    return inner
@verification
def index():
    print('歡迎來到我的主頁')

index()
user_list = [{'name':'小維','passwd':'123'},{'name':'小薇','passwd':'456'},{'name':'小欣','passwd':'789'}]
login_status = {'name':None,'lonin':False}
def verification(fun):
    names = {'張三':'123','李四':'456'}
    def inner(*args,**kwargs):
        if login_status['name'] and login_status['lonin']:
            result = fun(*args,**kwargs)
            return result
        user_name = input('用戶名:')
        pswd = input('密碼:')
        for user_dict in user_list:
            if user_name == user_dict['name'] and pswd == user_dict['passwd']:
                login_status['name'] = user_name
                login_status['lonin'] = True
                res = fun(*args,**kwargs)
                return res
        else:
            print('用戶名或密碼錯誤')
    return inner
@verification
def index():
    print('歡迎來到我的主頁')

@verification
def change_password():
    print('修改密碼')
    pass

@verification
def change_name():
    print('修改用戶名')
    pass

index()
change_name()
change_password()

裝飾器加參數:

import time

#添加打印函數運行時間的功能
def function_doc(docstring):
    def decorator(func):
        def inner():
            start_time = time.time()
            func()
            # result = func()
            stop_time = time.time()
            print('{}:'.format(docstring))
            print('函數對象{}的運行時間是:{}'.format(func.__name__,stop_time-start_time))
            # return result
        return inner
    return decorator

@function_doc('添加了打印函數運行時間的功能')  #至關於  modified_function = function_doc(docstring)(modified_function)
def modified_function():
    time.sleep(1)
    print('在modified_function函數裏的輸出語句')
    return '函數modified_function的返回值'

modified_function()

functools.wraps

裝飾器通常返回一個包裝器(wrapper),查看被裝飾函數對象的屬性的時候,返回的不是被裝飾函數的屬性,而是包裝器的屬性,若是須要要保持原屬性,functools.wraps就是裝飾包裝器的裝飾器。

from functools import wraps

def decorator(func):
    @wraps(func) #加在最內層函數正上方
    def wrapper(*args,**kwargs):
        '''包裝器'''
        return func(*args,**kwargs)
    return wrapper

@decorator
def index():
    '''哈哈哈哈'''
    print('from index')

print(index.__doc__)
print(index.__name__)
print(help(index))

疊加多個裝飾器

1. 加載順序(outter函數的調用順序):自下而上

2. 執行順序(wrapper函數的執行順序):自上而下

def outter1(func1): #func1=wrapper2的內存地址
    print('加載了outter1')
    def wrapper1(*args,**kwargs):
        print('執行了wrapper1')
        res1=func1(*args,**kwargs)
        return res1
    return wrapper1

def outter2(func2): #func2=wrapper3的內存地址
    print('加載了outter2')
    def wrapper2(*args,**kwargs):
        print('執行了wrapper2')
        res2=func2(*args,**kwargs)
        return res2
    return wrapper2

def outter3(func3): # func3=最原始的那個index的內存地址
    print('加載了outter3')
    def wrapper3(*args,**kwargs):
        print('執行了wrapper3')
        res3=func3(*args,**kwargs)
        return res3
    return wrapper3

@outter1 # outter1(wrapper2的內存地址)======>index=wrapper1的內存地址     outter2 = outter1(outter2) | wrapper1
@outter2 # outter2(wrapper3的內存地址)======>wrapper2的內存地址           outter3 = outter2(outter3) | wrapper2
@outter3 # outter3(最原始的那個index的內存地址)===>wrapper3的內存地址     index = outter3(index) | wrapper3
def index():   # 至關於執行了語句  index = outter1(outter2(outter3(index)))
    return 'from index'
print('======================================================')
print(index())
由 outter1(outter2(outter3(index)))的傳參過程
可推index的執行過程:
outter1(outter2(outter3(index))) ======> 參數func3 = index ,outter3()返回 wrapper3
outter1(outter2(wrapper3))     ======> 參數func2 = wrapper3 ,outter2()返回 wrapper2
outter1(wrapper2)           ======> 參數func1 = wrapper2 ,outter1()返回 wrapper1
index = wrapper1
上面的過程只要運行模塊,不管是否調用index函數,都會被執行,而後造成閉包。
下面接着講解index被調用的過程。
index() ======> wrapper1()
wrapper1()      ======> res1 = func1() ,res1 在等待func1的返回結果
func1()        ======> wrapper2()
wrapper2()       ======> res2 = func2() ,res2 在等待func2的返回結果
func2()        ======> wrapper3()
wrapper3()       ======> res3 = index() ,res3 在等待func1的返回結果
return 'from index' ======> res3 = index() = 'from index'
return res3      ======> res2 = func2() = wrapper3() = 'from index'
return res2      ======> res1 = func1() = wrapper2() = 'from index'
return res1      ======> index() = wrapper1() = 'from index'

遞歸函數

遞歸函數:遞歸是函數嵌套調用的一種特殊形式,遞歸就是子程序(或函數)直接調用本身或經過一系列調用語句間接調用本身,這樣的函數就叫遞歸函數,是一種描述問題和解決問題的基本方法。(一句話,本身調用本身)

def calc(n):
    if int(n / 2) == 0:  
        return n
    n = int(n / 2)
    re = calc(n)
    return re
print(calc(10))

 遞歸優化:尾調用。

尾調用的概念很是簡單,一句話就能說清楚,就是指某個函數的最後一步是調用另外一個函數。

咱們知道,函數調用會在內存造成一個"調用記錄",又稱"調用幀"(call frame),保存調用位置和內部變量等信息。若是在函數A的內部調用函數B,那麼在A的調用記錄上方,還會造成一個B的調用記錄。等到B運行結束,將結果返回到A,B的調用記錄纔會消失。若是函數B內部還調用函數C,那就還有一個C的調用記錄棧,以此類推。全部的調用記錄,就造成一個"調用棧"(call stack)。

尾調用因爲是函數的最後一步操做,因此不須要保留外層函數的調用記錄,由於調用位置、內部變量等信息都不會再用到了,只要直接用內層函數的調用記錄,取代外層函數的調用記錄就能夠了。

如下兩種狀況,都不屬於尾調用。

#狀況一
def calc(n):
    if int(n / 2) == 0:
        return n
    n = int(n / 2)
    re = calc(n)
    return re

#狀況二
def calc(n):
    if int(n / 2) == 0:
        return n
    n = int(n / 2)
    # return 2+calc(n)
    return calc(n)+calc(n-1)

函數調用自身,稱爲遞歸。若是尾調用自身,就稱爲尾遞歸。

遞歸很是耗費內存,由於須要同時保存成千上百個調用記錄,很容易發生"棧溢出"錯誤(stack overflow)。但對於尾遞歸來講,因爲只存在一個調用記錄,因此永遠不會發生"棧溢出"錯誤。

上面代碼改寫成尾遞歸,只保留一個調用記錄:

def calc(n): if int(n / 2) == 0:
        return n
    n = int(n / 2)
    return calc(n)
print(calc(10))

深刻理解python尾遞歸

雖然尾遞歸優化很好, 但python 不支持尾遞歸,遞歸深度超過1000時會報錯,下面以一個階乘的例子演示。

#常規遞歸階乘
def normal_recursion(n):
    "calculate a factorial"
    if n == 1:
        return 1
    return n*normal_recursion(n-1)
'''執行normal_recursion(5):
normal_recursion(5)
5 * normal_recursion(4)
5 * 4 * normal_recursion(3)
5 * 4 * 3 * normal_recursion(2)
5 * 4 * 3 * 2 * normal_recursion(1)
5 * 4 * 3 * 2
5 * 4 * 6
5 * 24
120
能夠看到, 通常遞歸, 每一級遞歸都須要調用函數, 會建立新的棧,隨着遞歸深度的增長, 建立的棧愈來愈多, 形成爆棧
'''

#尾遞歸階乘
def tail_recursion(n, acc=1):
    "calculate a factorial"
    if n == 0:
        return acc
    return tail_recursion(n-1, n*acc)
'''執行tail_recursion(5):
tail_recursion(5)
tail_recursion(4,5)
tail_recursion(3,20)
tail_recursion(2,60)
tail_recursion(1,120)
tail_recursion(0,120)
120
'''

print(normal_recursion(1000)) #有遞歸深度的限制,會報錯。
print(tail_recursion(1000)) #有遞歸深度的限制,會報錯。

在其餘語言中有支持尾遞歸的,因而一個牛人想出的解決辦法:實現一個 tail_call_optimized 裝飾器。

import sys
class TailRecurseException(BaseException):
    def __init__(self, args, kwargs):
        self.args = args
        self.kwargs = kwargs
def tail_call_optimized(g):
    """
    This function decorates a function with tail call
    optimization. It does this by throwing an exception
    if it is it's own grandparent, and catching such
    exceptions to fake the tail call optimization.

    This function fails if the decorated
    function recurses in a non-tail context.
    """
    def func(*args, **kwargs):
        f = sys._getframe()
        if f.f_back and f.f_back.f_back and f.f_back.f_back.f_code == f.f_code:
            # 拋出異常
            raise TailRecurseException(args, kwargs)
        else:
            while 1:
                try:
                    return g(*args, **kwargs)
                except TailRecurseException as e:
                    args = e.args
                    kwargs = e.kwargs
    func.__doc__ = g.__doc__
    return func

@tail_call_optimized
def factorial(n, acc=1):
    "calculate a factorial"
    if n == 0:
        return acc
    return factorial(n-1, n*acc)
print(factorial(10000))

或是用sys模塊的一個內置方法設置遞歸深度。

import sys
sys.setrecursionlimit(1000000)

總結遞歸的使用:

1. 必須有一個明確的結束條件(使用return中斷執行)。

2. 每次進入更深一層遞歸時,問題規模相比上次遞歸都應有所減小。

3. 遞歸效率不高,須要在進入下一次遞歸時保留當前的狀態,遞歸層次過多會致使棧溢出,在其餘語言中能夠有解決方法:尾遞歸優化,即在函數的最後一步(而非最後一行)調用本身,尾遞歸優化:可是python又沒有尾遞歸,且對遞歸層級作了限制(在計算機中,函數調用是經過棧(stack)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返回,棧就會減一層棧幀。因爲棧的大小不是無限的,因此,遞歸調用的次數過多,會致使棧溢出)。

匿名函數

匿名函數:定義函數的過程當中,沒有給定名稱的函數就叫作匿名函數;Python中使用lambda表達式來建立匿名函數。
lambda 來建立匿名函數規則。
●lambda只是一個表達式,函數體比def簡單不少。
●lambda的主體是一個表達式,而不是一個代碼塊,因此不能寫太多的邏輯進去。
●lambda函數擁有本身的命名空間。
●lambda定義的函數的返回值就是表達式的返回值,不須要return語句塊。
●lambda表達式的主要應用場景就是賦值給變量、做爲參數傳入其它函數。

lambda匿名函數的表達式規則是:lambda 參數列表: 表達式。

有名字的函數與匿名函數的對比:

有名函數:循環使用,保存了名字,經過名字就能夠重複引用函數功能 。

匿名函數:一次性使用,隨時隨時定義

fun1 = lambda x,y:x+y #給匿名函數命名
print(fun1)

'''相似於'''
def fun(x,y):
    return x+y
print(fun)
print(fun1(5))
print(fun(5))

內置函數

python提供了一些內置函數,可方便開發人員使用,內置函數主要來自「__builtins__」模塊,__builtins__   模塊是python的內置模塊,啓動python便會自動導入,因此可使用print(dir(__builtins__))查看‘「__builtins__」模塊全部內置函數。

zip():

str1 = 'abcde'
list1 = [1,2,3,4,5]
for i in zip(str1,list1):
    print(i)
element1,element2 = zip(*zip(str1,list1)) #zip() 與 * 運算符相結合能夠用來拆解一個列表
print(element1)

max():

1.max函數處理的是可迭代對象,至關於一個for循環取出每一個元素進行比較。注意,不一樣類型之間不能進行比較。

2.每一個元素間進行比較,是從每一個元素的第一個位置依次比較,若是第一位置分出大小,後面的都不須要比較了,直接得出這兩個元素的大小。

str1 = 'abcde'
list1 = [1,2,3,4,5]
list2 = [[6,7],[89,0]]
fruit = {'Apples':60, 'bananas':20, 'oranges':30}
people = [{'name':'小薇','age':18},{'name':'小北','age':20},{'name':'小月','age':19}]
print(max(list1))
print(max(list2))
print(max(str1))
print(max(fruit))
print(max(zip(fruit.values(),fruit.keys())))  #結合zip
# print(max(people)) #會報錯,不支持字典與字典的比較

#解決字典之間的比較
print(max(people,key=lambda dic:dic['age']))

sorted():

fruit = {'Apples':60, 'bananas':20, 'oranges':30}
people = [{'name':'小薇','age':18},{'name':'小北','age':20},{'name':'小月','age':19}]
print(sorted(fruit))
print(sorted(fruit.values()))
print(sorted(fruit,key=lambda key:fruit[key]))
print(sorted(zip(fruit.values(),fruit.keys())))
print(sorted(people,key=lambda dic:dic['age']))
相關文章
相關標籤/搜索