Python學習-名稱空間、高階函式
記錄下python中函式中名稱空間、高階函式、內建函式的內容,這一節主要是概念的東西。
名稱空間
名稱空間,或名稱空間,可以參考Java中全域性變數和區域性變數,python中為全域性名稱空間和區域性名稱空間。 名稱空間相當於不同的房間裡面放了不同的東西(變數),有些公用有些私用。
名稱空間
py程式執行時,直譯器會在記憶體中開闢一個空間,用於儲存變數和變數值之間的對應關係,如果是函式,只是將函式名和函式的內容儲存到這個名稱空間,對裡面的變數和邏輯並不關心。一開始函式只是載入進記憶體,只有當函式被呼叫和訪問的時候,直譯器才會根據函式內部宣告的變數來進行內部空間的開闢,隨著函式執行完畢,這些內部變數佔用的空間也會隨著函式執行完畢而被清空。
全域性&區域性&內建名稱空間
全域性名稱空間:在py檔案中,函式體外宣告的變數都屬於全域性名稱空間。
區域性名稱空間:當函式執行時,函式體裡面會定義一些變數,這個時候就會開闢區域性或者臨時名稱空間,將這些變數和值的對應關係儲存,當函式體執行完畢,這些臨時名稱空間下的變數和值的關係也就消失。
內建名稱空間:python直譯器提供的一些內建的函式或名字,如list、str、int、print,input等,儲存在這個空間,它們可以拿來直接使用,會在python直譯器啟動的時候載入進記憶體。
載入&取值順序
載入順序(載入到記憶體的順序)內建名稱空間->全域性名稱空間->區域性名稱空間(函式執行時才開闢)。
取值順序(就近原則,也叫LEGB原則(local enclosing global builtin))區域性名稱空間→全域性名稱空間→內建名稱空間 ,為單向不可逆。
# 取值順序 name='clyang' def print_name(): # 就近原則,print列印的是區域性名稱空間的name,區域性如果沒有就從全域性找,全域性沒有就去內建找 # 就近原則 name='messi' print(name) # messi print_name() # 取值順序 內建名稱空間最後載入 def print_name_2(): # 註釋掉後,就先去全域性找,全域性沒有,就去內建找 # input='clyang' print(input) print_name_2() # <built-in function input> # 取值順序 單向不可逆 def print_name_3(): name='messi' # 這裡是先從全域性找name,不會從區域性找,起點確定後不會逆向回到區域性再從新找,叫做單向不可逆 print_name_3() print(name) # clyang
作用域
作用域就是名稱空間作用範圍,按照生效範圍分為全域性作用域和區域性作用域。
全域性作用域:包含內建名稱空間和全域性名稱空間,在整個檔案的任意位置都可以使用。
區域性作用域:包含區域性名稱空間,在函式內部可以使用。
區域性作用域可以引用全域性作用域的變數,但是不能修改,全域性作用域不能引用區域性作用域的變數。
date='週六'
def func():
year=2012
print(date)
func() # 週六
# 全域性作用域不能引用區域性作用域的變數year
print(year) # 報錯 NameError: name 'year' is not defined
再看下面例子,雖然區域性作用域對date重新賦值,但是這個是在區域性新建立了變數並賦值,並不是修改全域性作用域的date,因此執行func()函式後列印的是區域性的date,後面print(date)列印的是全域性的date。
date='週六'
def func():
# 注意,這不是改變,這是在區域性新建立了變數並賦值
date='週日'
print(date)
func()
# 這裡列印結果還是週六,說明全域性作用域的變數沒有改變
print(date)
列印結果。
週日
週六
再看下面例子,區域性作用域不能修改全域性作用域中的變數,當python直譯器發現你準備對區域性作用域中的某個變數count進行修改時,它會預設你已經在區域性作用域定義了這個區域性變數,它就會去區域性作用域去找這個區域性變數,執行時發現並沒有區域性變數count,只有全域性有變數count,就報錯‘local variable 'count' referenced before assignment’。
count=1
def revise():
# 報錯 UnboundLocalError: local variable 'count' referenced before assignment
# 提示count這個區域性變數在定義前就引用,是不允許的
count+=1
print(count)
revise()
以下也是區域性作用域修改全域性作用域變數的例子,當inner函式直接列印count,是列印的名稱空間1的count,但是當需要修改時,這個count就是名稱空間2下的區域性變數,也是需要先定義。
def func():
count=1 # 區域性名稱空間1
def inner():
# count+=1 # 區域性名稱空間2
print(count)
inner()
func()
可以通過globals()和locals()函式分別檢視全域性及區域性作用域內容,主要key-value的形式展示。
a=1
b=2
def func():
name='messi'
score=55
assist=50
print(globals())
print(locals())
func()
執行結果可以看出,a、b和func函式,都是全域性作用域內容,name、score、assist都是區域性作用域的內容。
{'__name__': '__main__', '__doc__': '\n內建函式\n', '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x10fb2f470>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Users/yangchaolin/pythonCode/python22期/day10/04 內建函式.py', '__cached__': None, 'a': 1, 'b': 2, 'func': <function func at 0x10fae2268>}
{'name': 'messi', 'score': 55, 'assist': 50}
global nonlocal
當需要在區域性名稱空間修改全域性名稱空間的變數,可以使用global,下面例子count就在區域性名稱空間修改成功,從0變成1。
count=0
def func():
global count
count+=1
print(count)
func()
print(count)
執行結果
0
1
當在區域性宣告一個全域性變數,也可以使用global,下面例子如果print(name)在func()前執行,會報錯,因為還沒有全域性變數,但是執行func()後,會建立全域性變數name,再次執行print(name)可以列印結果。另外通過使用 globals(),也可以看到name為全域性變數。
def func():
global name
name='messi'
print(name)
# 1 print放在func前會報錯
# print(name)
func()
# 2 print放在func後不會報錯
print(name)
# 驗證是否是全域性,下面的方法可以打印出當前作用域的所有全域性變數
print(globals())
執行結果
# 執行func結果
messi
# 列印name,區域性作用域name變成全域性
messi
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x1072e3470>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Users/yangchaolin/pythonCode/python22期/day11/02 補充的知識點(global,nonlocal).py', '__cached__': None, 'func': <function func at 0x107296268>, 'ret': ['alex', 'messi'], 'ret_2': [20], 'ret_1': [10, 100], 'ret_3': [10, 100], 'count': 0, 'name': 'messi'}
當需要在內層巢狀函式修改外層函式的變數,可以使用nonlocal,其為python3.4後更新,注意nonlocal不能操作全域性變數。
def outer():
count=0
def inner():
# 這裡依然不能直接修改,內層函式不能修改外層函式的區域性變數,使用nonlocal可以解決
nonlocal count
count+=1
print('inner函式執行前:%d'%(count,))
inner()
print('inner函式執行後:%d'%(count,))
outer()
執行結果
inner函式執行前:0
inner函式執行後:1
高階函式
參考文末博文,當一個函式a作為引數傳給另外一個函式b,或者一個函式b的返回值為另外一個函式a(若返回值為該函式本身,則為遞迴),只要滿足其中一個條件,b就是是高階函式。即當一個函式引數中有函式,或函式的返回值還是函式,則這個函式為高階函式,如map、filter和reduce都是高階函式。
以下也是高階函式,函式裡面引用函式,函式裡面定義函式。
# 函式引用函式
def func1():
print('i am func1')
print(3)
def func2():
print('i am func2')
func1()
print(4)
print(1)
func2()
print(2)
# 函式裡面定義函式
def func2():
print(2)
def func3():
print(6)
print(4)
func3()
print(8)
print(3)
func2()
print(5)
函式引用函式執行結果
1
i am func2
i am func1
3
4
2
函式裡面定義函式執行結果
3
2
4
6
8
5
內建函式
python內建函式,可以參考官方文件https://docs.python.org/3.7/library/functions.html?highlight=built#ascii。
相關練習
(1)看程式碼寫結果
def func(*args,**kwargs):
print('args:',args)
print('kwargs:',kwargs)
# 請執行函式,並實現讓args的值為(1,2,3,4)
func(1,2,3,4)
# 請執行函式,並實現讓args的值為([1,2,3,4],[11,22,33])
func([1,2,3,4],[11,22,33])
# 請執行函式,並實現args的值為([11,22],33),並且kwargs的值為{'k1':'v1','k2':'v2'}
func([11,22],33,k1='v1',k2='v2')
# 如果執行func(*{'messi','ronald','herry'}),請問args和kwargs的值分別是多少?
func(*{'messi','ronald','herry'})
# 如果執行func({'messi','ronald','herry'},[11,22,33]),請問args和kwargs的值分別是多少?
s1={'messi','ronald','herry'}
# s1是set集合
print(s1,type(s1))
func({'messi','ronald','herry'},[11,22,33])
# 如果執行func({'messi','ronald','herry'},[11,22,33],**{'k1':'v1'}),請問args和kwargs的值分別是多少?
func('messi','ronald','herry',[11,22,33],**{'k1':'v1'})
執行結果
args: (1, 2, 3, 4)
kwargs: {}
args: ([1, 2, 3, 4], [11, 22, 33])
kwargs: {}
args: ([11, 22], 33)
kwargs: {'k1': 'v1', 'k2': 'v2'}
args: ('messi', 'herry', 'ronald')
kwargs: {}
{'messi', 'herry', 'ronald'} <class 'set'>
args: ({'messi', 'herry', 'ronald'}, [11, 22, 33])
kwargs: {}
args: ('messi', 'ronald', 'herry', [11, 22, 33])
kwargs: {'k1': 'v1'}
['messi', 'ronald', 'herry']
(2)位置引數一定要在關鍵字引數的前面,並且引數不能多重賦值,參考程式碼。
# 注意位置引數一定要在關鍵字引數的前面,並且引數不能多重賦值,否則報錯
def func(name,age=18,email='[email protected]'):
print(name)
print(age)
print(email)
# 位置引數一定要在關鍵字引數的前面,下面的語句編譯都不會通過
# func(age=20,'messi')
# 報錯func() got multiple values for argument 'name' 引數多重賦值了
# func('messi','[email protected]',name='clyang')
(3)看程式碼寫結果
def func(users,name):
users.append(name)
return users
result=func(['messi','ronald'],'herry')
print(result)
執行結果
['messi', 'ronald', 'herry']
(4)高階函式
v1='alex'
def func():
v1='女神'
def inner():
print(v1)
v1='男神'
inner()
# v1='男神'
func()
print(v1)
v1='老男人'
func()
print(v1)
執行結果
男神
alex
男神
老男人
(5)如果函式預設引數,指向的是可變的資料型別,無論呼叫多少次,這個預設引數在記憶體中都是同一個。
def func(name, li=[]):
li.append(name)
return li
ret = func('alex')
print(ret)
ret_2 = func('messi')
print(ret_2)
執行結果
['alex']
['alex', 'messi']
再看例子,li如果是預設引數,多次呼叫不傳入[],使用的是同一個列表。
def func(a, li=[]):
li.append(a)
return li
# 引數傳了[],就使用新的,否則使用以前的
# print(func(10,)) # [10]
# print(func(20,[])) # [20] # 引數傳了就用新的列表
# print(func(100,)) #[10,100] 引數沒傳就用以前的列表
ret_1 = func(10, )
ret_2 = func(20, [])
ret_3 = func(100, )
# 如果先執行完,再一一列印,就是這個結果,坑太多,有啥用呢?難道我還寫程式碼會經常想這個嗎
print(ret_1) # [10,100]
print(ret_2) # [20]
print(ret_3) # [10,100]
(6)區域性作用域的坑,下面程式碼如果count=1不註釋,print(count)時會先從區域性找,但是區域性還未定義就會報錯,把count=1註釋掉,就會從全域性作用域找count,不會給直譯器造成困擾。
count = 0
def func():
print(count) # 報錯 local variable 'count' referenced before assignment,程式走到這裡就會從區域性找
# 把這個註釋掉,上面的錯就不會報了,不會給直譯器造成困擾,如果定義瞭解釋器就蒙圈了,不知道你到底要使用區域性的還是全域性的
# count=1
func()
PS:以上,理解不一定正確,學習就是一個不斷認識和糾錯的過程,如果有誤還請批評指正。
參考博文:
(1)https://www.cnblogs.com/luckinlee/p/11620074.html 名稱空間
(2)https://www.cnblogs.com/littlefivebolg/articles/9094942.html 高階函式
(3)https://zhuanlan.zhihu.com/p/93225449 常見的高階函式
(4)https://zhuanlan.zhihu.com/p/108021527?from_voters_page=true