Python函數的返回值和作用域


函數的返回值和作用域

1、返回值

def guess(x):
    if x > 3:
        return "> 3"
    else:
        return "<= 3"
print(guess(10))

  1> Python 函數使用 return 語句返回 "返回值”
  2> 所有函數都有返回值,如果沒有 return 語句,隱式調用 return None
  3> return 語句並不一定是函數的語句塊的最后一條語句
  4> 一個函數可以存在多個 return 語句,但是只有一條可以被執行。如果沒有一條 return 語句被執行到,隱式調用 return None
  5> 如果有必要,可以顯示調用return None,可以簡寫為return
  6> 如果函數執行了 return 語句,函數就會返回,當前被執行的 return 語句之后的其它語句就不會被執行了
  7> 返回值的作用:結束函數調用、返回 "返回值”

2、能一次返回多個值嘛?

def showvalues():
    return 1, 3, 5
print(showvalues())    # 返回 (1, 3, 5)

  函數不能同時返回多個值
  return 1, 3, 5 看似返回多個值,隱式的被 python 封裝成了一個元組
  x, y, z = showvalues() 使用解構提取返回值更為方便

3、函數作用域**

3.1 作用域

  一個標識符的可見范圍,這就是標識符的作用域,一般常說變量的作用域。

x = 20
def fn():
    x = 100  # x 的作用域:當前函數
fn()
print(x)     # x = 20

  注意:每一個函數都會開辟一個作用域。

3.2 作用域分類

  全局作用域:
    在整個程序運行環境中都可見
    全局作用域中的變量稱為全局變量

  局部作用域:
    在函數、類等內部可見
    局部作用域中的變量稱為局部變量,其使用范圍不超過其所在局部作用域

# 局部變量
def fn1():
    x = 1    # 局部作用域,x 為局部變量,使用范圍在 fn1 內

def fn2():
    print(x) # x 能打印嗎?不能

print(x)     # x 能打印嗎?不能
# 全局變量
x = 5        # 全局變量,也在函數外定義
def foo():
    print(x) # 可見嗎?可以
foo()

  一般來講外部作用域變量在函數內部可見,可以使用
  反過來,函數內部的局部變量,不能在函數外部看到

4、函數嵌套

  在一個函數中定義另一個函數

def outer():
    def inner():
        print('inner')
    print('outer')
    inner()
outer()
inner()    # 不可以

  內部函數 inner 不能在外部直接使用,會拋出 NameError 異常,因為它在函數外部不可見。
  其實,inner 不過就是一個標識符,就是一個函數 outer 內部定義的變量而已。

5、嵌套函數的作用域

def outer():
    o = 65    # 局部變量、本地 local 變量、臨時變量
    def inner():
        o = 97
        print('inner', o)
    print('outer 1 ', o)
    inner()
    print('outer 2 ', o)
outer()       # 1:outer 1  65    2:inner 97    3:outer 2  65

  外層變量在內部作用域可見。
  內層作用域中,如果定義了和外層相同的變量,相當於在當前函數作用域中重新定義了一個新的變量,這個內層變量並不能覆蓋掉外部作用域中的變量。

6、一個賦值語句的問題

x = 100
def fn():
    y = x + 200
    print(y)
fn()
x = 100
def fn():
    x += 1    # 報錯! 賦值即定義,即 x = x + 1 (局部變量 = 局部變量 + 1)!
    print(x)
fn()
x = 100
def fn():
    print(x)  # 報錯!該步執行不了!
    x += 1    # 只要在該作用域內賦值定義('=')局部變量,在該作用域內的所有該變量都為局部變量!
    print(x)
fn()

  能否解決呢?可以,使用 global 語句

x = 100
def fn():
    global x  # 聲明全局變量
    print(x)  # 100
    x += 1
    print(x)  # 101
fn()
print(x)      # 101

  注意:全局變量一般情況不推薦修改,一旦在作用域中使用 global 聲明全局變量,那么相當於在對全局變量賦值、定義。

  global 使用原則:

    1> 外部作用域變量會在內部作用域可見,但也不要在這個內部的局部作用域中直接使用,因為函數的目的就是為了封裝,盡量與外界隔離。
    2> 如果函數需要使用外部全局變量,請盡量使用函數的形參定義,並在調用傳實參解決。
    3> 一句話:不用 global,學習它就是為了深入理解變量作用域。

# 不建議直接傳入全局變量!
y = []

def foo():    # x 就是標識符,就是變量,就是本地變量
    y.append(1)

foo()
foo()
print(y)
# 建議使用傳參的方式,在函數內使用全局變量
y = []

def foo(x):    # x 就是標識符,就是變量,就是本地變量
    x.append(1)

foo(y)
foo(y)
print(y)

 

7、閉包**

  自由變量:未在本地作用域中定義的變量。例如定義在內層函數外的外層函數的作用域中的變量
  閉包:就是一個概念,出現在嵌套函數中,指的是內層函數引用到了外層函數的自由變量,就形成了閉包。很多語言都有這個概念,最熟悉就是 JavaScript。

# python 2 實現閉包
def counter():
    c = [0]
    def inc():
        c[0] += 1  # 是賦值即定義嘛?不是!是修改值
        return c[0]
    return inc     # 返回標識符,即函數對象

m = counter()
m()                # 調用函數 inc(),但是 c 消亡了嘛?沒有,內層函數沒有消亡,c 不消亡(閉包)
m()
m()
print(m())
# 不推薦使用 global !
def counter():
    global c
    c = 0
    def inc():
        global c
        c += 1        # 不是閉包!
        return c
    return inc

m = counter()
m()
m()
m()
print(m())
# 推薦使用 nonlocal,python 3 實現閉包
def counter():
    c = 0
    def inc():
        nonlocal c    # 非當前函數的本地變量,當前函數之外的任意層函數的變量,絕非 global
        c += 1        # 是閉包嗎?是!
        return c
    return inc

m = counter()
m()
m()
m()
print(m())

  nonlocal 語句:將變量標記為不在本地作用域定義,而是在上級的某一級局部作用域中定義,但不能是全局作用域中

8、默認值的作用域

def foo(x=1):
    x += 1
    print(x)
foo()    # 2
foo()    # 2

def bar(x=[]):    # x = [],引用類型
    x.append(1)   # [1]
    print(x)
bar()    # [1]
bar()    # [1, 1]

  為什么上列 bar 函數第二次調用打印的是 [1, 1]?
    因為函數也是對象,每個函數定義被執行后,就生成了一個函數對象和函數名這個標識符關聯。
    python 把函數的默認值放在了函數對象的屬性中,這個屬性就伴隨着這個函數對象的整個生命周期。

# 查看 foo.__defaults__ 屬性,它是個元組
def bar(x=[]):
    x.append(1)
    print(x)
print(bar.__defaults__)
bar()    # [1]
print(bar.__defaults__)
bar()    # [1, 1]
print(bar.__defaults__)

# 執行結果:
([],)
[1]
([1],)
[1, 1]
([1, 1],)    # 元組不變,記錄的是地址,引用類型變化
def foo(x, m=123, n='abc'):
    m=456
    n='def'
    print(x)
print(foo.__defaults__)    # (123, 'abc')
foo('yang')
print(foo.__defaults__)    # (123, 'abc')
def foo(x, m=123, *, n='abc', t=[1,2]):
    m=456
    n='def'
    t.append(12)
    #t[:].append(12)    # t[:],全新復制一個列表,避免引用計數
    print(x, m, n, t)

print(foo.__defaults__, foo.__kwdefaults__) #(123,) {'n': 'abc', 't': [1, 2]}
foo('yang')
print(foo.__defaults__, foo.__kwdefaults__) #(123,) {'n': 'abc', 't': [1, 2, 12]}
def x(a=[]):
    a = a + [5]         # 加法的本質:返回新列表、新地址;賦值即定義
print(x.__defaults__)   # ([],)
x()
x()
print(x.__defaults__)   # ([],)

def y(a=[]):
    a += [5]            # += 即 extend => a.extend([5])
print(y.__defaults__)   # ([],)
y()
y()
print(y.__defaults__)   # ([5, 5],)


# 列表的 + 和 += 的區別:
# + 表示兩個列表合並並返回一個全新的列表。
# += 表示,就地修改前一個列表,在其后追加后一個列表,就是 extend 方法。
# 例:
l1 = [1, 2]
l2 = [3, 4]
l3 = l1 + l2
print(id(l1), id(l2), id(l3))
l3 += l2    # 就地修改
print(id(l3))

執行結果:
2279905055304 2279905055816 2279906334216
2279906334216

 

9、變量名解析原則 LEGB**

  Local,本地作用域、局部作用域的 local 命名空間,函數調用時創建,調用結束消亡。
  Enclosing,Python2.2 時引入了 嵌套函數,實現了閉包,這個就是嵌套函數的外部函數的命名空間
  Global,全局作用域,即一個模塊的命名空間。模塊被 import 時創建, 解釋器退出時消亡。
  Build-in,內置模塊的命名空間,生命周期從 python 解釋器啟動時創建到解釋器退出時消亡。例如 print(open),print 和 open 都是內置的變量。

  所以一個名詞的查找順序就是 LEGB

  

 

10、函數的銷毀

  定義一個函數就是生成一個函數對象,函數名指向的就是函數對象。
  可以使用del語句刪除函數,使其引用計數減 1。
  可以使用同名標識符覆蓋原有定義,本質上也是使其引用計數減 1。
  Python程序結束時,所有對象銷毀。
  函數也是對象,也不例外,是否銷毀,還是看引用計數是否減為 0。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM