简明 Python 教程学习笔记_2_函数
?
?
1. Python 內建函數
?
Python 內建函數:https://docs.python.org/zh-cn/3.9/library/functions.html#classmethod
Python 解釋器內置了很多函數和類型,您可以在任何時候使用它們。以下按字母表順序列出它們。
|   abs()  |   delattr()  |   hash()  |   memoryview()  |   set()  | 
|   all()  |   dict()  |   help()  |   min()  |   setattr()  | 
|   any()  |   dir()  |   hex()  |   next()  |   slice()  | 
|   ascii()  |   divmod()  |   id()  |   object()  |   sorted()  | 
|   bin()  |   enumerate()  |   input()  |   oct()  |   staticmethod()  | 
|   bool()  |   eval()  |   int()  |   open()  |   str()  | 
|   breakpoint()  |   exec()  |   isinstance()  |   ord()  |   sum()  | 
|   bytearray()  |   filter()  |   issubclass()  |   pow()  |   super()  | 
|   bytes()  |   float()  |   iter()  |   print()  |   tuple()  | 
|   callable()  |   format()  |   len()  |   property()  |   type()  | 
|   chr()  |   frozenset()  |   list()  |   range()  |   vars()  | 
|   classmethod()  |   getattr()  |   locals()  |   repr()  |   zip()  | 
|   compile()  |   globals()  |   map()  |   reversed()  |   __import__()  | 
|   complex()  |   hasattr()  |   max()  |   round()  | ? | 
?
python之內置函數,匿名函數:https://www.cnblogs.com/jin-xin/articles/8423937.html
?
1.1 作用域相關:
- locals :函數會以字典的類型返回當前位置的全部局部變量。
 - globals:函數以字典的類型返回全部全局變量。
 
?
1.2 其他相關
1.2.1 字符串類型代碼的執(zhí)行 eval,exec,complie。
( 執(zhí)行簡單的Python時用 eval,復雜的用 exec,一般不用 compile )
- eval:執(zhí)行字符串類型的代碼,并返回最終結果。
 - exec:執(zhí)行字符串類型的代碼。
 - compile:將字符串類型的代碼編譯。代碼對象能夠通過exec語句來執(zhí)行或者eval()進行求值。
 
示例:
""" eval() 函數用來執(zhí)行一個字符串表達式,并返回表達式的值。 """ eval('2 + 2') # 4 n = 81 eval("n + 4") # 85 eval('print(666)') # 666""" exec 執(zhí)行儲存在字符串或文件中的 Python 代碼,相比于 eval,exec可以執(zhí)行更復雜的 Python 代碼。 """ s = ''' for i in [1,2,3]:print(i) list(map(lambda i: print(i), [4, 5, 6])) ''' print(f'exec : {exec(s)}')''' 參數說明: 1. 參數 source:字符串或者AST(Abstract Syntax Trees)對象。即需要動態(tài)執(zhí)行的代碼段。 2. 參數 filename:代碼文件名稱,如果不是從文件讀取代碼則傳遞一些可辨認的值。當傳入了source參數時,filename參數傳入空字符即可。 3. 參數model:指定編譯代碼的種類,可以指定為 ‘exec’,’eval’,’single’。當source中包含流程語句時,model應指定為‘exec’;當source中只包含一個簡單的求值表達式,model應指定為‘eval’;當source中包含了交互式命令語句,model應指定為'single'。 ''' # 流程語句使用 exec code1 = 'for i in range(0,10): print (i)' compile1 = compile(code1, '', 'exec') exec(compile1)# 簡單求值表達式用 eval code2 = '1 + 2 + 3 + 4' compile2 = compile(code2, '', 'eval') eval(compile2)# 交互語句用 single code3 = 'name = input("please input your name:")' compile3 = compile(code3, '', 'single') # name # 執(zhí)行前name變量不存在 # Traceback (most recent call last): # File "<pyshell#29>", line 1, in <module> # name # NameError: name 'name' is not definedexec(compile3) # 執(zhí)行時顯示交互命令,提示輸入 # please input your name:'pythoner' # name #執(zhí)行后name變量有值 # "'pythoner'"1.2.2 輸入輸出相關: input,print
1.2.3 內存相關 hash、id
? ? ? ? ? ? ? ? hash:獲取一個對象(可哈希對象:int,str,Bool,tuple)的哈希值。id:用于獲取對象的內存地址。
1.2.3 文件操作相關 open
? ? ? ? ? ? ? ? open 函數用于打開一個文件,創(chuàng)建一個?file?對象,相關的方法才可以調用它進行讀寫。
1.2.4 模塊相關?__import__?
? ? ? ? ? ? ? ? __import__?函數用于動態(tài)加載類和函數 。
1.2.5 幫助 help
? ? ? ? ? ? ? ? help 函數用于查看函數或模塊用途的詳細說明。print(help(list))
1.2.6 調用相關 callable
? ? ? ? ? ? ? ? callable 函數用于檢查一個對象是否是可調用的。如果返回True,object 仍然可能調用失敗;但如果返回False,調用對象ojbect絕對不會成功。
1.2.7 查看內置屬性 dir
????????????????dir 函數不帶參數時,返回當前范圍內的變量、方法和定義的類型列表;
 ????????????????dir 帶參數時,返回參數的屬性、方法列表。
 ????????????????如果參數包含方法__dir__(),該方法將被調用。如果參數不包含__dir__(),該方法將最大限度地收集參數信息。
?
1.3 迭代器生成器相關
- range:函數可創(chuàng)建一個整數對象,一般用在 for 循環(huán)中。
 - next:內部實際使用了__next__方法,返回迭代器的下一個項目。
 - iter:函數用來生成迭代器(講一個可迭代對象,生成迭代器)。
 
?
1.4 基礎數據類型相關
1.4.1 數字相關(14)
數據類型(4 個):
- bool :用于將給定參數轉換為布爾類型,如果沒有參數,返回 False。
 - int:函數用于將一個字符串或數字轉換為整型。
 - float:函數用于將整數和字符串轉換成浮點數。
 - complex:函數用于創(chuàng)建一個值為 real + imag * j 的復數或者轉化一個字符串或數為復數。如果第一個參數為字符串,則不需要指定第二個參數。。
 
進制轉換(3 個):
- bin:將十進制轉換成二進制并返回。
 - oct:將十進制轉化成八進制字符串并返回。
 - hex:將十進制轉化成十六進制字符串并返回。
 
數學運算(7 個):
- abs: ? 函數返回數字的絕對值。
 - divmod:計算除數與被除數的結果,返回一個包含商和余數的元組(a // b, a % b)。
 - round:保留浮點數的小數位數,默認保留整數。
 - pow:求x**y次冪。(三個參數為x**y的結果對z取余)
 - sum:對可迭代對象進行求和計算(可設置初始值)。
 - min:返回可迭代對象的最小值(可加key,key為函數名,通過函數的規(guī)則,返回最小值)。
 - max:返回可迭代對象的最大值(可加key,key為函數名,通過函數的規(guī)則,返回最大值)。
 
1.4.2 和數據結構相關(24個)
列表和元祖(2)
     list:將一個可迭代對象轉化成列表(如果是字典,默認將 key 作為列表的元素)。
     tuple:將一個可迭代對象轉化成元祖(如果是字典,默認將 key 作為元祖的元素)。
相關內置函數(2)
     reversed:將一個序列翻轉,并返回此翻轉序列的迭代器。
     slice:構造一個切片對象,用于列表的切片。
字符串相關(9)
     str:將數據轉化成字符串。
     format: 與具體數據相關,用于計算各種小數,精算等。
bytes:用于不同編碼之間的轉化。
s = '你好' bs = s.encode('utf-8') print(bs) s1 = bs.decode('utf-8') print(s1) bs = bytes(s, encoding='utf-8') print(bs) b = '你好'.encode('gbk') b1 = b.decode('gbk') print(b1.encode('utf-8'))bytearry:返回一個新字節(jié)數組。這個數組里的元素是可變的,并且每個元素的值范圍: 0 <= x < 256。
ret = bytearray('alex', encoding='utf-8') print(id(ret)) print(ret) print(ret[0]) ret[0] = 65 print(ret) print(id(ret))memoryview:內存數據查看
ret = memoryview(bytes('你好', encoding='utf-8')) print(len(ret)) print(ret) print(bytes(ret[:3]).decode('utf-8')) print(bytes(ret[3:]).decode('utf-8'))    ord:輸入字符找該字符編碼的位置
     chr:輸入位置數字找出其對應的字符
     ascii:是ascii碼中的返回該值,不是就返回/u...
repr:返回一個對象的string形式(原形畢露)。
# %r 原封不動的寫出來 name = 'taibai' print('我叫%r' % name)# repr 原形畢露 print(repr('{"name":"alex"}')) print('{"name":"alex"}')數據集合(3)
     dict:創(chuàng)建一個字典。
     set:創(chuàng)建一個集合。
     frozenset:返回一個凍結的集合,凍結后集合不能再添加或刪除任何元素。
相關內置函數(8)
     len:返回一個對象中元素的個數。
     sorted:對所有可迭代的對象進行排序操作。
enumerate:枚舉,返回一個枚舉對象。
print(enumerate([1, 2, 3])) for i in enumerate([1, 2, 3]):print(i) for i in enumerate([1, 2, 3], 100):print(i)    all:可迭代對象中,全都是True才是True
     any:可迭代對象中,有一個True 就是True
    zip:函數用于將可迭代的對象作為參數,將對象中對應的元素打包成一個個元組,然后返回由這些元組組成的列表。
        如果各個迭代器的元素個數不一致,則返回列表長度與最短的對象相同。
filter:過濾。
# filter 過濾 通過你的函數,過濾一個可迭代對象,返回的是True # 類似于[i for i in range(10) if i > 3]def func(x): return x % 2 == 0ret = filter(func, [1, 2, 3, 4, 5, 6, 7]) print(list(ret)) # [2, 4, 6]map:會根據提供的函數對指定序列做映射。
def square(x): # 計算平方數return x ** 2print(list(map(square, [1, 2, 3, 4, 5]))) # 計算列表各個元素的平方 # [1, 4, 9, 16, 25] print(list(map(lambda x: x ** 2, [1, 2, 3, 4, 5]))) # 使用 lambda 匿名函數 # [1, 4, 9, 16, 25]# 提供了兩個列表,對相同位置的列表數據進行相加 print(list(map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10]))) # [3, 7, 11, 15, 19]?
廖雪峰 Python 函數:https://www.liaoxuefeng.com/wiki/897692888725344/897693284043840
?
?
2. Python 函數
 ?
為什么要有函數 ?
- 編寫代碼時,如果沒有函數的話,將會出現很多重復的代碼,這樣代碼重用率就比較低,并且這樣的代碼很難維護,為了解決這些問題,就出現了函數,函數可以將一些經常出現的代碼進行封裝,這樣就可以在任何需要調用這段代碼的地方調用這個函數就行了。
 
什么是函數?
- 函數是指將一組語句的集合通過一個名字(函數名)封裝起來,要想執(zhí)行這個函數,只需調用其函數名即可。
 
函數的定義:
- 函數通過?def?關鍵字?定義。def 關鍵字后跟一個函數的 標識符 名稱,然后跟一對圓括號。圓括號之中可以包括一些變量名,該行以冒號結尾。接下來是一塊語句,它們是函數體。
 
下面這個例子將說明這事實上是十分簡單的:
#!/usr/bin/python # Filename: function1.pydef sayHello():print('Hello World!') # 函數塊,即 函數體sayHello() # 調用函數輸出:
$ python function1.py Hello World?
?
3. 函數參數
?
參數 在 函數定義 的圓括號對內指定,用逗號分割。當我們調用函數的時候,我們以同樣的方式提供值。
?
3.1 參數的類型、參數的種類
形參?和 實參?(?函數中的參數名稱 為 形參?,而你?提供給函數調用的值稱 為 實參 ):
- 形參:定義函數時,函數中的參數就叫做形參。
 - 實參:調用函數時,傳遞給函數的參數,形參和實參需要一一對應起來,否則調用函數會報錯。
 
函數的參數對應有如下幾種:
?
3.2 必須參數(位置參數)
#!/usr/bin/python # Filename: func_param.py def printMax(a, b):if a > b:print(f'{a} is maximum')else:print(f'{b} is maximum')printMax(3, 4) # directly give literal values x = 5 y = 7 printMax(x, y) # give variables as arguments''' $ python func_param.py 4 is maximum 7 is maximum '''在?printMax(3, 4) 中,我們直接把 整數 3、4 提供給函數( 這里 3、4 就是實參 )。
在 printMax(x, y) 中,我們把 x、y 提供給函數( 這里 x、y 就是實參 ),使 實參x 的值賦給 形參a,實參y 的值賦給 形參b。
這兩次調用中,printMax 函數的工作完全相同。
?
3.3?關鍵字參數
如果你的某個函數有許多參數,而你只想指定其中的一部分,那么你可以通過命名來為這些參數賦值,這被稱作?關鍵字參數。
即 使用 名字(關鍵字) 而不是 位置 來給函數指定實參。
這樣做有兩個?優(yōu)勢 :
- 一、由于我們不必擔心參數的順序,使用函數變得更加簡單了。
 - 二、假設其他參數都有默認值,我們可以只給我們想要的那些參數賦值。
 
使用關鍵參數
#!/usr/bin/python # Filename: func_key.pydef func(a, b=5, c=10):print(f'a is {a}, and b is {b}, and c is {c}')func(3, 7) func(25, c=24) func(c=50, a=100)''' $ python func_key.py a is 3 and b is 7 and c is 10 a is 25 and b is 5 and c is 24 a is 100 and b is 5 and c is 50 '''名為 func 的函數有一個沒有默認值的參數,和兩個有默認值的參數。
- 第一次調用函數 func 時,func(3, 7),參數a得到值3,參數b得到值7,而參數c使用默認值10。
 - 第二次調用函數 func 時,func(25, c=24) ,根據實參的位置變量a得到值25。根據命名,即關鍵參數,參數c得到值24。變量b根據默認值,為5。
 - 第三次調用函數 func 時,func(c=50, a=100) ,使用關鍵參數來完全指定參數值。注意,盡管函數定義中,a在c之前定義,我們仍然可以在a之前指定參數c的值。
 
?
3.4?默認參數
#!/usr/bin/python # Filename: func_default.pydef say(message, times=1):print(message * times)say('Hello') say('World', 5)''' $ python func_default.py Hello WorldWorldWorldWorldWorld '''名為say的函數用來打印一個字符串任意所需的次數。如果我們不提供一個值,那么默認地,字符串將只被打印一遍。我們通過給形參times指定默認參數值1來實現這一功能。
在第一次使用say的時候,我們只提供一個字符串,函數只打印一次字符串。在第二次使用say的時候,我們提供了字符串和參數5,表明我們想要說?這個字符串消息5遍。
?
重要:
只有在形參表末尾的那些參數可以有默認參數值,即你不能在聲明函數形參的時候,先聲明有默認值的形參而后聲明沒有默認值的形參。
這是因為賦給形參的值是根據位置而賦值的。例如,def func(a, b=5)是有效的,但是def func(a=5, b)是無效的。?
?
3.5? *args 和 **kwargs、參數的混合使用
假如一個函數使用了上面所有種類的參數,那該怎么辦 ?
為了不產生歧義,python 里面規(guī)定了假如有多種參數混合的情況下,遵循如下的順序使用規(guī)則:
def func(必須參數, 默認參數, *args, **kwargs):pass- 如果同時存在 *args 和 **kwargs 的話,*args 在左邊。
 - 默認參數 在 必須參數 的右邊,在 *args 的左邊。
 - 關鍵字參數的位置不固定 ( ps:關鍵字參數也不在函數定義的時候確定 )
 
示例:
def f(*args, **kwargs):print(f'args : {args}')for i in kwargs:print(f"{i} : {kwargs[i]}")f(*[1, 2, 3], **{"a": 1, "b": 2})''' args : (1, 2, 3) a : 1 b : 2 '''?
?
4. LEGB 作用域
?
- 1.? 變量查找順序:LEGB,作用域局部 ---> 外層作用域 ---> 當前模塊中的全局 ---> python內置作用域;
 - 2.? 在 Python 中,只有?模塊(module),類(class)以及?函數(def、lambda)才會 引入新的作用域,其它的代碼塊 ( 如 if、try、for 等 ) 不會引入新的作用域。
 - 3.? 對于一個變量,內部作用域先聲明就會覆蓋外部變量,不聲明直接使用時就會使用外部作用域的變量;
 - 4.? 內部作用域要修改外部作用域變量的值時,全局變量要使用 global 關鍵字,嵌套作用域變量要使用 nonlocal 關鍵字。nonlocal 是 python3 新增的關鍵字,有了這個關鍵字,就能完美的實現閉包了。
 
變量的修改( 錯誤修改,面試題里經常出 ):
x = 6def f2():print(x)x = 5f2()''' 錯誤的原因在于print(x)時, 解釋器會在局部作用域找, 會找到x=5(函數已經加載到內存), 但 x的使用 是 在聲明前, 所以報錯: local variable 'x' referenced before assignment. 如何證明找到了 x=5 呢? 簡單: 注釋掉 x=5,x=6 報錯為:name 'x' is not defined '''同理:
x = 6 def f2():x += 1 # local variable 'x' referenced before assignment. f2()?
4.1 局部變量
函數體內聲明的變量 與 函數體外同名稱的其他變量 沒有任何關系,即 函數體內的同名變量 對于函數來說 是 局部的,也只能在函數體內使用,這就是?變量的作用域 。
所有 變量的作用域 是從 變量被定義的那點開始,直到被定義的塊結束為止。
使用局部變量
#!/usr/bin/python # Filename: func_local.pydef func(x):print(f'x is {x}')x = 2print(f'Changed local x to {x}')x = 50 func(x) print(f'x is still {x}')''' x is 50 Changed local x to 2 x is still 50 '''?
4.2 使用 global 語句
如果要為 函數體外的變量 賦值,那么你就得告訴 Python 這個變量名不是局部的,而是全局的,可以使用 global 語句完成這一功能。
使用 global 語句 可以清楚地表明?變量是在外面的塊定義的。內部作用域 想修改 外部作用域的變量 時,就要用到 global 和 nonlocal 關鍵字。
沒有 global 語句,是不可能為定義在函數體外的變量賦值的,但是可以使用定義在函數外的變量的值(假設在函數內沒有同名的變量)。
示例代碼:
#!/usr/bin/python # Filename: func_global.py""" global 語句被用來聲明 x 是全局的,因此,當我們在函數內把值賦給x的時候, 這個變化也反映在我們在主塊中使用x的值的時候。 一個 global 語句指定多個全局變量。例如:global x, y, z """def func():global x, yprint(f'x is {x}')x = 2print(f'Changed global x to {x}')x = 50 y = 100 z = 200 func() print('Value of x is {x}')''' 輸出: $ python func_global.py x is 50 Changed global x to 2 Value of x is 2 '''?
4.3 nonlocal 關鍵字
global 聲明的變量必須在全局作用域上,不能嵌套作用域上,當要修改嵌套作用域(enclosing 作用域,外層非全局作用域)中的變量怎么辦呢,這時就需要 nonlocal 關鍵字了
def outer():count = 10def inner():nonlocal countcount = 20print(count)inner()print(count) outer()?
?
4.4 全面解析 Python 中 變量的作用域
?
從 局部變量 和 全局變量 開始全面解析 Python 中 變量的作用域
From :?http://www.jb51.net/article/86766.htm
?
理解 全局變量 和 局部變量
?
1. 定義在函數內部的變量名如果是第一次出現, 且在=符號前,那么就可以認為是被定義為局部變量。在這種情況下,不論全局變量中是否用到該變量名,函數中使用的都是局部變量。例如:
num = 100 def func():num = 123print num func()輸出結果是123。說明函數中定義的變量名 num 是一個局部變量,覆蓋全局變量。再例如:
num = 100 def func():num += 100 # 等價于 num = num + 100print(num) func()''' 結果:local variable 'num' referenced before assignment 解釋:程序執(zhí)行報錯,是因為局部變量 num 在賦值前被應用。也就是說該變量沒有定義就被錯誤使用。由此再次證明這里定義的是一個局部變量,而不是全局變量。 '''?
2. 函數內部的變量名如果是第一次出現,且出現在=符號后面,且在之前已被定義為全局變量,則這里將引用全局變量。例如:
num = 100 def func():x = num + 100print(x) func()''' 200 '''如果變量名 num 在之前沒有被定義為全局變量,則會出現錯誤提示:變量沒有定義。例如:
def func():x = num + 100print(x) func()''' 輸出結果是:NameError: global name 'num' is not defined。 '''?
3. 函數中使用某個變量時,如果該變量名既有全局變量也有局部變量,則默認使用局部變量。例如:
num = 100 def func():num = 200x = num + 100print(x) func()''' 300 '''?
4. 在函數中將某個變量定義為全局變量時需要使用關鍵字 global。例如:
num = 100 def func():global numnum = 200num += 300print(num)func() print(num)''' 500 500 '''?
變量作用域
變量作用域(scope)在Python中是一個容易掉坑的地方。Python的作用域一共有4中,分別是:
L (Local) ? ? ? ?局部作用域
 E (Enclosing) 閉包函數外的函數中
 G (Global) ?全局作用域
 B (Built-in) 內建作用域
以 L --> E --> G -->B 的規(guī)則查找,即:在局部找不到,便會去局部外的局部找(例如閉包),再找不到就會去全局找,再者去內建中找。
Python 除了 def / class / lambda 外,其他如:if /?elif / else / try / except for / while 并不能改變其作用域。定義在他們之內的變量,外部還是可以訪問。
if 1:a = 100 print(a)''' 100 '''定義在 if 語言中的變量 a,外部還是可以訪問的。
def test():print(var_temp)if __name__ == '__main__':var_temp = 100test()注意:如果 if 被 def / class / lambda 包裹,在其內部賦值,則就變成了此 函數 / 類 / lambda 的 局部作用域。
即在 def/class/lambda 內進行賦值,就變成了其局部的作用域,局部作用域會覆蓋全局作用域,但不會影響全局作用域。
g = 1 # 全局的def fun():g = 2 # 局部的return gprint(fun()) # 2 print(g) # 1但是要注意,有時候想在函數內部引用全局的變量,疏忽了就會出現錯誤,比如:
#file1.py var = 1 def fun():print varvar = 200 print fun()#file2.py var = 1 def fun():var = var + 1return var print fun()這兩個函數都會報錯 UnboundLocalError: local variable 'var' referenced before assignment 在未被賦值之前引用的錯誤!為什么?
因為在函數的內部,解釋器探測到 var被重新賦值了,所以 var 成為了局部變量,但是在沒有被賦值之前就想使用 var,便會出現這個錯誤。
解決的方法是在函數內部添加 globals var ,但運行函數后全局的 var 也會被修改。
?
閉包 Closure
閉包的定義:在 內部函數里面 引用 外部函數內的變量(不是全局作用域的變量),那么 內部函數 就被認為是 閉包(closure)
函數嵌套 / 閉包 中的作用域:
def external():global a # 引用全局變量aa = 200print(a)b = 100 # 局部變量 bdef internal():# nonlocal b # 使用 nonlocal 可以解決報錯print(b) # 下面有個 b=200,說明b是局部變量,但是在 print 語句之后,所以報錯,b = 200return binternal()print(b)a = 1000 # 全局變量a print(external())程序執(zhí)行報錯,因為引用在賦值之前,Python3 有個關鍵字 nonlocal 可以解決這個問題,但在 Python2 中還是不要嘗試修改閉包中的變量。
關于閉包中還有一個坑:
from functools import wrapsdef wrapper(log):def external(F):@wraps(F)def internal(**kw):if False:log = 'modified'print(log)return internalreturn external@wrapper('first') def abc():passprint(abc())也會出現 引用在賦值之前 的錯誤,
原因是解釋器探測到了 if False 中的重新賦值,所以不會去閉包的外部函數(Enclosing)中找變量,但 if Flase 不成立沒有執(zhí)行,所以便會出現此錯誤。除非你還需要 else: log='var' 或者 if True 但這樣添加邏輯語句就沒了意義,所以盡量不要修改閉包中的變量。
好像用常規(guī)的方法無法讓閉包實現計數器的功能,因為在內部進行 count +=1 便會出現 引用在賦值之前 的錯誤,解決辦法:( 或Py3環(huán)境下的 nonlocal 關鍵字)
def counter(start):count = [start]def internal():count[0] += 1return count[0]return internalcount = counter(0) for n in range(10):print(count()) # 1,2,3,4,5,6,7,8,9,10count = counter(0) print(count()) # 1由于 list 具有可變性,而字符串是不可變類型。
?
locals() 和 globals()
globals()
global 和 globals() 是不同的,
- global 是關鍵字,用來聲明一個局部變量為全局變量。
 - globals() 和 locals() 提供了基于字典的訪問 全局變量 和 局部變量 的方式
 
例如:在函數func內定義了一個局部變量 temp,但是有一個同名的函數 temp,同時要在 函數func內 引用 temp 函數。
def temp():passdef func():temp = 'Just a String'f1 = globals()['temp']print(temp)return type(f1)print(func())''' Just a String <class 'function'> '''locals()
如果你使用過 Python 的 Web 框架,那么你一定經歷過需要把一個視圖函數內很多的局部變量傳遞給模板引擎,然后作用在HTML上。
雖然你可以有一些更聰明的做法,但你還是仍想一次傳遞很多變量。先不用了解這些語法是怎么來的,用做什么,只需要大致了解 locals() 是什么。
可以看到,locals() 把局部變量都給打包一起扔去了。
@app.route('/') def view():user = User.query.all()article = Article.query.all()ip = request.environ.get('HTTP_X_REAL_IP', request.remote_addr)s = 'Just a String'# return render_template('index.html', **locals())return render_template('index.html', user=user, article=article, ip=ip, s=s)?
?
5. return 語句
?
#!/usr/bin/python # Filename: func_return.py def maximum(x, y):if x > y:return xelse:return yprint(maximum(2, 3)) # 3注意:沒有返回值的 return 語句等價于 return None。None 是 Python 中表示沒有任何東西的特殊類型。例如,如果一個變量的值為 None,可以表示它沒有值。
除非你提供你自己的 return 語句,每個函數都在結尾暗含有 return None 語句。通過運行 print someFunction(),你可以明白這一點,函數 someFunction 沒有使用 return 語句,如同:
def someFunction():pass注意: 函數在執(zhí)行過程中只要遇到 return 語句,就會停止執(zhí)行并返回結果,也可以理解為 return 語句代表著函數的結束,如果沒有在函數中指定 return,那這個函數的返回值為 None
return 多個對象,解釋器會把這多個對象組裝成一個元組,然后 return 這個元祖。
def func():return 123, 'abc', 'efg'if __name__ == "__main__":print(type(func())) # <class 'tuple'>print(func()) # (123, 'abc', 'efg')pass?
?
6. DocStrings
?
python 的 文檔字符串
#!/usr/bin/python # Filename: func_doc.py def printMax(x, y):"""Prints the maximum of two numbers.The two values must be integers.:param x: :param y: :return: """x = int(x) # convert to integers, if possibley = int(y)if x > y:print(f'{x} is maximum')else:print(f'{y} is maximum')printMax(3, 5) print(printMax.__doc__)''' 輸出 $ python func_doc.py 5 is maximum Prints the maximum of two numbers. The two values must be integers. '''在函數的第一個邏輯行的字符串是這個函數的 文檔字符串 。注意,DocStrings也適用于模塊和類,我們會在后面相應的章節(jié)學習它們。
文檔字符串的慣例是一個多行字符串,它的首行以大寫字母開始,句號結尾。第二行是空行,從第三行開始是詳細的描述。 強烈建議 你在你的函數中使用文檔字符串時遵循這個慣例。
你可以使用__doc__(注意雙下劃線)調用printMax函數的文檔字符串屬性(屬于函數的名稱)。請記住Python把每一樣東西 都作為對象,包括這個函數。我們會在后面的類一章學習更多關于對象的知識。
如果你已經在Python中使用過help(),那么你已經看到過DocStings的使用了!它所做的只是抓取函數的__doc__屬性,然后整潔地展示給你。你可以對上面這個函數嘗試一下——只是在你的程序中包括help(printMax)。記住按q退出help。
?
?
7. Python Decorator 和 函數式編程
?
來源:https://www.oschina.net/translate/decorators-and-functional-python
英文原文:Decorators and Functional Python:http://www.brianholdefehr.com/decorators-and-functional-python
?
函數式編程 的一個特點就是,允許把 函數本身作為參數 傳入另一個函數,還 允許返回一個函數。
Python 對函數式編程提供部分支持。由于 Python 允許使用變量,因此,Python 不是純函數式編程語言。
高階函數英文叫 Higher-order function。什么是高階函數?下面以實際代碼為例子,一步一步深入概念。
?
函數式編程的幾個技術
- 1.map & reduce:基于命令式的編程語言中,如果對一個數據集進行特定操作的時候,需要使用 for 或者是 while 循環(huán),讓用戶在循環(huán)語句內部進行操作,并且所有的邊界條件都是用戶自己定義的,所以就會出現邊界溢出的 bug。而 map 和 reduce 思想就是用一種更加數學化(理論化)的編程。
 - 2.Pipeline:之前map、reduce是將一組數據放到特定的函數里面進行操作,這里的操作函數一般就一個。管道(Pipeline)就像是Linux系統中的管道一樣。數據依此通過不同的管道,最后輸出。
 - 3.recuring:遞歸最大的好處就簡化代碼,他可以把一個復雜的問題用很簡單的代碼描述出來。注意:遞歸的精髓是描述問題,而這正是函數式編程的精髓。
 - 4.currying:把一個函數的多個參數分解成多個函數, 然后把函數多層封裝起來,每層函數都返回一個函數去接收下一個參數,這樣可以簡化函數的多個參數。而且每一層只專注最少的功能性代碼,所以你的代碼可維護性將大大提高,出現bug的概率也會大大降低。
 - 5.higher order function:高階函數:所謂高階函數就是函數當參數,把傳入的函數做一個封裝,然后返回這個封裝函數。現象上就是函數傳進傳出,就像面向對象對象滿天飛一樣。
 - 6.lazy evaluation:惰性求值:這個需要編譯器的支持。表達式不是在它被綁定到變量之后就立即求值,而是在該值被取用的時候求值,也就是說,語句如x:=expression; (把一個表達式的結果賦值給一個變量)明顯的調用這個表達式被計算并把結果放置到 x 中,但是先不管實際在 x 中的是什么,直到通過后面的表達式中到 x 的引用而有了對它的值的需求的時候,而后面表達式自身的求值也可以被延遲,最終為了生成讓外界看到的某個符號而計算這個快速增長的依賴樹。
 - 7.determinism :確定性:所謂確定性的意思就是像數學那樣 f(x) = y ,這個函數無論在什么場景下,都會得到同樣的結果,這個我們稱之為函數的確定性。而不是像程序中的很多函數那樣,同一個參數,卻會在不同的場景下計算出不同的結果。所謂不同的場景的意思就是我們的函數會根據一些運行中的狀態(tài)信息的不同而發(fā)生變化。
 
變量可以指向函數
以 Python 內置的求絕對值的函數 abs() 為例,調用該函數用以下代碼:
>>> abs(-10) 10但是,如果只寫 abs 呢?
>>> abs <built-in function abs>可見,abs(-10) 是函數調用,而 abs 是 函數本身。要獲得函數調用結果,我們可以把結果賦值給變量:
>>> x = abs(-10) >>> x 10但是,如果把函數本身賦值給變量呢?
>>> f = abs >>> f <built-in function abs>結論:函數本身也可以賦值給變量,即:變量可以指向函數。
如果一個變量指向了一個函數,那么,可否通過該變量來調用這個函數?用代碼驗證一下:
>>> f = abs >>> f(-10) 10 成功!說明變量f現在已經指向了abs函數本身。函數名也是變量
那么函數名是什么呢?函數名其實就是指向函數的變量!對于abs()這個函數,完全可以把函數名abs看成變量,它指向一個可以計算絕對值的函數!
如果把 abs 指向其他對象,會有什么情況發(fā)生? >>> abs = 10 >>> abs(-10) Traceback (most recent call last):File "<stdin>", line 1, in <module> TypeError: 'int' object is not callable把 abs 指向 10 后,就無法通過 abs(-10) 調用該函數了!因為 abs 這個變量已經不指向求絕對值函數了!當然實際代碼絕對不能這么寫,這里是為了說明函數名也是變量。要恢復abs函數,請重啟Python交互環(huán)境。注:由于 abs 函數實際上是定義在 __builtin__ 模塊中的,所以要讓修改 abs 變量的指向在其它模塊也生效,要用 __builtin__.abs = 10。
?
Decorators 是 Python 中最重要的特性之一。?它除了使 Python 更好用外的,它還能幫助我們以一種更有趣的方法考慮問題 --- 函數式編程的方法
我會嘗試著從零開始解釋 Decorator 是怎么工作的。首先,?我們會介紹一些幫助你理解 Decorator 的概念。?然后,?我們會深入的去解釋一些示例代碼以及他們的工作原理。?最后,?我們會討論一些更加高級的 Decorator 的用法,?諸如給他們傳可選參數,?把他們組成一個執(zhí)行鏈。?
首先,?讓我們來用我所能想到的最簡單的方法來定義 Python 中的方法 (Function)。?然后基于這個簡單的定義,再用相同方法來定義 Decorators。
方法(Function)是一段用以執(zhí)行某一特定任務的可重用的代碼。
那什么是 Decorator 呢 ?Decorator 是一個改變其他方法的方法。
現在,讓我們通過幾個先決條件來解釋 decorators 的含義。
?
7.1 函數(Functions)是第一個對象
在 Python 中 一切皆對象。這意味著即使一個函數被其他對象所包裝,我們仍然可以通過這個對象名來進行調用。?舉個列子:
def traveling_function():print("Here?I?am!")function_dict = {"func": traveling_function }trav_func = function_dict['func'] trav_func() # ?>>?Here?I?am!traveling_function 盡管在 function_dictdictionary 中被指定為 func 這個 ‘key ’的 ‘value’, 但是我們仍然可以正常的使用。
?
7.2 在高階函數中使用第一類函數
我們可以以類似其它對象的方式傳遞對象。它可以是字典的值、列表的項或是另一個對象的屬性。那么,我們不能將函數以參數的方式傳遞給另一個函數么?可以!函數作為參數傳遞給高階函數。
def self_absorbed_function():return "I'm?an?amazing?function!"def printer(func):print("The?function?passed?to?me?says:?") + func() # ?Call?`printer`?and?give?it?`self_absorbed_function`?as?an?argument printer(self_absorbed_function) # ?>>?The?function?passed?to?me?says:?I'm?an?amazing?function!??上面就是將函數作為參數傳遞另一個函數,并進行處理的示例。用這種方式,我們可以創(chuàng)造很多有趣的函數,例如?decorator。
?
7.3 Decorator 基礎
平心而論,decorator?只是將 函數作為參數 的 函數。通常,它的作用是返回封裝好的經過修飾的函數。
下面這個簡單的身份識別 decorator?可以讓我們了解 decorator?是如何工作的。
def identity_decorator(func):def wrapper():func()return wrapperdef a_function():print("I'm a normal function.")# `decorated_function` is the function that `identity_decorator` returns, which # is the nested function, `wrapper` decorated_function = identity_decorator(a_function) # This calls the function that `identity_decorator` returned decorated_function() # >> I'm a normal function在這里,identity_decoratordoes 并沒有修改其封裝的函數。它僅僅是返回了這個函數,調用了作為參數傳遞給 identity_decorator 的函數。這個decorator?毫無意義!
有趣的是在 identity_decorator 中,雖然函數沒有傳遞給 wrapper ,它依然那可以被調用,這是因為閉包原理。
?
7.4 閉包
閉包 是個花哨的術語,意思是當一個函數被聲明,在其被聲明的詞法環(huán)境中,它都可以被引用。
上例中,當 wrapper 被定義時,它就訪問了本地環(huán)境中的函數變量。一旦 identity_decorator 返回,你就只能通過 decorated_function 訪問函數。在 decorated_function 的閉包環(huán)境之外,函數并非以變量形式存在的。
?
7.5 簡單的 decorator
現在, 讓我們來創(chuàng)建一個簡單的,有點實際功能的 Decorator。這個 Decorator 所做的就是記錄他所包裝的方法被調用的次數。
def logging_decorator(func):def wrapper():wrapper.count += 1print("The function I modify has been called {0} times(s).".format(wrapper.count))func()wrapper.count = 0return wrapperdef a_function():print("I'm a normal function.")modified_function = logging_decorator(a_function) modified_function() # >> The function I modify has been called 1 time(s). # >> I'm a normal function. modified_function() # >> The function I modify has been called 2 time(s). # >> I'm a normal function.?Decorator 會修改另外一個方法,?這個例子可能會幫助你理解這其中的意思。正如你在這個例子中所看到的一樣,?logging_decorator 所返回的新方法和 a_function 很相似,只是多增加了日志功能。
在這個例子中,logging_decorator 接收一個方法作為參數,?返回另一個包裝過的方法。?每當 logging_decorator 返回的方法被調用的時候,?他會為 wrapper.count 加 1,打印 wrapper.count 的值,?然后再調用logging_decorator 所包裝的方法。
你可能會問為什么我們要把 counter 作為 wrapper 的屬性而不是一個普通的變量呢。?包裝器的閉包特性不是可以讓我們訪問其本地作用域中所申明的變量嗎?? 是的,?但是有一個小問題。?在Python中,?閉包完整的提供了對方法作用域鏈中變量的讀權限,但只為同樣作用域中的可變對象(比如:列表、字典等)提供了寫權限。然而,?Python 中的整數類型是不可變的,?所以我們無法對 wrapper 中的整數變量加 1。?取而代之的是,我們把 counter 作為 wrapper 的一個屬性,它就變成了一個可變對象,這樣我們就可以對它進行自增操作了。
?
7.6 Decorator 語法
在上一個例子中,我們看到一個 Decorator 可以接受一個方法作為參數,然后在該方法上再包裝上其他方法。
一旦你熟悉了裝飾器(Decorator),?Python 還為你提供了一個特定的語法使得它看上去更直觀,更簡單。
def logging_decorator(func):def wrapper():wrapper.count += 1print("The function I modify has been called {0} times(s).".format(wrapper.count))func()wrapper.count = 0return wrapper# In the previous example, we used our decorator function by passing the # function we wanted to modify to it, and assigning the result to a variable def some_function():print("I'm happiest when decorated.")# Here we will make the assigned variable the same name as the wrapped function some_function = logging_decorator(some_function)# We can achieve the exact same thing with this syntax: @logging_decorator def some_function():print("I'm happiest when decorated.")Decorator 語法的簡要工作原理:
記住這些步驟,再來仔細看一下 identity_decoratora 方法 和 它的注釋。
def identity_decorator(func):# ?Everything?here?happens?when?the?decorator?LOADS?and?is?passed??# ?the?function?as?described?in?step?2?above??def wrapper():# ?Things?here?happen?each?time?the?final?wrapped?function?gets?CALLED??func()return wrapper希望這里的注釋能起到一定的引導作用. 只有在裝飾器所返回的方法中的指令才會在每次調用的時候被執(zhí)行. 在被返回函數外的指令只會被執(zhí)行一次-- 在第二步 當裝飾器第一次接受一個方法的時候。
在我們研究更有趣的裝飾器之前, 我還有一件事情需要特別解釋一下。
?
7.7 代碼記憶(Memoization)
代碼記憶是一種避免潛在的重復計算開銷的方法。你通過緩存一個函數每次運行的結果來達到此目的。 這樣,下一次函數以同樣的參數運行時,它將從緩存中返回結果,并不需要花費額外的時間來計算結果。
from functools import wrapsdef memoize(func):cache = {}@wraps(func)def wrapper(*args):if args not in cache:cache[args] = func(*args)return cache[args]return wrapper@memoize def an_expensive_function(arg1, arg2, arg3):pass你可能已經注意到代碼示例中的一個奇怪的 @wrapsdecorator,在我們稍后要講解代碼記憶之前,我將簡要介紹下這個奇怪的 @wrapsdecorator。
- 使用裝飾器的一個副作用就是,裝飾之后函數丟失了它本來的 __name__、__doc__ 及 __module__ 屬性。 包裝函數用作裝飾器來包裝裝飾器返回的函數,如果被包裝的函數沒有被裝飾,則將恢復他們所有的三個屬性值。 例如:一個 _expensive_function 的名字(可以通過 _expensive_function.__name__ 來查看)將被包裝,即使我們沒有使用裝飾器。
 
我認為,代碼記憶是一個很好的使用裝飾器的示例。通過創(chuàng)建一個通用的裝飾器,他為很多函數想要的功能服務, 我們可以將裝飾器添加到任何想要利用這些功能的函數上。這避免了在不同的地方寫同樣的功能。 不重復自己(DRY)讓我們的代碼更易于維護,易于閱讀和理解。 只要看到一個單詞就可以馬上知道函數有代碼記憶。
我應該指出的是,代碼記憶只適用于純函數。因為這種函數保證了給定特定的同樣的參數就會得出同樣的結果。 如果一個函數它的結果取決于一個沒有作為參數傳遞的全局變量,或者I/O,或者其它可能影響到結果值的東西, 代碼記憶將產生令人困惑的結果!同樣,純函數沒有任何副作用。因此,如果你的讓一個計數器增加,或者在另一個對象中調用方法,或者任何不在函數得到的返回值上面的東西,如果結果是從緩存中返回的話,也不會什么副作用。
?
7.8 類的裝飾器 ( 裝飾一個類?)
上面我們說到裝飾器是修飾函數的函數。凡事總有個但是。我們還可以用它來修飾 類 或 方法。雖然一般不會這么用它。但有些情況下用來替代元類也未嘗不可。
foo = ['important', 'foo', 'stuff']def add_foo(klass):klass.foo = fooreturn klass@add_foo class Person(object):passbrian = Person()print(brian.foo) # ['important', 'foo', 'stuff']現在任何從 Person 實例出來的對象都會包含 foo 屬性。
注意:裝飾器函數 沒有返回一個函數,而是返回一個類。所以 裝飾器 是一個可以修飾 函數,類或方法 的 函數。
?
7.9 類作為一個裝飾器 ( 使用類來裝飾?)
事實證明,我在之前隱瞞了其它的什么東西。 裝飾器不僅僅可以裝飾一個類,它可以作為一個類來使用。一個裝飾器的唯一需求是他的返回值可以被調用。 這意味著當你調用一個對象時它必須實現_call_這個魔幻般的在幕后調用的方法。函數設置了這個隱式方法。讓我們重新建立 identity_decorators 作為一個類,然后來看它是怎么運作的。這個例子到底發(fā)生了什么呢:
class IdentityDecorator(object):def __init__(self, func):self.func = funcdef __call__(self):self.func()@IdentityDecorator def a_function():print("I'm a normal function.")a_function() # I'm a normal function- 當 IdentityDecorator 裝飾器裝飾了 a_function,它表現僅僅是一個裝飾器也是一個函數。這個片段相當于這個例子的裝飾語法: a_function?= IdentityDecorator(a_function)。這個類裝飾器被調用并實例化,然后把它作為參數傳遞給它所裝飾的函數。
 - 當 IdentityDecorator 實例化,它的初始化函? _init_ 與當做參數傳遞進來的裝飾函數一起調用。 這種情況下,它所做的一切就是分派給這個函數一個屬性,隨后可以被其它方法訪問。
 - 最后,當 a_function(真實的返回的 IdentityDecorator 對象包裝了 a_function )被調用,這個對象的 call 方法被引用進來,由于這僅僅是一個有標識的裝飾器,它可以簡單的調用它所裝飾的函數。?
 
讓我們再一次更新我們的裝飾器!
裝飾模式可以做為修改函數、方法或者類來被調用。?
?
7.10 帶參數的裝飾器
時你需要根據不同的情況改變裝飾器的行為,這可以通過傳遞參數來完成。
from functools import wrapsdef argumentative_decorator(gift):def func_wrapper(func):@wraps(func)def returned_wrapper(*args, **kwargs):print(f"I don't like this {gift} you gave me!")return func(gift, *args, **kwargs)return returned_wrapperreturn func_wrapper@argumentative_decorator("sweater") def grateful_function(gift):print(f"I love the {gift}! Thank you!")grateful_function()''' I don't like this sweater you gave me! I love the sweater! Thank you! '''讓我們看看如果我們不用裝飾器語法,裝飾器函數是怎么運作的:
# If we tried to invoke without an argument: grateful_function = argumentative_function(grateful_function) # But when given an argument, the pattern changes to: grateful_function = argumentative_decorator("sweater")(grateful_function)主要的要關注的是當給定一些參數,裝飾器會首先被引用并帶有這些參數,就像平時包裝過的函數并不在此列。 然后這個函數調用返回值, 裝飾器已經包裝的這個函數已經傳遞給初始化后的帶參數的裝飾器的返回函數。(這種情況下, 返回值是(argumentative_decorator("swearter")).
一步步來看:
- 1.? 解釋器到達裝飾過的函數,?編譯 grateful_function,?并把它綁定給 'grateful_fucntion' 這個名字。
 - 2.? argumentativ_decorator 被調用,?并傳遞參數 "sweater",?返回 func_wrapper.?
 - 3.? func_wrapper 被調用,?并傳入 grateful_function 作為參數,?func_wrapper 返回 returned_wrapper。
 - 4.? 最后, returned wrapper 被替代為原始的函數 grateful_function,?然后被綁定到 grateful function 這個名字下。
 
我認為當不使用裝飾器參數的時候,?這一系列的事件有點難以追蹤。請花點時間通盤考慮下,希望這對你會有點啟發(fā)。
?
7.11 帶可選參數的裝飾器
有許多方法使用帶可選參數的裝飾器。這取決于你是要用一個位置參數還是關鍵字參數,或者兩個都用。在使用上可能有一點點不同。下面就是其中的一種方法:
from functools import wrapsGLOBAL_NAME = "Brian"def print_name(function=None, name=GLOBAL_NAME):def actual_decorator(function):@wraps(function)def returned_func(*args, **kwargs):print(f"My name is {name}")return function(*args, **kwargs)return returned_funcif not function: # User passed in a name argument def waiting_for_func(function):return actual_decorator(function)return waiting_for_funcelse:return actual_decorator(function)@print_name def a_function():print("I like that name!")@print_name(name='Matt') def another_function():print("Hey, that's new!")a_function() # >> My name is Brian # >> I like that name! another_function() # >> My name is Matt # >> Hey, that's new!如果我們需要傳 name 到 print_name 方法里面,他將會和之前的 argumentative_decoratorin 效果相同。也就是說,第一個 print_name 將會把 name 作為它的參數。函數在第一次請求時返回的值將會傳遞到函數里。
如果沒有向 print_name 傳 name 的參數,將會報缺少修飾的錯。它將會像單參數函數一樣發(fā)送請求。
print_name 有這兩種可能。他要檢查收到的參數是不是一個被包裝的函數。如果不是的話,返回 waiting_for_func 函數來請求被包裝的函數。如果收到的是一個函數參數,它將會跳過中間的步驟,立刻請求actual_decorator。
?
7.12 鏈式裝飾器
今天讓我們來探索下裝飾器的最后一個特性吧:鏈式裝飾器。
你可以在任意給定的函數中放置多個裝飾器。 它使用一種類似用多繼承來構造類的方式來構造函數。但是最好不要過于追求這種方式。
from functools import wrapsGLOBAL_NAME = "Brian"def logging_decorator(func):def wrapper():wrapper.count += 1print("The function I modify has been called {0} times(s).".format(wrapper.count))func()wrapper.count = 0return wrapperdef print_name(function=None, name=GLOBAL_NAME):def actual_decorator(function):@wraps(function)def returned_func(*args, **kwargs):print(f"My name is {name}")return function(*args, **kwargs)return returned_funcif not function: # User passed in a name argument def waiting_for_func(function):return actual_decorator(function)return waiting_for_funcelse:return actual_decorator(function)@print_name(name='Sam') @logging_decorator def some_function():print("I'm the wrapped function!")some_function() ''' My name is Sam The function I modify has been called 1 times(s). I'm the wrapped function! '''當你將裝飾器鏈接在一起時,他們在放置在棧中順序是從下往上。 被包裝的函數,some_fuction,編譯之后,傳遞給在它之上的第一個裝飾器(loging_decorator). 然后第一個裝飾器的返回值傳遞給下一個。它將以這樣的式傳遞給鏈中的每一個裝飾器。
因為我們這里用到的裝飾器都是打印一個值然后返回傳遞給它們的函數。這意味著在鏈中的最后一個裝飾器,print_name,當被包裝(裝飾)的函數調用時,將打印整個輸出的第一行。
?
總結
decorator 最大的好處之一是它能讓你從高些的層次進行抽象。如果你開始讀一個方法的定義,發(fā)現他有個?amemoize decorator,你會馬上意識到你是在看 memoized 方法。如果 memoization 的代碼是在方法內部,則可能要花些額外的心思去解析,且有可能誤解。 使用 decorator,也能實現代碼重用,從而節(jié)省時間,簡化調試,使得反射更容易。使用 decorator 也是個很好的學習函數式編程概念的方式,如高級函數、閉包。
?
?
8. 高階函數 (?函數做為參數 )
?
既然變量可以指向函數,函數的參數能接收變量,那么一個函數可以接收另一個函數作為參數(即函數作為參數傳入),這種函數就稱之為高階函數。
一個最簡單的高階函數:
def add(x, y, f):return f(x) + f(y) # 當我們調用 add(-5, 6, abs)時,參數x,y和f分別接收-5,6和abs # 根據函數定義,我們可以推導計算過程為: # x ==> -5 # y ==> 6 # f ==> abs # f(x) + f(y) ==> abs(-5) + abs(6) ==> 11# 用代碼驗證一下: print(add(-5, 6, abs)) # 11 # 編寫高階函數,就是讓函數的參數能夠接收別的函數。小結:把函數作為參數傳入,這樣的函數稱為高階函數,函數式編程就是指這種高度抽象的編程范式。
?
?
8.1 python 內建的高階函數(map、reduce、filter)
?
map:我們在使用 map 函數時候,map 函數需要接收兩個參數,第一個參數是函數,第二個參數是序列。
那么表示的含義就是 map 將傳入的函數依次作用在序列中的每一個元素,并把結果以列表的形式返回。
def f(x):return x*xprint(list(map(f,[1,2,3,4,5,6,7,8,9,10]))) # list里的每個元素都會走一遍f(x)方法示例:
def doubleMe(para):return para * 2result = list(map(doubleMe, range(1, 11))) print(result) # [2,4,6,8,10,12,14,16,18,20]# 結合 lambda 函數 result = list(map(lambda x: x * 2, range(1, 11))) print(result)result = list(map(lambda x, y: x + y, range(1, 5), range(6, 10))) print(result) # [7,9,11,13]# result = map(lambda x, y: x + y, range(1, 6), range(6, 10)) # 結果是會報錯誤:TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'# 傳遞給map的是一個None的function,實際上就是做了個數據轉移操作,將數據從iterable data轉移到list中。 print(list(map(None, range(1, 3)))) # 輸出 [1,2] print(list(map(None, range(1, 3), range(5, 7)))) # 輸出[(1, 5), (2, 6)] # 可以看到第二個有多個參數組的情況下,只是把同下標的給“打包”了?
reduce:reduce 函數和 map 函數有什么不同之處呢?
reduce 函數也需要兩個參數:
- 函數:這個函數必須接收兩個參數。
 - 序列:和 map 接收的一樣
 
reduce 函數表示的含義:把 返回的結果 繼續(xù)和 序列中的下一個元素?進行相應操作。
from functools import reducesum = reduce(lambda a, x: a + x, [0, 1, 2, 3, 4]) print(sum) # 10def f2(x, y):return x + y print(reduce(f2, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])) # 55def fn(x, y):return x * y print(reduce(fn, [1, 2, 3, 4, 5])) # 計算 6!= 120def add1(x, y):return x + y print(reduce(add1, range(1, 101))) # 4950 (注:1+2+...+100) print(reduce(add1, range(1, 101), 20)) # 4970 (注:1+2+...+100+20)print(reduce(lambda x, y: x + y, range(1, 5))) # 結果是result=10 print(reduce(lambda x, y: x + y, range(1, 5), 3)) # 結果是result=13?
filter:filter 函數用于過濾序列中某些元素。和 map、reduce 函數一樣,filter 也接收一個函數和一個序列,不同的是,filter 把傳入的函數參數作用于序列中每一個元素,然后根據返回值判斷是true還是false來決定該元素是否被丟棄。
print(list(filter(lambda x: x > 3, range(1, 5)))) # [4] print(list(filter(None, range(1, 3)))) # [1,2] print(list(filter(None, [False, True]))) # [True]def is_odd(x):return x % 2 == 1 print(list(filter(is_odd, [1, 2, 3, 4, 5, 6, 7]))) # [1, 3, 5, 7]def fun1(s):if s != 'a':return sif __name__ == "__main__":str_list = ['a', 'b', 'c', 'd']ret = filter(fun1, str_list)print(list(ret)) # ret 是一個迭代器對象passsort 函數
""" python3 中 sorted 取消了對 cmp 的支持。 sorted(iterable, key=None, reverse=False)reverse 是一個布爾值。如果設置為True,列表元素將被倒序排列,默認為Falsekey 接受一個函數,這個函數只接受一個元素,默認為 None sorted([36, 5, 12, 9, 21], reverse=True)就可以實現倒序Python2中的自定義布爾函數 cmp=custom_cmp(x, y)由 Python3中的 key=custom_key(x)代替,Python2中是返回-1,1,0 在 python3 中,待比較元素 x 通過 custom_key 函數轉化為 Python能比較的值custom_key(x),進而再基于返回值進行排序。 Python3中是返回待比較的元素! 你可以想象成集合,集合就是需要用key來索引,因為它是無序的。沒有key,后面的比較函數沒法作用到前面的的元素。 """def reversed_cmp(x, y):if x > y:return -1if x < y:return 1return 0print(sorted([36, 5, 12, 9, 21], reverse=True)) # [36, 21, 12, 9, 5] print(sorted(['about', 'bob', 'Zoo', 'Credit'])) # ['Credit', 'Zoo', 'about', 'bob']# 默認情況下,對字符串排序,是按照 ASCII 的大小比較的, # 由于 'Z' < 'a',結果,大寫字母Z會排在小寫字母a的前面。 # 現在,我們提出排序應該忽略大小寫,按照字母序排序。 # 要實現這個算法,不必對現有代碼大加改動,只要我們能定義出忽略大小寫的比較算法就可以:def cmp_ignore_case(s: str):return s.upper()# 忽略大小寫來比較兩個字符串,實際上就是先把字符串都變成大寫(或者都變成小寫),再比較。 # 這樣,我們給sorted傳入上述比較函數,即可實現忽略大小寫的排序: print(sorted(['about', 'bob', 'Zoo', 'Credit'], key=cmp_ignore_case)) # ['about', 'bob', 'Credit', 'Zoo']?
?
9. lambda 匿名函數
?
lambda 語法格式:lambda [arg1 [,arg2,.....argn]]:expression
示例:
sum = lambda arg1, arg2: arg1 + arg2;print(f"Value of total : {sum( 10, 20 )}") print(f"Value of total : {sum( 20, 20 )}")''' Value of total : 30 Value of total : 40 ''' #!/usr/bin/python # Filename: lambda.pydef make_repeater(n):return lambda s: s*ntwice = make_repeater(2) print(twice('word')) print(twice(5))''' wordword 10 '''?
它如何工作
這里,我們使用了make_repeater 函數在運行時創(chuàng)建新的函數對象,并且返回它。lambda語句用來創(chuàng)建函數對象。本質上,lambda需要一個參數,后面僅跟單個表達式作為函數體,而表達式的值被這個新建的函數返回。注意,即便是print語句也不能用在lambda形式中,只能使用表達式。
 匿名函數有個限制,就是只能有一個表達式,不用寫return,返回值就是該表達式的結果。
 用匿名函數有個好處,因為函數沒有名字,不必擔心函數名沖突。此外,匿名函數也是一個函數對象,也可以把匿名函數賦值給一個變量,再利用變量來調用該函數:
?
?
10. 后期綁定 ( late binding )
?
def create_multipliers():return [lambda x: i * x for i in range(5)]for multiplier in create_multipliers():print(multiplier(2))''' 8 8 8 8 8 '''上面的例子中是不是在定義 create_multipliers()函數而不是調用它時完成了循環(huán)?
為什么設定一個默認參數就得到預期的結果?
def create_multipliers():return [lambda x, i=i: i * x for i in range(5)]for multiplier in create_multipliers():print(multiplier(2))''' 0 2 4 6 8 '''你定義一個函數,函數內的變量并不是立刻就把值綁定了,而是等調用的時候再查找這個變量,只要調用的時候環(huán)境里有就行。
同理,在 for 里面 i 的值是不斷改寫的,但是 lambda 里面只是儲存了 i 的符號,調用的時候再查找。這就是所謂的 后期綁定。
為什么你加了默認參數就成功了呢?
因為在創(chuàng)建函數的時候就要獲取默認參數的值,放到 lambda 的環(huán)境中,所以這里相當于存在一個賦值,從而使?lambda 函數環(huán)境中有了一個獨立的 i。 ?最后,優(yōu)雅的寫法是用生成器:
for multiplier in (lambda x: i * x for i in range(5)):print(multiplier(2))這樣惰性求值就可以避免 i 的改寫。或者:
def create_multipliers():for i in range(5):yield lambda x: i * xfor multiplier in create_multipliers():print(multiplier(3))?
?
11. 遞歸函數
?
遞歸函數定義:遞歸函數就是在函數內部調用自己
 有時候解決某些問題的時候,邏輯比較復雜,這時候可以考慮使用遞歸,因為使用遞歸函數的話,邏輯比較清晰,可以解決一些比較復雜的問題。但是遞歸函數存在一個問題就是假如遞歸調用自己的次數比較多的話,將會使得計算速度變得很慢,而且在python中默認的遞歸調用深度是1000層,超過這個層數將會導致“爆棧”。。。所以,在可以不用遞歸的時候建議盡量不要使用遞歸。
遞歸函數的優(yōu)點:定義簡單,邏輯清晰。理論上,所有的遞歸函數都可以寫成循環(huán)的方式,但循環(huán)的邏輯不如遞歸清晰。
 遞歸特性:
- 1. 必須有一個明確的結束條件
 - 2. 每次進入更深一層遞歸時,問題規(guī)模相比上次遞歸都應有所減少
 - 3. 遞歸效率不高,遞歸層次過多會導致棧溢出
 
( 在計算機中,函數調用是通過棧(stack)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返 回,棧就會減一層棧幀。由于棧的大小不是無限的,所以,遞歸調用的次數過多,會導致棧溢出。)
def factorial(n): # 使用循環(huán)實現求和Sum=1for i in range(2,n+1):Sum*=ireturn Sum print(factorial(7))def recursive_factorial(n): # 使用遞歸實現求和return (2 if n==2 else n*recursive_factorial(n-1))print(recursive_factorial(7))def feibo(n): # 使用遞歸實現菲波那切數列if n==0 or n==1:return nelse:return feibo(n-1)+feibo(n-2) print(feibo(8))def feibo2(n): # 使用循環(huán)實現菲波那切數列before,after=0,1for i in range(n):before,after=after,before+afterreturn before print(feibo2(300))?
?
12. 偏函數
?
Python 的 functools 模塊提供了很多有用的功能,其中一個就是偏函數(Partial function)。要注意,這里的偏函數和數學意義上的偏函數不一樣。
在介紹函數參數的時候,我們講到,通過設定參數的默認值,可以降低函數調用的難度。而偏函數也可以做到這一點。
舉例如下:
int()函數可以把字符串轉換為整數,當僅傳入字符串時,int()函數默認按十進制轉換: >>> int('12345') 12345但int()函數還提供額外的base參數,默認值為10。如果傳入base參數,就可以做N進制的轉換: >>> int('12345', base=8) 5349 >>> int('12345', 16) 74565假設要轉換大量的二進制字符串,每次都傳入int(x, base=2)非常麻煩, 于是,我們想到,可以定義一個int2()的函數,默認把base=2傳進去: def int2(x, base=2):return int(x, base)這樣,我們轉換二進制就非常方便了: >>> int2('1000000') 64 >>> int2('1010101') 85functools.partial 就是幫助我們創(chuàng)建一個偏函數的,不需要我們自己定義int2(), 可以直接使用下面的代碼創(chuàng)建一個新的函數int2: >>> import functools >>> int2 = functools.partial(int, base=2) >>> int2('1000000') 64 >>> int2('1010101') 85所以,簡單總結 functools.partial 的作用就是:把一個函數的某些參數給固定住(也就是設置默認值),返回一個新的函數,調用這個新函數會更簡單。
小結:當函數的參數個數太多,需要簡化時,使用functools.partial可以創(chuàng)建一個新的函數,這個新函數可以固定住原函數的部分參數,從而在調用時更簡單。
?
?
?
總結
以上是生活随笔為你收集整理的简明 Python 教程学习笔记_2_函数的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: Kali linux 渗透测试技术之搭建
 - 下一篇: scrapy学习笔记(二)进阶使用