1. 程式人生 > 其它 >Python學習-名稱空間、高階函式

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