python教程:自定義函式

小小程序员ol發表於2024-07-04

1.多型

我們可以看到,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

2.函式巢狀

Python 函式的另一大特性,是 Python 支援函式的巢狀。所謂的函式巢狀,就是指函式里面又有函式,比如:

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

# 輸出
hello
world

巢狀帶來的好處

  • 函式的巢狀能夠保證內部函式的隱私。

內部函式只能被外部函式所呼叫和訪問,不會暴露在全域性作用域,因此,如果你的函式內部有一些隱私資料(比如資料庫的使用者、密碼等),不想暴露在外,那你就可以使用函式的的巢狀,將其封裝在內部函式中,只透過外部函式來訪問。比如:

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
  • 合理的使用函式巢狀,能夠提高程式的執行效率。

看下面這個例子:

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))

這裡,我們使用遞迴的方式計算一個數的階乘。因為在計算之前,需要檢查輸入是否合法,所以寫成了函式巢狀的形式,這樣一來,輸入是否合法就只用檢查一次。而如果我們不使用函式巢狀,那麼每呼叫一次遞迴便會檢查一次,這是沒有必要的,也會降低程式的執行效率。

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

3.函式變數作用域

  • 區域性變數優先順序高於全域性變數

如果遇到函式內部區域性變數和全域性變數同名的情況,那麼在函式內部,區域性變數會覆蓋全域性變數,比如下面這種:

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

這裡MIN_VALUE=3

  • 不能在函式內部隨意改變全域性變數的值
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,就是之前定義的全域性變數,並不是新的全域性變數,也不是區域性變數。這樣,程式就可以在函式內部訪問全域性變數,並修改它的值了.

  • 對於巢狀函式來說,內部函式可以訪問外部函式定義的變數,但是無法修改,若要修改,必須加上 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

4.閉包

閉包(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 沒有定義。

閉包解決了函式執行基礎變數問題,尤其這個函式需要被多次呼叫的時候。

5.UnboundLocalError

函式雖然在不被呼叫的情況下不會執行,但是python直譯器會做一些變數檢測、或者型別檢測,比如是不是有yield,如果有那麼就會被標記為生成器,這個在編譯成位元組碼的時候就已經確定了

import dis
x = 1
y = 2


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

dis.dis(foo)
# 學習中遇到問題沒人解答?小編建立了一個Python學習交流群:725638078
# 直接呼叫 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在賦值之前就已經被引用了。

相關文章