回顧我們曾經在C語言中學過的函數,在程式設計過程中用到的次數真的是太多了,Python當中也是一樣,它是可重用的程式程式碼塊,且具有一致性,修改函數程式碼,則所有呼叫該函數的地方都能夠得到體現
基本格式:
def 函數名 ([參數列表]):
‘"文件字串’’’
函數體 / 若幹語句
【注】
# 測試函數
def is_prime(n):
flag = 1
for i in range(2,n):
if n % i == 0:
flag = 0
break
if flag == 1:
return True
else:
return False
num = int(input("Input a number:"))
if is_prime(num):
print("{0}是素數".format(num))
else:
print("{0}不是素數".format(num))
# 測試函數物件
print(id(is_prime))
print(type(is_prime))
print(is_prime)
# 測試函數
def printMax(a,b): # 形參(定義)
if a > b:
print(a,"較大值")
else:
print(b,"較大值")
printMax(10,20) # 實參(呼叫)
printMax(25,2)
增強程式的可讀性,三個單引號 / 三個雙引號
# 測試函數
def printMax(a,b): # 形參(定義)
'''輸入兩個數,判斷哪個值較大並列印較大值''' # 文件字串
if a > b:
print(a,"較大值")
else:
print(b,"較大值")
printMax(10,20) # 實參(呼叫)
printMax(25,2)
還可以通過help(函數名._ _ doc_ _)可以列印輸出函數的文件字串
help(printMax.__doc__)
return返回值要點:
【例】
# 測試返回值
# 有參 有返回值
def add(a,b):
print("求兩數和:{0},{1},{2}".format(a,b,(a+b)))
return (a+b)**2
# 無參 無返回值
def pri():
print("Hello")
print("Stefan")
return # return兩個作用:1.返回具體值 2.結束函數執行
print("Yes") # return之後不會執行
# 返回多個值
def calc(x,y,z):
return [x*3,y*4,z*5] # 返回一個列表
s = add(7,9)
print("兩數和的平方:",s)
print(add(10,20)*3)
a = pri()
print(a) # 無返回值 輸出None
print(calc(5,7,9))
Python中,一切即爲物件,在執行def所定義的函數之後,系統就建立了相應的函數物件,以後呼叫時就直接呼叫已建立好的那個函數物件,而不需要再建立一個
【例】
def func01():
print("sssddd")
func01()
a = func01
a()
print(id(func01))
print(id(a))
內層分析示意圖:
變數的作用域,即變數的作用範圍
【例】
#全域性變數 區域性變數
a = 3 # 全域性變數
def func01():
b = 4 # 區域性變數
print(b*3)
func01()
print(a)
print(b) # 報錯
記憶體分析:
【如果想在函數內部改變全域性變數值,用global】
#全域性變數 區域性變數
a = 3 # 全域性變數
def func01():
b = 4 # 區域性變數
print(b*3)
global a # 函數內要改變全域性變數的值,使用global關鍵字宣告
a = 300
func01()
print(a)
【輸出區域性變數和全域性變數】
#全域性變數 區域性變數
a = 100
c = 6
def fun1(): # 函數名fun1也是全域性
b = 5
global a
a = 300
print(locals()) # 列印輸出的區域性變數
print(globals()) # 列印輸出的全域性變數
fun1()
print(a)
{‘b’: 5}
{‘name’: ‘main’, ‘doc’: None, ‘package’: None, ‘loader’: <_frozen_importlib_external.SourceFileLoader object at 0x0330AF40>, ‘spec’: None, ‘annotations’: {}, ‘builtins’: <module ‘builtins’ (built-in)>, ‘file’: ‘C:/Users/Administrator/PycharmProjects/mypro01/test_if.py’, ‘cached’: None, ‘a’: 300, ‘c’: 6, ‘fun1’: <function fun1 at 0x033E7FA0>}
300
這裏又是一處可以進行優化的地方,尤其在回圈的時候,在特別強調效率的地方或者回圈次數較多的地方,可以通過將全域性變數轉爲區域性變數從而提高執行速度
# 測試區域性、全域性效率
import math
import time
def test01():
start1 = time.time()
for i in range(10000000):
math.sqrt(30)
end1 = time.time()
print("耗時:{0}".format(end1 - start1))
def test02():
b = math.sqrt
start2 = time.time()
for i in range(10000000):
b(30)
end2 = time.time()
print("耗時:{0}".format(end2 - start2))
test01()
test02()
本質:實參——>形參
由於Python中一切都是物件,所以在Python中的參數傳遞都是「參照傳遞」(地址),而不是「值傳遞」
Python中,可變物件有:
字典、列表、集合、自定義的物件等
對「可變物件」進行「寫操作」,直接作用於原物件本身
傳遞參數是可變物件時,實際傳遞的還是物件的參照。
在函數體中不建立新的物件拷貝,而是可以直接修改所傳遞的物件。
b = [10,20] # 可變物件
def f1(m):
print("m:",id(m))
m.append(30) # m = b 地址相同
f1(b)
print("b:",id(b))
print(b)
記憶體分析:
Python中,不可變物件有:
數位(int、float)、字串、元組、布爾值、function等
對「不可變物件」進行「寫操作」,會產生一個新的「物件空間」,並用新的值填充這塊空間(注意,只是起到其他語言「值傳遞」的效果,但本質上不是「值傳遞」)
傳遞參數是不可變物件時,實際傳遞的還是物件的參照
由於不可變物件無法修改,那麼系統會新建立一個物件用於「賦值操作」,沒有拷貝,依舊是參照。
a = 100
def f2(n):
print("n:",id(n))
n = n + 200
print("n:",id(n))
print(n)
f2(a)
print("a:",id(a))
記憶體分析:
使用內建函數:copy(淺拷貝)、deepcopy(深拷貝)
淺拷貝與深拷貝區別示意圖:
【測試淺拷貝】
# 測試 淺拷貝
import copy
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)
【淺拷貝具體分析】
【測試深拷貝】
# 測試 深拷貝
import copy
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)
【深拷貝具體分析】
【注】
傳遞參數是不可變物件時(如int、float、字串、元組、布爾值),實際傳遞的還是物件的參照,但在「寫操作」時,會建立一個新的物件拷貝,要注意的是,這個拷貝使用的是「淺拷貝」,而不是「深拷貝」。
【測試】
a = (10,20,[5,6])
print("a:",id(a))
def f1(m):
print("m:",id(m))
m[2][0] = 888
print("m:",m)
print("m:",id(m))
f1(a)
print("a",a)
函數呼叫時,實參預設按位元置順序傳遞,需要個數和形參匹配。
按位元置傳遞的參數,稱爲:位置參數
我們可以爲某些參數設定預設值,這樣這些參數在傳遞時就是可選的,稱爲「預設值參數」。
預設值參數放在位置參數後面。
【例】
def func1(a,b,c = 10,d = 20):
print(a,b,c,d)
func1(8,9)
func1(8,9,19)
func1(8,9,19,29)
在呼叫函數時,實參除了通過位置一一對應以外,還可以按照形參的名稱去傳遞參數
【例】
def func1(a,b,c):
print(a,b,c)
func1(8,9,19)
func1(c = 8,a = 9,b = 19)
可變參數:可變數量的參數
【例】
def func1(a,b,*c): # c是一個元組
print(a,b,c)
func1(8,9,19,20,45,6)
def func2(a,b,**c): # c是一個字典
print(a,b,c)
func2(4,8,name = 'Stefan',age = 24)
def func3(a,*b,**c):
print(a,b,c)
func3(2,4,6,7,9,name = 'Kitty',age = 22,address = 'New York')
要想在帶星號的「可變參數」後面增加新的參數,必須是「強制命名參數」
【測試】
def func1(*a,b,c):
print(a,b,c)
func1(2,3,4) # 報錯
【必須強制命名】
def func1(*a,b,c):
print(a,b,c)
func1(2,7,b = 3,c = 4)
lambda表達式可以用來宣告匿名函數。
lambda表達式是一種簡單的、在同一行中定義函數的方法,它實際上是生成了一個函數物件
【注】lambda表達式只允許包含一個表達式,不能包含複雜語句,該表達式的運算結果就是函數的返回值。
基本格式:
lambda arg1,arg2,arg3…: <表達式>
【注】
# lambda測試
f = lambda a,b,c : a + b + c
print(f)
print(f(2,3,4))
a = [lambda x:x ** 2,lambda y:y // 5,lambda z:z + 4]
print(a[2](3),a[1](10),a[0](7))
【函數也是物件】
# lambda測試
f = lambda a,b,c : a + b + c
print(f)
print(f(2,3,4))
a = [lambda x:x ** 2,lambda y:y // 5,lambda z:z + 4]
print(a[2](3),a[1](10),a[0](7))
g = [f,a] # 函數也是物件
print(g[0](1,6,8))
print(g[1][0](4),g[1][1](25),g[1][2](9))
eval()函數,將字串str當成有效的表達式來求值並返回計算結果
基本語法:
eval(source [,globals [,locals]]) -> value
通俗來講,就是可以將檔案、以及別的地方的字串(這個字串是一段可執行程式碼)傳過來,傳到一個變數中,通過對這個變數存取來執行這段程式碼字串,當成有效表達式來執行。
# eval()函數測試
s = "print('Hello yaya')"
eval(s)
a = 10
b = 20
c = eval("a+b")
print(c)
dict1 = dict(a = 100,b = 200)
d = eval("a+b")
e = eval("a+b",dict1)
print(d)
print(e)
自己呼叫自己
在函數體內部直接或間接得自己呼叫自己
每個遞回函數必須包含兩個部分:
【例1】
# 遞回測試
def f(n):
print("f:",n)
if n == 0:
print("over")
else:
f(n-1)
print("f**:",n)
f(4)
【函數呼叫記憶體分析】
【例2】求階乘
# 遞回求階乘
def fac(n):
if n == 1 or n == 0:
return 1
else:
return n * fac(n-1)
for i in range(8):
print("{0}!= {1}".format(i,fac(i)))
巢狀函數:
在函數內部定義的函數
# 巢狀函數
def outer():
print("outer running!")
def inner01(): # 僅限內部呼叫
print("inner01 running!")
inner01()
outer()
【注】一般在什麼情況下需要使用巢狀函數呢?
【測試】
def outer():
b = 10
def inner():
print("inner:",b)
b = 20 # 無法修改 報錯
inner()
outer()
def outer():
b = 10
def inner():
nonlocal b # 宣告外部函數的區域性變數
print("inner:",b)
b = 20
inner()
print("outer b:",b)
outer()
a = 100
def outer():
b = 10
def inner():
nonlocal b # 宣告外部函數的區域性變數
print("inner:",b)
b = 20
global a
a = 1000
inner()
print("outer b:",b)
outer()
print("a:",a)
什麼是LEGB?
Python在查詢「名稱」時,是按照LEGB規則查詢的:
Local–> Enclosed–> Global–> Built in
如果某個name對映在區域性(local)名稱空間中沒有找到,接下來就會在閉包作用域(enclosed)進行搜尋,如果閉包作用域也沒有找到,Python就會到全域性(global)名稱空間中進行查詢,最後會在內建(built-in)名稱空間搜尋(如果一個名稱在所有名稱空間中都沒有找到,就會產生一個NameError)。
【測試】
# 測試LEGB
print(type(str))
# str = "global"
def outer():
# str = "outer"
def inner():
# str = "inner"
print(str)
inner()
outer()
【注】這裏爲什麼不會報錯是因爲,Python中還有自動建立的內建函數str()在發揮作用