python函數對象-命名空間-作用域-02

  • 2019 年 10 月 7 日
  • 筆記

函數對象

函數是第一對象: # 函數名指向的值可以被當做參數傳遞

函數對象的特性(*****靈活運用,後面講裝飾器會用到)

函數名可以像變數一樣被傳遞

# 變數可以被傳遞  name = 'jason'  x = name  print(x)  # jason  print(id(name), id(x))  # 3085464224688 3085464224688    # 嘗試函數像變數一樣被傳遞  def func():      print('from func')  print(func)  # <function func at 0x0000016E5E062E18>    f = func  # 其實指向的也是函數func 指向的函數體程式碼的記憶體地址  print(f)  # <function func at 0x000001B4D0D92E18>  f()  # from func  print(id(func), id(f))  # 3085463137816 3085463137816

函數名可以被當做參數傳遞給其他函數

def func():      print("from func")    def index(args):      print(args)      args()  # 函數主要一定義(先定義)就可以在任意地方調用      print("from index")    index(func)  # <function func at 0x000001B7429A2E18>  # from func  # from index

函數名可以被當做函數的返回值

def index():      print('index')    def func():      print('func')      return index    res = func()  # 將返回的函數名index 賦值給變數res  # func  print(res)  # <function index at 0x000001EF64362E18>  res()  # index

  函數名可以被當做容器類型的參數

def func():      print('func')    l = [1, 2, func, func()]  # 定義的時候默認執行了func(),所以下面先列印了 func  # func  # func函數沒有返回值,所以默認是None  print(l)  # [1, 2, <function func at 0x0000013931C92E18>, None]

函數對象小練習

題目: # 循環列印項目功能提示資訊 供用戶選擇 用戶選擇誰就執行誰

def register():      print("註冊了")      pass      def login():      print("登錄了")      pass      def shopping():      print("購物了")      pass      def output_func_list():      print("----- 請選擇功能!")      for key in func_list:          print(f"----  {key}.{func_list[key][1]}")      func_list = {      0: [register, '註冊'],      1: [login, '登錄'],      2: [shopping, '購物'],  }      while True:      output_func_list()      chose_func = input("請輸入功能編號(q 退出系統):").strip()      if chose_func.isdigit():          # 執行相應功能          chose_func = int(chose_func)          # 判斷輸入的編號在不在功能列表裡          if chose_func in func_list:              func_list[chose_func][0]()  # 取到功能函數名,加括弧調用          else:              print("您輸入的功能編號不存在,請重新輸入!")      elif chose_func.lower() in ['q', 'quit']:          print("感謝您的使用,祝您生活愉快~")          break      else:          print("請正確輸入數字!")

知識點: # 函數名可以作為容器對象的元素值 , # 函數名(即函數記憶體地址)可以加括弧直接調用

  上述其他三個特性在裝飾器中會有靈活運用,就暫不舉例了

函數的嵌套調用與定義

嵌套調用

函數的嵌套調用: # 在函數內部調用其他函數

def index():      print('index')    def func():      index()  # 在定義 func 函數的時候不會直接調用 index 的方法 --> 函數定義的時候不執行程式碼      print('func')    func()  # index  # 通過 func()函數內部調用了index() 函數,列印出了 index  # func

函數的嵌套調用可以 # 將複雜的邏輯簡單化

小練習: # 寫一個函數可以求四個數中的最大值

def my_max(x, y):      if x > y:          return x      return y    def my_max4(a, b, c, d):      res = my_max(a, b)      res = my_max(res, c)      res = my_max(res, d)      return res    print(my_max4(1, 5, 7, 1))  # 7

嵌套定義

def outer():      x = 1      print("outer")      def inner():          print("inner")      inner()    # inner()  # 會報錯,在外部無法訪問內部內容  outer()  # outer  # inner

實現在外部調用 outer函數的內部函數 inner

# 想在外部調用inner 可通過把內部的函數名當做外部函數的返回值來返回給外部  def outer():      x = 1      print("outer")      def inner():          print("inner")      return inner  # 把 inner 函數當做函數的返回值返回給 outer函數的調用者    res = outer()  # outer  res()  # 變相調用inner  # inner

小案例: # 寫一個函數,該函用戶可以通過傳參的不同 控制函數指向不同的功能

def all_func(type):      def register():          print('register')      def login():          print('login')      def shopping():          print('shopping')        if type == 1:          register()      if type == 2:          login()      if type == 3:          shopping()    all_func(1)  all_func(2)  all_func(3)  # register  # login  # shopping

名稱空間(****繞且重要)

 名稱空間: # 存放的是變數名與變數值的記憶體地址綁定關係的地方 ,後文可能稱之為命名空間。

 訪問變數的值: # 要想訪問一個變數的值,必須先去名稱空間拿到對應的名字,才能訪問變數的值

命名空間的分類

 命名空間分為: # 內置名稱空間、全局名稱空間、局部名稱空間 三大類

 內置命名空間

內置名稱空間: # python 解釋器提前已經定義好了的名字(已經存放到了內置名稱空間中了)

print("hello world")  max(1, 44, 62, 15)  len('26515f1asfafqw')  sum([1, 2, 3, 4, 5])  # 像上面的print max len sum 並沒有定義就可以值使用,它們就是python解釋器提前定義好了的函數,屬於內置命名空間的

 全局命名空間

 全局命名空間: # 文件級別的程式碼

x = 1  if x == 1:      y = 2  print(y)  # 2    for i in [1, 2]:      print(i)  print(i)  # 1  # 2  # 2    # 上面的 x y z 都在全局名稱空間,不要以為縮進的就是局部的(if、 for、 while 無論嵌套,多少層,他們內部所創建的名字都是全局名稱空間的)

 局部命名空間

 局部命名空間: # (目前所學)函數體內創建的名字都屬於局部名稱空間(最外層的函數名是屬於全局名稱空間的)

def func():      username = 'jason'  # print(username)  # 會報錯 NameError: name 'username' is not defined  func()

  至於為什麼上面的 print(username) 為什麼會報錯,學完下面的知識你就知道啦。

命名空間的生命周期

'''  名稱空間的生命周期      內置名稱空間:(最長)只要 python解釋器啟動,立馬創建  關閉 python解釋器時自動銷毀      全局名稱空間: 只要右鍵運行 py文件就會自動創建  py文件程式運行結束自動銷毀      局部名稱空間:(動態創建動態銷毀)函數被調用的時候自動創建  函數執行結束後立即銷毀  '''

補充:與垃圾回收機制的關係

# 名稱空間生命周期結束 -- >  裡面存的變數與指向值的記憶體地址解綁,記憶體中的值等待垃圾回收機制回收  # def 刪除變數 -- >  裡面存的變數與指向值的記憶體地址解綁,記憶體中的值等待垃圾回收機制回收  ---> 等同於名稱空間里刪除了一個變數(綁定關係)  # 垃圾回收機制:垃圾回收機制隔一段時間就會檢查一次,記憶體中的值如果沒有變數指向它(引用),那垃圾回收機制就會把它清除掉(釋放記憶體)  #             如果多次檢查都有變數等指向它,那就會把它等級提升,檢查頻率就會變低

 命名空間的查找順序

 驗證思路: # 找一個三個地方都有的東西來驗證(比如 len、max等,暫時忽略命名規範不能與關鍵字重複) , # 分別注釋來測試其查找順序(全局、局部)

 驗證過程

len = '我是全局名稱空間的len'  def func():      len = '我是局部名稱空間的len'      print(len)  print(len)  # 這裡是全局的位置  # 我是全局名稱空間的len  '''  # 把全局的len 注釋掉,就去找了內置的len  print(len)  # 是全局的位置  # <built-in function len>  '''  func()  # 我是局部名稱空間的len

 大致結論:

'''  (******)名稱空間的查找順序      1.需要先確定當前的在哪(全局、局部),大前提          1.1 站在全局:全局 >>> 內置          1.2 站在局部:局部 >>> 全局 >>> 內置            1.2.2 站在局部的內部(多個局部嵌套):局部 >>> 上一級局部 >>> 上一級局部 >>> .... >>> 全局 >>> 內置                會在作用域同級的前後(這句程式碼前後的同級語句)去找,然後再上一級      2.函數在定義階段查找名字的順序(範圍)就已經固定了, 不會因為函數的調用位置變化而變化(*******)            可以在函數定義的時候寫個注釋,指出他查找的位置,防止邏輯複雜了搞不清楚  '''

加深理解的小案例

# 通過注釋不同函數層內的x 來加深理解命名空間查找順序(可以採用收起(摺疊)程式碼塊的技巧來快速指定)  x = 111  def f1():      x = 222      def f2():          x = 333          def f3():              # x = 444              def f4():                  # x = 555                  print(x)  # 這個案例在本局部找到了 變數x, 所以用的是內部的這個 777,在調用前定義了,所以不會報錯              x = 777  # 純粹為了教學演示              f4()              x = 777  # 純粹為了教學演示          f3()      f2()  f1()  # 777
def func():      x = 1      def index():          print(x)  # 查找順序:本作用域找x,沒找到,上一級func里找,找到了,那就引用的是func 作用域里的 局部變數x      return index    res = func()  x = 999  res()  # 1
x = 111  def outer():      def inner():          print('from inner', x)  # 查找順序:函數體inner 內上下沒有x,再找 outer裡面,也沒有x, 那就找全局,找到了x,所以這裡的x 就是全局的x      return inner  f = outer()  x = 222  # 調用前改變了全局 x 的值,所以最後結果是 222  f()  # from inner 222
x = 111  def outer():      def inner():          print('from inner', x)  # 查找順序:函數體inner 內上下沒有x,再找 outer裡面,也沒有x, 那就找全局,找到了x,所以這裡的x 就是全局的x      return inner  f = outer()  def func():      x = 333  # 沒有global 關鍵字指定x 為全局變數,這裡是重修申請了一個局部變數 x,這裡並不會影響 全局的那個x      f()  func()  # from inner 111
# 下面這個案例會直接報錯  x = 111  def outer():      def inner():          print('from inner', x)  # 會直接報錯,UnboundLocalError: local variable 'x' referenced before assignment ---> 本地變數(局部變數)x 在定以前被引用了          # 報錯原因: ---> 查找順序:函數體 inner內部有 x, 所以這個print 內x 指定的是x = 66666666的那個局部變數,而調用print 時,他還沒定義出來          x = 66666666      return inner  f=outer()  f()

案例一原理圖

作用域

python中的作用域有 全局作用域 與 局部作用域 , 全局作用域: # 全局有效: 內置名稱空間、全局名稱空間 都屬於全局作用域 , 局部作用域: # 局部有效:局部名稱空間

局部修改全局變數(修改和訪問是兩回事)

# 嘗試修改不可變類型的全局變數  x = 1  def func():      x = 2  # 實質是又創建了一個局部變數 x  func()  print(x)  # 局部無法修改不可變類型的全局變數  # 1    # 嘗試修改可變類型的局部變數  x = []  def func():      x.append('嘿嘿嘿')  func()  print(x)  # 修改成功,局部可以修改可變類型的全局變數  # ['嘿嘿嘿']    # 全局訪問不了局部的變數,所以不展開研究

    小結論: # 局部無法修改不可變類型的全局變數 , # 局部可以修改可變類型的全局變數 (前提:在不使用 global  和  nonlocal  關鍵字的情況下)

  通過  global  關鍵字在局部修改全局,修改多個用 , 隔開

x = 1  # 不可變類型  username = 'jason'  def func():      global x,username      x = 999  # 修改全局變數,而不是創建局部變數      username = 'egon'  func()  print(x, username)  # 999 egon

  通過  nonlocal  關鍵字在局部修改局部,修改多個用 , 隔開

def func():      x = 1      def index():          x = 2      index()      print(x)  func()  # 1    # 想就在 index 里把 x 改了  def func():      x = 1      def index():          nonlocal x          x = 2      index()      print(x)  func()  # 2