python 基礎篇 自定義函數


多態

我們可以看到,Python 不用考慮輸入的數據類型,而是將其交給具體的代碼去判斷執行,同樣的一個函數(比如這邊的相加函數 my_sum()),可以同時應用在整型、列表、字符串等等的操作中。

在編程語言中,我們把這種行為稱為多態。這也是 Python 和其他語言,比如 Java、C 等很大的一個不同點。當然,Python 這種方便的特性,在實際使用中也會帶來諸多問題。因此,必要時請你在開頭加上數據的類型檢查。

def my_sum(a, b):
    if type(a) == type(b):
        if isinstance(a, (int, str, list)):
            return a + b
        else:
            raise Exception("input is not int/str/list")
    else:
        raise Exception("input type is not same")

print(my_sum(3, 5))
# 輸出
# 8

print(my_sum([1, 2], [3, 4]))
# 輸出
# [1, 2, 3, 4]

print(my_sum('hello ', 'world'))
# 輸出
# hello world

print(my_sum([1, 2], 'hello'))
# 輸出
# input type is not same

函數嵌套

Python 函數的另一大特性,是 Python 支持函數的嵌套。所謂的函數嵌套,就是指函數里面又有函數,比如:

def f1():
    print('hello')
    def f2():
        print('world')
    f2()
f1()

# 輸出
hello
world

嵌套帶來的好處

  1. 函數的嵌套能夠保證內部函數的隱私。

    內部函數只能被外部函數所調用和訪問,不會暴露在全局作用域,因此,如果你的函數內部有一些隱私數據(比如數據庫的用戶、密碼等),不想暴露在外,那你就可以使用函數的的嵌套,將其封裝在內部函數中,只通過外部函數來訪問。比如:

    def connect_DB():
        def get_DB_configuration():
            ...
            return host, username, password
        conn = connector.connect(get_DB_configuration())
        return conn
    

    這里的函數 get_DB_configuration 是內部函數,它無法在 connect_DB() 函數以外被單獨調用。也就是說,下面這樣的外部直接調用是錯誤的:

    get_DB_configuration()
    
    # 輸出
    NameError: name 'get_DB_configuration' is not defined
    
  2. 合理的使用函數嵌套,能夠提高程序的運行效率。

    看下面這個例子:

    def factorial(input):
        # validation check
        if not isinstance(input, int):
            raise Exception('input must be an integer.')
        if input < 0:
            raise Exception('input must be greater or equal to 0' )
    
        def inner_factorial(input):
            if input <= 1:
                return 1
            return input * inner_factorial(input-1)
        return inner_factorial(input)
    
    print(factorial(5))
    

    這里,我們使用遞歸的方式計算一個數的階乘。因為在計算之前,需要檢查輸入是否合法,所以寫成了函數嵌套的形式,這樣一來,輸入是否合法就只用檢查一次。而如果我們不使用函數嵌套,那么每調用一次遞歸便會檢查一次,這是沒有必要的,也會降低程序的運行效率。

    實際工作中,如果你遇到相似的情況,輸入檢查不是很快,還會耗費一定的資源,那么運用函數的嵌套就十分必要。

函數變量作用域

  1. 局部變量優先級高於全局變量

    如果遇到函數內部局部變量和全局變量同名的情況,那么在函數內部,局部變量會覆蓋全局變量,比如下面這種:

    MIN_VALUE = 1
    MAX_VALUE = 10
    def validation_check(value):
        MIN_VALUE = 3
        ...
    

    這里MIN_VALUE=3

  2. 不能在函數內部隨意改變全局變量的值

    MIN_VALUE = 1
    MAX_VALUE = 10
    def validation_check(value):
        ...
        MIN_VALUE += 1
        ...
    validation_check(5)
    

    如果運行這段代碼,程序便會報錯:

    UnboundLocalError: local variable 'MIN_VALUE' referenced before assignment
    

    這是因為,Python 的解釋器會默認函數內部的變量為局部變量,但是又發現局部變量 MIN_VALUE 並沒有聲明,因此就無法執行相關操作。所以,如果我們一定要在函數內部改變全局變量的值,就必須加上 global 這個聲明:

    MIN_VALUE = 1
    MAX_VALUE = 10
    def validation_check(value):
        global MIN_VALUE
        ...
        MIN_VALUE += 1
        ...
    validation_check(5)
    

    這里的 global 關鍵字,並不表示重新創建了一個全局變量 MIN_VALUE,而是告訴 Python 解釋器,函數內部的變量MIN_VALUE,就是之前定義的全局變量,並不是新的全局變量,也不是局部變量。這樣,程序就可以在函數內部訪問全局變量,並修改它的值了.

  3. 對於嵌套函數來說,內部函數可以訪問外部函數定義的變量,但是無法修改,若要修改,必須加上 nonlocal 這個關鍵字:

    def outer():
        x = "local"
        def inner():
            nonlocal x # nonlocal關鍵字表示這里的x就是外部函數outer定義的變量x
            x = 'nonlocal'
            print("inner:", x)
        inner()
        print("outer:", x)
    outer()
    # 輸出
    inner: nonlocal
    outer: nonlocal
    

閉包

閉包(closure)其實和剛剛講的嵌套函數類似,不同的是:

  • 在嵌套函數中外部函數返回的是一個具體的值
  • 閉包中外部函數返回的是一個函數,返回的函數通常賦於一個變量,這個變量可以在后面被繼續執行調用。

比如,我們想計算一個數的 n 次冪,用閉包可以寫成下面的代碼


def nth_power(exponent):
    def exponent_of(base):
        return base ** exponent
    return exponent_of # 返回值是exponent_of函數

square = nth_power(2) # 計算一個數的平方
cube = nth_power(3) # 計算一個數的立方 
square
# 輸出
<function __main__.nth_power.<locals>.exponent(base)>

cube
# 輸出
<function __main__.nth_power.<locals>.exponent(base)>

print(square(2))  # 計算2的平方
print(cube(2)) # 計算2的立方
# 輸出
4 # 2^2
8 # 2^3

需要注意的是,在執行完square = nth_power(2)cube = nth_power(3)后,外部函數 nth_power() 的參數 exponent,仍然會被內部函數 exponent_of() 記住。這樣,之后我們調用 square(2) 或者 cube(2) 時,程序就能順利地輸出結果,而不會報錯說參數 exponent 沒有定義。

閉包解決了函數運行基礎變量問題,尤其這個函數需要被多次調用的時候。

補充:UnboundLocalError

參考博客

函數雖然在不被調用的情況下不會執行,但是python解釋器會做一些變量檢測、或者類型檢測,比如是不是有yield,如果有那么就會被標記為生成器,這個在編譯成字節碼的時候就已經確定了。

import dis
x = 1
y = 2


def foo():
    print(x)
    x = 2
    print(y)

dis.dis(foo)

# 直接調用 foo() 會報錯
# UnboundLocalError: local variable 'x' referenced before assignment

# 輸出
  7           0 LOAD_GLOBAL              0 (print)
              2 LOAD_FAST                0 (x)
              4 CALL_FUNCTION            1
              6 POP_TOP

  8           8 LOAD_CONST               1 (2)
             10 STORE_FAST               0 (x)

  9          12 LOAD_GLOBAL              0 (print)
             14 LOAD_GLOBAL              1 (y)
             16 CALL_FUNCTION            1
             18 POP_TOP
             20 LOAD_CONST               0 (None)

因為python尋找變量的時候,會按照本地作用域、閉包、全局、內置這種順序去查找,當看到x=2的時候,python解釋器就知道函數體內部聲明局部變量x,這是在編譯的時候就已經確定,於是在print的時候也會從本地查找,但是print(x)語句在x=2的上面,這是在執行的時候才發現的,於是報了個錯:提示局部變量x在賦值之前就已經被引用了。


免責聲明!

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



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