函數的定義、解構、及銷燬過程

函數主要作用爲了複用

函數中的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)

spacer.gif

可變的本身就是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進行解構

spacer.gif

spacer.gif

可變的類型:

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 

總結:

閉包的概念:內部函數使用了“外部自由變量”的時候產生了一個所謂比高

spacer.gif

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.待到程序結束時

spacer.gif


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章