1. 程式人生 > >python 基礎:函式(二)

python 基礎:函式(二)

一. 知識倉庫

  1. 預設引數的陷阱:如果預設引數的值是一個可變資料型別,那麼每次呼叫的時候不傳值,就公用這個資料型別的資源
  2. 函式的名稱空間 1、內建名稱空間: - python直譯器一啟動就將可以使用的名字儲存在內建名稱空間中,內建的名字(如print、input、str…)在啟動直譯器的時候被載入進記憶體中 - 與python直譯器有關 2、全域性名稱空間: - 是在程式從上到下被執行的過程中依次載入進記憶體的,放置了我們設定的所有變數名和函式名 - 與我們寫的程式碼有關,但不包括函式中的程式碼 3、區域性名稱空間: - 是函式內部定義的名字,在函式被呼叫的時候才會產生這個名稱空間,函式執行結束後該空間被回收(消失) - 與函式內部的名義有關 4、注意: - 在區域性:可以使用全域性、內建名稱空間中的名字 - 在全域性:可以使用內建名稱空間中的名字,但是不能使用區域性名稱空間中的名字(函式執行結束後該空間被回收) - 在內建:不能使用區域性、全域性名稱空間中的名字(在讀程式碼和呼叫函式之前就已經啟動了) - 依賴倒置原則:小的可以依賴大的,但大的不能依賴小的。該處內建為大,區域性為小。 - 當我們在全域性定義了和內建空間中同名的名字時,會使用全域性的名字(即當自己有的時候,就不找自己的上級要,一直到內建也沒有該名字,就報錯) - 區域性和全域性名稱空間只有一個,而區域性名稱空間可能有多個(多個函式),且各個區域性名稱空間中的名字不共享,是相互隔離的
  3. 函式的執行(呼叫):func()本質上是函式的記憶體地址+(),即函式的記憶體地址(),而外在表現的func剛好指向了該函式的記憶體地址(見function_2.py)
  4. 作用域 1、全域性作用域:作用在全域性,內建和全域性名稱空間中的名字屬於全域性作用域 2、區域性作用域:作用在區域性,函式(區域性空間)中的名字屬於區域性作用域 3、注意: - 對於不可變資料型別,在區域性可以檢視全域性作用域中的變數,但不能直接去修改,修改則需要在程式的一開始新增global宣告)(見function_2.py) - 如果在一個佈局(函式)內聲明瞭一個global變數,那麼這個變數在區域性的所有操作將對全域性的變數有效,出於安全應該避免使用global
  5. 兩個方法:locals() ——> 檢視該方法所在作用域中的所有名字、globals() ——> 檢視全域性作用域中的所有名字(內建名稱空間+全域性名稱空間)
  6. 函式的巢狀和作用域鏈 1、函式的巢狀呼叫:在函式裡面呼叫函式 2、函式的巢狀定義: - 內部的函式可以使用外部函式的變數 - 內部函式在定義之後需要進行呼叫,否則會在外部函式呼叫之後消失而不產生作用 - 同樣的,對於不可變資料型別,內部函式只可以使用外部變數,但不可修改 - 如果內部函式用到global則是修改全域性變數,也不會對外部函式(區域性)定義的a產生作用(見function_2.py) - 內部函式修改外部函式的變數:使用 nonlocal 進行宣告,其作用範圍是向上找到的第一個該區域性變數(注意區域性!),到最外層區域性也找不到就報錯
  7. 函式名的本質 1、函式名本質上就是指向該函式的一段記憶體地址 2、函式名是第一類物件 - 函式名可以作為函式的返回值和引數 - 函式名可以作為容器型別的元素 - 函式名可以賦值 3、第一類物件(first-class object) - 可在執行期建立 - 可用作函式引數或返回值 - 可存入變數的實體
  8. 閉包 1、條件:巢狀函式,內部函式呼叫外部函式的變數 2、常見的閉包的使用方法:在一個函式的外部去使用這個函式內部的函式 3、使用閉包的好處:保護了局部變數,減少了區域性變數頻繁的建立和消散而造成的浪費 二. 程式碼練習
"""
預設引數的陷阱
- 如果預設引數的值是一個可變資料型別,那麼每次呼叫的時候不傳值,就公用這個資料型別的資源
"""
def func1(l1=[]):
    l1.append(1)
    print(l1)
func1()     # [1]
func1([])   # [1]
func1()     # [1, 1]
func1()     # [1, 1, 1]
# 列表是可變資料型別

def func2(k, dic1={}):
    dic1[k] = 'v'
    print(dic1)
func2(1)        # {1: 'v'}
func2(2)        # {1: 'v', 2: 'v'}
func2(3)        # {1: 'v', 2: 'v', 3: 'v'}
# 字典是可變資料型別

def func3(dic2={}):
    dic2['k'] = 'v'
    print(dic2)
func3()     # {'k': 'v'}
func3()     # {'k': 'v'}
func3()     # {'k': 'v'} 因為鍵都為'k',值進行覆蓋

"""
函式的呼叫的本質
- 函式名func4指向了該函式的記憶體地址,該記憶體地址存的是該函式的函式體
- 本質上是0x00000000011B5840(),但是太麻煩,python做了優化,用func4()就可以呼叫該函式
"""
def func4():
    print(1)
print(func4)    # <function func4 at 0x00000000011B5840>
print(id(func4))    # 18569280

"""
不可變資料型別在區域性修改全域性的變數:global宣告
"""
a = 1
def func5():
    global a
    a += 1
func5()
print(a)        # global宣告不安全,可以採用傳參和返回值的方式來實現需要global的操作,見下

a = 1
def func6(a):
    a += 1
    return a
a = func6(a)
print(a)    # 2

"""
對於不可變資料型別,在區域性可以檢視全域性作用域中的變數,但不可直接修改
"""
a = 1
def func7():
   a = 2    # 此處對全域性變數a無法修改,沒有影響
func7()
print(a)     # 1

"""
兩個方法
"""
a = 1
b = 2
def func8():
    x = 'PYTHON'
    y = 'WEB'
    print(locals())     # locals()放置的位置不同,代表的作用域也不同,該處放置在func6區域性中,所以列印func6中的名字
func8()     # {'x': 'PYTHON', 'y': 'WEB'}
print(globals())
print(locals())     # 該處放置在全域性作用域中,所以列印的是全域性作用域中的名字,此時列印結果與glocals()列印的結果一致

"""
函式的巢狀呼叫
"""
def max(a, b):
    return a if a > b else b
def the_max(x, y, z):
    c = max(x, y)        # 函式的巢狀呼叫
    return max(c, z)
print(the_max(1, 2, 3))

"""
函式的巢狀定義
"""
def outer_1():
    def inner():
        print('inner')
outer_1()     # outer被呼叫之後inner的名稱空間消失

def outer_2():
    a = 1

    def inner():
        print(a)    # 區域性中的區域性,此時inner找不到a則到外部函式outer_2()中找到a
        print('inner')
    inner()
outer_2()     # 這才是正確的函式的巢狀定義,在內部定義之後需要接著呼叫

"""
global在巢狀中的作用範圍
"""
a = 1   # 聲明瞭一個全域性變數a
def func_9():
    a = 1   # 聲明瞭一個區域性變數a

    def func_small():
        global a    # 宣告a為全域性變數,此處需要修改func_9()區域性的a則用nonlocal進行宣告即可
        a += 1      # 此時對a的操作是對func_9()外部的全域性變數a做操作,而不是func_9()區域性的a
    func_small()

    print('外部函式a:', a)      # 外部函式a: 1
func_9()
print('全域性a:', a)        # 全域性a: 2

"""
對函式名本質的理解
"""
def func_10():
    print(1)
func_10()       # 函式名本質就是記憶體地址
func_my = func_10   # 函式名可以賦值,表示func_my和func_10都指向了這個函式
func_my()
func_10()
l1 = [func_10, func_my]     # 函式名可以作為容器型別(可變資料型別)的元素
print(l1)
for i in l1:
    i()     # 迴圈,地址+()就可以呼叫函式
def my_func(f):
    f()     # 將func_10這個變數(地址)作為引數傳給my_func(),地址+()呼叫了func_10函式
    return f        # 函式名可以作為返回值
my_func(func_10)    # 函式名可以作為函式的引數
my_return = my_func(func_10)    # 函式名可以作為返回值
print(my_return)
my_return()

"""
閉包
"""
def my_outer():
    a = 1

    def my_inner():
        print(a)
    return my_inner()       # 常見的閉包的使用方法是作為返回值賦值給外面的變數在函式外部使用該函式,而不是以前講的再次呼叫my_inner()
    print(my_inner.__closure__)     # (<cell at 0x000000000119C138: int object at 0x00000000607B22D0>,)
my_outer()
# 函式.__closure__可以用來檢視一個函式是不是閉包,是的話返回一個cell at...,不是閉包則返回None

"""
閉包常見的用法
"""
def func_11():
    b = 520     # 閉包的使用使得該區域性變數一直存在與記憶體中

    def my_func_11():
        print(b)
    return my_func_11   # 將my_func_11函式的地址作為返回值返回,在函式外部定義一個變數進行接收,加上()即可呼叫my_func_11()
func_12 = func_11()     # 此時func_12作為全域性變數在程式全部結束前一直存在從而保護了上面的區域性變數b
func_12()   # 在一個函式的外部去使用這個函式內部的函式