函數主要作用爲了複用
函數中的return定義
函數中,所有的語句都是有retrun操作,如果函數沒有自定義的return,則默認retrun None值
形參和實參
參數中,是一種形式的表示,一種符號的表達簡稱形參;而調用時所傳達的參數列表爲實實在在傳入的值,簡稱爲實參
def fn(xx): #形參
return xx
print(fn('hello')) #實參數
callable
使用callbale可以看其是否是可被調用對象
例:
def add(x,y):
return 1
print(callable(add))
True
函數參數
測定函數需要匹配定義個數相匹配(可變參數例外)
位置參數
調用使用:按照要求是按照順序去定義傳入實參
In [1]: def f(x,y):
...: print(x,y)
...:
In [2]: f('a','ccc')
a ccc
關鍵字參數
調用使用對應的形參明確指定要傳入的參數
使用形式參數名字傳入實參的方式,順序是可以和定義的順序不一樣,因爲是通過名稱去定義
In [3]: f(y='python', x='hello')
hello python
關鍵字傳參
In [5]: def f(x,y,z):
...: print(x,y,z)
調用
In [7]: f(z=None,y=10,x=[1])
返回值:
[1] 10 None
In [8]: f((1,),z=6,y=4.1)
返回值:
(1,) 4.1 6
位置參數必須在關鍵字參數之前傳入,否則會被誤認
In [13]: f((1,),z=6,1)
File "<ipython-input-13-452adbde2693>", line 1
f((1,),z=6,1)
^
SyntaxError: positional argument follows keyword argument
def add(x,y):
result = x + y
print(result)
add(6,7)
13
#將關鍵字參數先傳進會提示報錯
add(x=6,7)
add(x=6,7)
^
SyntaxError: positional argument follows keyword argument
總結:簡單來講,有默認值的參數,需要在後面進行調用
一般流程來講,用戶必須要填寫的參數,直接定義爲位置參數
例:
def add(x,y=9):
result = x + y
print(result)
add(6) #這樣x就被定義爲必須定義的對象
定義一個login函數,參數命名爲host,port,username,password
def login(host='127.0.0.1',port='8080',username='chaoye',password='q1w2e3'):
print('{}:{}:{}/{}'.format(host,port,username,password))
login()
127.0.0.1:8080:chaoye/q1w2e3
login('192.168.0.1',9909,username='haha')
192.168.0.1:9909:haha/q1w2e3
login('192.168.0.1',9909,password='this')
192.168.0.1:9909:chaoye/thi
*name 可變參數
傳遞參數,可以匹配任意個(0個到n個)參數
def fn(*args):
print(type(args))
print(args)
fn(1,2,3,4,88)
<class 'tuple'>
(1, 2, 3, 4, 88)
fn()
<class 'tuple'> # 可變參數在封裝過程中,將其以元組的方式進行傳遞
()
def fn(*nums):
sum = 0
for x in nums:
sum+=x
print(sum)
fn(10)
10
add(1,2,3,4,88) 這裏定義表示可以接收多個參數,這裏形參可以接受0個到多個實參
在傳遞的過程中,可變參數在封裝過程中,將其以元組的方式進行傳遞,因爲傳遞的同時已知其需要傳遞的個數,因爲內部是不允許改變nums
*傳遞是一個不可變的類型(元組)
驗證return
In [5]: def add(*nums):
...: sum = 0
...: for x in nums:
...: sum += x
...: print(sum)
...: #retrun sum
...:
In [6]: val = add(3,5,7)
15
In [8]: print(val)
None
這裏沒有定義return,則解釋器直接調用隱含的return None進行返回
自定義return
In [12]: def add(*nums):
...: sum = 0
...: for x in nums:
...: sum += x
...: return(sum)
In [14]: add(0)
0
Out[14]: 0
**kwargs 可變關鍵字參數
**kwargs 將傳遞進來的值封裝爲字典類型,格式如下:
def showconfig(**name):
print(name)
showconfig(a=5,b=32,hj=90)
調用:
調用的時候需要對關鍵字進行指定值的賦予
{'a': 5, 'b': 32, 'hj': 90}
例:
def showconfig(**kwagrs):
for k,v in kwagrs.items():
print(k,v)
showconfig(hostname='127.0.0.1',port='8080',username='chaoye',passwd='134qwe')
hostname 127.0.0.1
username chaoye
port 8080
passwd 134qwe
以上,傳遞的信息是未知的,可能是n個,所以需要使用可變類型去接收
我們需要確定這個值傳遞給那些kv,所以需要key=value 這樣進行賦值
傳遞之後會形成一個字典,我們只需要對字典進行操作即可
混合使用
將關鍵字參數提取,比如username,passwd ,因爲有些參數爲用戶必填,所以將其單獨分離出來,其他全部給予默認值
有時我們會忘記關鍵字參數,那麼可以只寫敏感部分,比如監控端口,用戶密碼等
def showconfig(username,password,**kwagrs):
print(username)
print(password)
for k,v in kwagrs.items():
print('{} = {}'.format(k,v))
showconfig('wangchao','1234',host='127.0.0.1')
wangchao
1234
host = 127.0.0.1
改進:
def showconfig(username,*args,**kwargs):
print(username)
print(args)
for k,v in kwargs.items():
print(k,v)
showconfig('chaoye')
定義時,第一個是必須需要定義的,其他都是可變類型,可變類型支持0到n個
定義位置的先後順序
*args 一般需要定義在**kwargs 之前的位置,以複雜度進行排放
def showconfig(username,**kwargs,*args):
print(username)
print(args)
for k,v in kwargs.items():
print(k,v)
如果定義的順序不當,則會直接告警
File "E:\python_project\a.py", line 3
def showconfig(username,**kwargs,*args):
^
SyntaxError: invalid syntax
[Finished in 0.1s with exit code 1]
總結:
·有位置可變的參數和關鍵可變的參數,*args和**kwargs
·位置可變參數和關鍵字可變參數(*args和**kwargs都可以收集若干個實例,位置可變參數(*args)收集實參封裝爲一個元組,關鍵字可變參數(**kwargs)則封裝爲一個字典
·混合使用參數的過程,可變參數要放到參數的列表最後,普通參數需要放到參數列表前,位置參數*args需放置**kwargs關鍵字參數前
即: 位置參數 --> 關鍵字參數 -->*args --> **kwargs
例:
以下語法沒有問題
def fn(*args,x,y,**kwargs):
print(x)
print(y)
print(args)
print(kwargs)
首先對位置參數進行賦值
fn(1,2,3)
提示:
TypeError: fn() missing 2 required keyword-only arguments: 'x' and 'y'
只對關鍵字參數進行賦值,但是沒有對位置參數進行傳遞,所以此類語法無法識別
fn(3,5,a=1,b='py')
以下語句通過,首先對args進行了傳遞,x和y分別以關鍵字形式進行傳遞,b以kw的方式傳遞
fn(3,5,y=1,x=2,b='python')
2
1
(3, 5)
{'b': 'python'}
關鍵字的傳參方式依然是將x,y進行對應,原則上沒有更變,只是區別當前的傳參的方式
keyword-only 關鍵字參數形參(強制參數)
keyword-only 在python3種加入,如在一個星號形參後,或一個位置可變參數後,則是kw-only參數類型
def fn(*args,x):
print(x)
print(args)
fn(3,5,1,x=9)
args可以手機所有的位置參數,x不能使用關鍵字參數就不可能拿到實參
如果kw放在後面是kw-only參數,kw爲可變kw,x也同樣,所以區分不出來,本身kw就跟順序無關,其本意都是收集到一個字典
*號特殊意義 後期詳細看
加上*號之後,*號將後面所有參數都強行轉爲keyword-only參數
def fn(*,x,y):
print(x,y)
fn(x=1,y=2)
這樣的定義在py3種的函數庫大量存在
例:
def fn(z,*,x,y):
print(z)
print(x,y)
fn(9,x=1,y=3)
單星號之後的參數都是kw-only參數,需要指定絕對的缺省值
一般都會給一個默認值,但是必須提醒其存在
def fn(*args,x=1):
print(x)
print(args)
可變的本身就是0個,唯一需要確定的是在args星號後,不是普通參數,而是變身爲kw-only類型,如果需要傳參的話,至少傳3個
def connect(host='localhost',port=3306,user='admin',**kwargs):
print(host,port)
print(user)
print(kwargs)
connect(db='cmdb')
localhost 3306
admin
{'db': 'cmdb'}
connect(host='123',db='cmdb')
123 3306
admin
{'db': 'cmdb'}
函數解構
例:
def add(x,y):
print(x+y)
return x+y
add((1,2)[0],(3,4)[1])
使用*號對數據結構進行解析
def add(x,y):
print(x+y)
return x+y
t = (3,4)
add(*t)
當前變量t爲記憶參數,對應的是元組,但是需要將元組拆解
def add(x,y):
print(x,y)
t = [1,2]
add(*t)
1 2
這裏必須將其對應參數位置,否則會報錯,也就是說形參和實參數必須對應一致
t = [1,2,3,4]
add(*t)
add(*t)
TypeError: add() takes 2 positional arguments but 4 were given
使用可迭代對象
add(*range(1,3))
1 2
解構字典
如解構字典類型必須使用**kwargs的形參即可
def fn(**kwargs):
for k,v in kwargs.items():
print(k,v)
d = {'a':1,'b':2}
fn(**d)
在解構的過程,必須對應函數的位置參數
使用字典方法進行傳參:這樣就並非是字符串傳參,而是直接將其付給形參
可以對其進行遍歷或者取key或value進行解構
可變的類型:
def add(*iterable):
result = 0
for x in iterable:
result += x
return result
print(add(1,2,3))
print(add(*[1,2,3]))
add(*range(10))
函數返回值
return和print
return的作用是直接出棧,寫在return之後會被認爲是廢語句
print 會作爲隱含類型進行轉換並輸出
隱式調用
return = return None
def showlist():
return [1,2,3]
print(type(showlist()))
showlist返回的爲一個值,這裏爲一個列表,但是列表中是存在元素的
def showlist():
return 1,2,3
print(type(showlist()))
當沒有定義返回的數據類型,則默認將其封裝爲元組並進行返回
所以,在return的時候都是返回一個值,最後一個以元組方式進行封裝並返回
函數嵌套
內部函數不能被外部直接使用,會直接拋NameError異常
如下
def outer():
def inner():
print('inner')
print('outer')
outer()
inner()
inner()
NameError: name 'inner' is not defined
函數外是不可看到函數內部,也就是說在函數外是找不到內部定義的
作用域
函數作爲一個標識符的可見範圍,被稱爲作用域,一般來講指的是變量
例:
def fn():
out = 123 #這個變量也算爲一個標識符
print('outer')
fn()
在函數內定義的變量,在函數外是不可被調用,因爲不可以超越函數
目前我們在全局定義變量,在函數內是可以的
x = 50
def show():
print(x)
show()
在函數中將變量進行更改
x = 50
def show():
x += 1
print(x)
show()
x += 1
UnboundLocalError: local variable 'x' referenced before assignment
在函數本地變量中,不允許對全局變量進行操作
作用域
全局作用域
在整個程序中運行環境都可見
局部作用域
在函數內定義的變量,被稱爲本地變量,只限局部使用範圍,其他範圍不可以被調用
函數嵌套結構
def outer1():
o = 65
def inner():
print('inner',o)
print('outer',o)
inner()
outer1()
outer 65
inner 65
def outer2():
o = 65
def inner():
o = 97 #當進入嵌套函數中,o已經被重新定義爲一個新的變量
print('inner',o) #其打印的變量爲上行重新定義的值
print('outer',o)
inner()
outer2()
outer 65
inner 97
原因在於賦值的定義,在外部的變量o與inner函數中的變量o沒有任何關係
外部對內部是可見的,但是內部對外部則爲不可見,在動態語言中,賦值既定義 所以都是在局部作用域中生效
x = 5
def foo():
y = x + 1
x += 1
print(x)
foo()
y = x + 1
UnboundLocalError: local variable 'x' referenced before assignment
在函數本地變量中,不允許對其進行操作,看似是y賦值時錯誤,但實際爲x+=1的時候出現錯誤
以上信息爲沒有綁定變量本地錯誤
至於x += 1爲何報錯的解釋如下:
因爲在本地語句塊定義的x,在等式中先進行右運算,右邊有x存在,那麼被認爲
x沒有被賦值的空標識符,所以都被認爲是局部變量
全局變量global
通過global對其進行全局變量聲明
z = 1
def xx():
global z
z += 21
print(z)
xx()
22
工作流程是先調用內部,再調用外部;在函數體內通過golbal 關鍵字聲明變量;
將xx內的z使用外部的全局作用域中進行定義
賦值既定義的理念
回到之前的例子中,
x = 5
def foo():
y = x + 1
x += 1
print(x)
foo()
y = x + 1
UnboundLocalError: local variable 'x' referenced before assignment
當x = 10,在x內部作用域作爲一個外部作用域的變量賦值,所以在x+=1 的時候不會報錯,這裏的x作用域還是全局的x+=1,先引用後賦值
而python中,賦值纔算真正的定義,才能被引用
解決辦法:在語句前重新增加賦值語句,或者使用global告知內部作用域去全局作用域中定義這個變量
內部作用域中x = 1 之類的賦值語句會重新定義局部作用域使用的變量x,但是一旦聲明全局global,聲明x爲全局的,那麼x=1相當於在全局作用域的變量x賦值
例:
z = 1
def xx():
global z
z += 21
print('xx_z:',z)
xx()
print('global_z:',z)
xx_z: 22
global_z: 22
閉包
閉包在裝飾器中使用比較廣泛
閉包的概念
自由變量:
沒有在本地作用域中定義變量,在內層函數外的定義函數的自由變量
並且在因曾函數引用到了外層函數的自由變量
def counter():
c = [0]
def inc():
c[0] += 1 #僅僅是對元素在賦值,與賦值變量是兩碼事
return c[0]
return inc # 是返回一個標識符(函數的引用),可調用的對象,inc本身就是可調用對象
foo = counter()
print([foo() for _ in range(10)])
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
查看函數的類型
def counter():
c = [0]
def inner():
c[0] += 1
return c[0]
return inner
foo = counter()
print(type(foo))
<class 'function'>
按照常理c會被賦值,但是現在爲內部調用,所以在foo = counter()的時候,獲取了引用變量,引用變量+1,inner不會被銷燬,在return的時候直接return到inner()函數中進行了調用
c = 200
def counter():
c = [0]
def inner():
c[0] += 1
return c[0]
return inner
c = 200
foo = counter()
print(type(foo))
c = 200
print(foo())
print(foo())
<class 'function'>
1
2
內部函數使用了外部自由變量則產生了一個閉包
如果對外部的自由變量進行改變的話,在2版本中只能使用元素修改的方式
如下所示:
c = 200
def counter():
c = [0]
def inner():
c[0] += 1
print(c)
return c[0]
print(c[0])
return inner
foo = counter()
foo()
foo()
[0]
#以下爲閉包所產生的值
1
2
3
總結:
閉包的概念:內部函數使用了“外部自由變量”的時候產生了一個所謂比高
nonlocal
python3使用了nonlocal方式進行閉包
將變量標記在上級的局部作用域中定義,但是不能在全局作用域中定義
python2 中和 python3種的閉包對比:
python3
def outer():
count = 0
def inner():
nonlocal count
count += 1
print(count)
return count
return inner
foo = outer()
foo()
foo()
1
2
在3中,使用nonlocal只能對其可以直接對上級變量進行操作,但是不能在全局中進行操作
python2中,僅能對自由變量進行操作
def outer():
c = [0]
def inner():
c[0] += 1
print(c)
return c
return inner
foo = outer()
foo()
foo()
默認值作用域 foo.__defaults__
涉及函數形參默認值
def foo(a=[]):
a.append(100)
print(a)
foo()
[100]
雖然是形式參數,但其也是局部變量
這個函數是一個對象,這個對象並非被銷燬,說明對象存在,那麼將其默認值a=[]存放在一個特殊屬性上,也就是foo.__defaults__
In [2]: foo.__defaults__
Out[2]: ([],)
In [3]: foo()
[1]
In [4]: foo()
[1, 1]
在此查看默認值
In [10]: foo.__defaults__
Out[10]: ([1, 1],)
在函數外部調用並查看
In [11]: print(foo(),id(foo))
[1, 1, 1]
None 139733908903048
In [12]: print(foo.__defaults__)
([1, 1, 1],)
In [13]: print(foo(),id(foo))
[1, 1, 1, 1]
None 139733908903048
In [14]: print(foo.__defaults__)
([1, 1, 1, 1],)
In [15]: print(foo(),id(foo))
[1, 1, 1, 1, 1]
None 139733908903048
當傳遞進來lst之後,則是將引用對象傳遞,他們之間都是調用同一個id的,所以值是更改的
將更改的值return
def foo(a=None):
if a is None:
a = []
a.append(1)
return a
print(foo([1]))
print(foo())
[1, 1]
[1]
賦予函數None缺省值是一種慣例,一般形參上定義None說明接下來會傳遞某些參數進行一些處理性的操作
一般如果後期有定義或者傳遞的需求,建議將默認值寫爲None
總結:
1.使用淺拷貝創建一個新的對象,永遠不能改變傳入的參數
2.通過值判斷,靈活的選擇串講或者修改傳入的對象
方法很龍火,在很多場景下,函數定義都可以看到使用None,這個不可變的值作爲默認值進行傳參,
函數的銷燬
1.del funcname
2.覆蓋,查看地址是否一致
3.待到程序結束時