日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Python函數用法和底層原理分析_python

作者:Thank?CAT ? 更新時間: 2023-01-31 編程語言

Python函數用法和底層分析

函數是可重用的程序代碼塊。函數的作用,不僅可以實現代碼的復用,更能實現代碼的一致性。一致性指的是,只要修改函數的代碼,則所有調用該函數的地方都能得到體現。

在編寫函數時,函數體中的代碼寫法和我們前面講述的基本一致,只是對代碼實現了封裝,并增加了函數調用、傳遞參數、返回計算結果等內容。

為了讓大家更容易理解,掌握的更深刻。我們也要深入內存底層進行分析。絕大多數語言內存底層都是高度相似的,這樣大家掌握了這些內容也便于以后學習其他語言

函數的基本概念

  • 一個程序由一個個任務組成;函數就是代表一個任務或者一個功能。
  • 函數是代碼復用的通用機制。

Python 函數的分類

Python 中函數分為如下幾類:

  • 內置函數

我們前面使用的 str()、list()、len()等這些都是內置函數,我們可以拿來直接使用。

  • 標準庫函數

我們可以通過 import 語句導入庫,然后使用其中定義的函數

  • 第三方庫函數

Python 社區也提供了很多高質量的庫。下載安裝這些庫后,也是通過 import 語句導入,然后可以使用這些第三方庫的函數

  • 用戶自定義函數

用戶自己定義的函數,顯然也是開發中適應用戶自身需求定義的函數。

核心要點

Python 中,定義函數的語法如下:

def 函數名 ([參數列表]) : 
    '''文檔字符串''' 
    函數體/若干語

要點:

  • 我們使用 def 來定義函數,然后就是一個空格和函數名稱;

(1) Python 執行 def 時,會創建一個函數對象,并綁定到函數名變量上。

  • 參數列表

(1) 圓括號內是形式參數列表,有多個參數則使用逗號隔開'

(2) 形式參數不需要聲明類型,也不需要指定函數返回值類型

(3) 無參數,也必須保留空的圓括號

(4) 實參列表必須與形參列表一一對應

  • return 返回值

(1) 如果函數體中包含 return 語句,則結束函數執行并返回值;

(2) 如果函數體中不包含 return 語句,則返回 None 值。

  • 調用函數之前,必須要先定義函數,即先調用 def 創建函數對象

(1) 內置函數對象會自動創建

(2) 標準庫和第三方庫函數,通過 import 導入模塊時,會執行模塊中的 def 語

形參和實參

【操作】定義一個函數,實現兩個數的比較,并返回較大的值

def print_max(a,b):
    '''實現兩個數的比較,并返回較大的值'''
    if a > b:
        print(a,'MAX')
    else:
        print(b,'MAX')

print_max(10, 20)
print_max(99, -99)

#result
#20 MAX
#99 MAX

上面的 printMax 函數中,在定義時寫的 print_max(a,b)。a 和 b 稱為“形式參數”,
簡稱“形參”。也就是說,形式參數是在定義函數時使用的。 形式參數的命名只要符合“標識符”命名規則即可。

在調用函數時,傳遞的參數稱為“實際參數”,簡稱“實參”。上面代碼中,
printMax(10,20),10 和 20 就是實際參數。

文檔字符串(函數的注釋)

程序的可讀性最重要,一般建議在函數體開始的部分附上函數定義說明,這就是“文檔字符串”,也有人成為“函數的注釋”。我們通過三個單引號或者三個雙引號來實現,中間可以加入多行文字進行說明

【操作】測試文檔字符串的使用

def print_max(a,b):
    '''實現兩個數的比較,并返回較大的值'''
    if a > b:
        print(a,'MAX')
    else:
        print(b,'MAX')

print(help(print_max.__doc__))

#result
#No Python documentation found for '實現兩個數的比較,并返回較大的值'.
#Use help() to get the interactive help utility.
#Use help(str) for help on the str class.
#None

返回值

return 返回值要點:

  • 如果函數體中包含 return 語句,則結束函數執行并返回值;
  • 如果函數體中不包含 return 語句,則返回 None 值。
  • 要返回多個返回值,使用列表、元組、字典、集合將多個值“存起來”即可

【操作】計算a + b 不設置返回值

def print_star(a,b):
    a + b
c = print_star(4,10)
print(c)

#result
#None

【操作】計算a + b 設置返回值

def print_star(a,b):
    c = a + b
    return c

c = print_star(4,10)
print(c)
#result
#14

函數也是對象,內存底層分析

Python 中,“一切都是對象”。實際上,執行 def 定義函數后,系統就創建了相應的函數對象。我們執行如下程序,然后進行解釋:

def print_star(n):
    print("*"*n)
print(print_star)
print(id(print_star))
c = print_star
c(3)

#result
#<function print_star at 0x0000000002BB8620>
#45844000
#***

上面代碼執行 def 時,系統中會創建函數對象,并通過 print_star 這個變量進行引用:

我們執行“c=print_star”后,顯然將 print_star 變量的值賦給了變量 c,內存圖變成了:

顯然,我們可以看出變量 c 和 print_star 都是指向了同一個函數對象。因此,執行 c(3)和執行 print_star(3)的效果是完全一致的。 Python 中,圓括號意味著調用函數。在沒有圓括號的情況下,Python 會把函數當做普通對象。

變量的作用域(全局變量和局部變量)

變量起作用的范圍稱為變量的作用域,不同作用域內同名變量之間互不影響。變量分為:全局變量、局部變量。

全局變量:

  • 在函數和類定義之外聲明的變量。作用域為定義的模塊,從定義位置開始直到模塊結束。
  • 全局變量降低了函數的通用性和可讀性。應盡量避免全局變量的使用。
  • 全局變量一般做常量使用。
  • 函數內要改變全局變量的值,使用 global 聲明一下

局部變量:

  • 在函數體中(包含形式參數)聲明的變量。
  • 局部變量的引用比全局變量快,優先考慮使用。
  • 如果局部變量和全局變量同名,則在函數內隱藏全局變量,只使用同名的局部變量

【操作】全局變量的作用域測試

def f1():
    global a #如果要在函數內改變全局變量的值,增加 global 關鍵字聲明
    print(a) #打印全局變量 a 的值
    a = 300
f1()
print(a)

#result
#100
#300

【操作】全局變量和局部變量同名測試

a=100
def f1():
    a = 3 #同名的局部變量
    print(a)
f1()
print(a) #a 仍然是 100,沒有變

#result
#3
#100

【操作】 輸出局部變量和全局變

a = 100
def f1(a, b, c):
    print(a, b, c)
    print(locals())
    print('*' * 20)
    print(globals())

f1(1, 2, 3)

#result
#1 2 3 
#{'a': 1, 'b': 2, 'c': 3} 返回一個字典
#********************
#{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000023F0086CA10>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'c:\\Users\\chenh\\OneDrive\\Data Learn\\Python 基礎\\課堂筆記\\05\\Book_code.py', '__cached__': None, 'a': 100, 'f1': <function f1 at 0x0000023F00810680>}

部變量和全局變量效率測試

局部變量的查詢和訪問速度比全局變量快,優先考慮使用,尤其是在循環的時候。在特別強調效率的地方或者循環次數較多的地方,可以通過將全局變量轉為局部變量提高運行速度

【操作】測試局部變量和全局變量效率

#測試局部變量、全局變量的效率
import time
import math
def test01():
    start = time.time()
    for i  in range(100000000):
        math.sqrt(30)
    end = time.time()
    print('耗時{0}'.format(end - start))

def test02():
    b = math.sqrt
    start = time.time()
    for i  in range(100000000):
        b(30)
    end = time.time()
    print('耗時{0}'.format(end - start))

test01()
test02() 

#result
#耗時7.24362325668335
#耗時6.6801464557647705

參數的傳遞

函數的參數傳遞本質上就是:從實參到形參的賦值操作。 Python 中“一切皆對象”,所有的賦值操作都是“引用的賦值”。所以,Python 中參數的傳遞都是“引用傳遞”,不是“值傳遞”。具體操作時分為兩類:

  • 對“可變對象”進行“寫操作”,直接作用于原對象本身。
  • 對“不可變對象”進行“寫操作”,會產生一個新的“對象空間”,并用新的值填充這塊空間。(起到其他語言的“值傳遞”效果,但不是“值傳遞”)
    可變對象有:

字典、列表、集合、自定義的對象等
不可變對象有:
數字、字符串、元組、function

【操作】參數傳遞:傳遞可變對象的引用

b = [10,20]
def f2(m):
    print("m:",id(m)) #b 和 m 是同一個對象
    m.append(30) #由于 m 是可變對象,不創建對象拷貝,直接修改這個對象
f2(b)
print("b:",id(b))
print(b)

#result
#m: 1619876826368
#b: 1619876826368
#[10, 20, 30]

傳遞不可變對象的引用

傳遞參數是不可變對象(例如:int、float、字符串、元組、布爾值),實際傳遞的還是對象的引用。在”賦值操作”時,由于不可變對象無法修改,系統會新創建一個對象。

【操作】參數傳遞:傳遞不可變對象的引用

a = 100
def f1(n):
    print("n:",id(n)) #傳遞進來的是 a 對象的地址
    n = n + 200 #由于 a 是不可變對象,因此創建新的對象 n
    print("n:",id(n)) #n 已經變成了新的對象
    print(n)
f1(a)
print("a:",id(a)) #a的內存地址并沒有發生改變

#result
#n: 140717568683912
#n: 2640908885520
#300
#a: 140717568683912

顯然,通過 id 值我們可以看到 n 和 a 一開始是同一個對象。給 n 賦值后,n 是新的對象。

淺拷貝和深拷貝

為了更深入的了解參數傳遞的底層原理,我們需要講解一下“淺拷貝和深拷貝”。我們可以使用內置函數:copy(淺拷貝)、deepcopy(深拷貝)。

淺拷貝:不拷貝子對象的內容,只是拷貝子對象的引用。
深拷貝:會連子對象的內存也全部拷貝一份,對子對象的修改不會影響源對象

【操作】測試淺拷貝和深拷貝

#測試淺拷貝和深拷貝
import copy
def testCopy():
    '''測試淺拷貝'''
    a = [10, 20, [5, 6]]
    b = copy.copy(a)
    print("a", a)
    print("b", b)
    b.append(30)
    b[2].append(7)
    print("淺拷貝......")
    print("a", a)
    print("b", b)

def testDeepCopy():
    '''測試深拷貝'''
    a = [10, 20, [5, 6]]
    b = copy.deepcopy(a)
    print("a", a)
    print("b", b)
    b.append(30)
    b[2].append(7)
    print("深拷貝......")
    print("a", a)
    print("b", b)

testCopy()
print("*************")
testDeepCopy()

#result
'''
a [10, 20, [5, 6]]
b [10, 20, [5, 6]]
淺拷貝......
a [10, 20, [5, 6, 7]]
b [10, 20, [5, 6, 7], 30]
*************
a [10, 20, [5, 6]]
b [10, 20, [5, 6]]
深拷貝......
a [10, 20, [5, 6]]
b [10, 20, [5, 6, 7], 30]
'''

傳遞不可變對象包含的子對象是可變的情況

傳遞不可變對象時。不可變對象里面包含的子對象是可變的。則方法內修改了這個可變對象,源對象也發生了變化

a = (10,20,[5,6])
print("a:",id(a))

def test01(m):
    print("m:",id(m))
    m[2][0] = 888
    print(m)
    print("m:",id(m))

test01(a)
print(a)

#result
'''
a: 3006159512640
m: 3006159512640
(10, 20, [888, 6])
m: 3006159512640
(10, 20, [888, 6])
'''

參數的幾種類型

位置參數

函數調用時,實參默認按位置順序傳遞,需要個數和形參匹配。按位置傳遞的參數,稱為:“位置參數”。

【操作】測試位置參數

def f1(a,b,c):
    print(a,b,c)
f1(2,3,4)
f1(2,3) #報錯,位置參數不匹配

#result
'''
2 3 4
Traceback (most recent call last):
  File "c:\Users\chenh\OneDrive\Data Learn\Python 基礎\課堂筆記\05\Book_code.py", line 4, in <module>
    f1(2,3) #報錯,位置參數不匹配
    ^^^^^^^
TypeError: f1() missing 1 required positional argument: 'c'
'''

默認值參數

我們可以為某些參數設置默認值,這樣這些參數在傳遞時就是可選的。稱為“默認值參數”。默認值參數放到位置參數后面。

【操作】測試默認值參數

def f1(a, b, c=10, d=20): #默認值參數必須位于普通位置參數后面
    print(a, b, c, d)

f1(9, 8)
f1(8, 9, 19)
f1(8, 9, 19, 29)

#result
'''
9 8 10 20
8 9 19 20
8 9 19 29
'''

命名參數

我們也可以按照形參的名稱傳遞參數,稱為“命名參數”,也稱“關鍵字參數”。

【操作】測試命名參數

def f1(a,b,c):
    print(a,b,c)

f1(8, 9, 19)            #位置參數
f1(c=10, a=20, b=30)    #命名參數、

#result
'''
8 9 19
20 30 10
'''

可變參數

可變參數指的是“可變數量的參數”。分兩種情況:

  • *param(一個星號),將多個參數收集到一個“元組”對象中。
  • **param(兩個星號),將多個參數收集到一個“字典”對象中。

【操作】測試可變參數處理(元組、字典兩種方式

def f1(a,b,*c):
    print(a,b,c)

f1(8, 9, 19, 20)

def f2(a,b,**c):
    print(a,b,c)

f2(8, 9, name = 'gaoqi', age = 18)

def f3(a,b,*c,**d):
    print(a,b,c,d)

f3(8, 9, 20, 30, name = 'gaoqi',age = 18)\

#result
'''
8 9 (19, 20)    #將*c參數放在元組中
8 9 {'name': 'gaoqi', 'age': 18}    #將**c參數放在字典中
8 9 (20, 30) {'name': 'gaoqi', 'age': 18}
'''

強制命名參數

在帶星號的“可變參數”后面增加新的參數,必須在調用的時候“強制命名參數”。

【操作】強制命名參數的使用

def f1(*a,b,c):
    print(a,b,c)
#f1(2,3,4) #會報錯。由于 a 是可變參數,將 2,3,4 全部收集。造成 b 和 c 沒有賦值。
f1(2,b=3,c=4) 
f1(2, 3, 4, b=10, c=100)
'''
result:
(2,) 3 4
(2, 3, 4) 10 100
'''

lambda 表達式和匿名函數

lambda 表達式可以用來聲明匿名函數。lambda 函數是一種簡單的、在同一行中定義函數的方法。lambda 函數實際生成了一個函數對象。

lambda 表達式只允許包含一個表達式,不能包含復雜語句,該表達式的計算結果就是函數的返回值。

ambda 表達式的基本語法如下:

lambda arg1,arg2,arg3... : <表達式>

arg1/arg2/arg3 為函數的參數。<表達式>相當于函數體。運算結果是:表達式的運算結果。

【操作】lambda 表達式使

f = lambda a, b, c : a + b + c

print(f)
print(f(2, 3, 4))

'''
result:
<function <lambda> at 0x0000024E1DBA0680>
9
'''

g = [lambda a : a*2, lambda b : b*3, lambda c : c*4]

print(g)
print(g[0](6),g[1](7),g[2](8))

'''
result:
[<function <lambda> at 0x0000019D11368E00>, <function <lambda> at 0x0000019D11368F40>, <function <lambda> at 0x0000019D11368FE0>]
12 21 32
'''

eval()函數

功能:將字符串 str 當成有效的表達式來求值并返回計算結果。

語法參數:

eval(source[, globals[, locals]]) -> value

source:一個 Python 表達式或函數 compile()返回的代碼對象
globals:可選。必須是 dictionary
locals:可選。任意映射對象
s = "print('abcde')"
eval(s)

a = 10
b = 20
c = eval("a + b")
print(c)

dict1 = dict(a = 100, b = 200)

d = eval("a+b",dict1)
print(d)

'''
result:
abcde
30
300
'''

eval 函數會將字符串當做語句來執行,因此會被注入安全隱患。比如:字符串中含有刪除文件的語句。那就麻煩大了。因此,使用時候,要慎重!!!

遞歸函數

遞歸函數指的是:自己調用自己的函數,在函數體內部直接或間接的自己調用自己。遞歸類似于大家中學數學學習過的“數學歸納法”。 每個遞歸函數必須包含兩個部分:

  • 終止條件

表示遞歸什么時候結束。一般用于返回值,不再調用自己。

  • 遞歸步驟

把第 n 步的值和第 n-1 步相關聯。

遞歸函數由于會創建大量的函數對象、過量的消耗內存和運算能力。在處理大量數據時,謹慎使用。

【操作】 使用遞歸函數計算階乘(factorial

def factorial(n):
    if n == 1:
        return 1
    else:
        return n * factorial(n-1)

for i in range(1,11):
    print(i,'!=',factorial(i))

'''
result:
1 != 1
2 != 2
3 != 6
4 != 24
5 != 120
6 != 720
7 != 5040
8 != 40320
9 != 362880
10 != 3628800
'''

原文鏈接:https://www.cnblogs.com/thankcat/p/17004804.html

欄目分類
最近更新