函式、引數、解構

scm1911發表於2019-02-15

函式、引數、解構

1 函式的概念

函式就是一個函式物件,函式的定義就是將函式名和函式記憶體中的函式地址對應起來
函式的數學定義是:y=f(x) ,y是函式名,x是自變數,自變數也可以是多個:y=f(x0,x1,x2,x3 ... xn)
python函式:python的函式的組成有:語句塊、函式名、引數列表,返回值。

python中的函式的,目的是完成一定的功能,是結構化程式設計對程式碼的最基本的封裝,一般按照功能組織一段程式碼,封裝是為了複用,減少程式碼的冗餘,使程式碼更美觀可讀。

2 函式的分類

pythonh中的函式分為:內建函式和庫函式

3 函式的定義和呼叫

3.1 函式的定義

python函式是通過 def 關鍵字定義的,具體的定義語法如下:

def add(x, y):
    res = x + y
    return res

def 是函式定義的關鍵字,x,y是函式的形式引數,上面的 add 就是定義的函式名,
實際上python中的函式名就是一個識別符號(變數名)。定義一個函式後,就在記憶體中建立了一個函式物件,變數名指向的這個函式物件的記憶體地址。
python中的函式如果沒有顯示的return語句,預設就會有一個隱式的return None.

3.2 函式的呼叫

定義一個函式只是宣告一個函式,並沒有執行該函式,我們只有在呼叫函式後才能發揮函式的功能。
函式的呼叫方式是在函式名的後面加上小括號,並加上必要的引數。
我們在呼叫函式的時候傳入的引數叫實參。

函式呼叫的示例

add(1,3)

檢視物件是否可呼叫物件可以使用callable()內建函式

callable(add)
=========================
In [5]: def add(x,y): 
   ...:     res = x + y 
   ...:     return res 
   ...:                  

In [6]: callable(add)    
Out[6]: True

返回 True 表示是一個可呼叫物件

我們通過print()返回的是函式的記憶體地址

In [7]: print(add)                    
<function add at 0x7fefc90e5730>

或者直接在ipython中執行add,返回是add的一種可見的字串模式

In [8]: add                           
Out[8]: <function __main__.add(x, y)>

3.3 函式的複用

函式是可以被其他函式呼叫的,函式本身也可以呼叫其他函式

def add(x, y):
    res = x + y
    raturn res

def new_add():
    return add(6, 9)
a = new_add()

執行的結果是

15

函式必須先定義後呼叫

其實很容易理解,沒有定義怎麼呼叫?

def new_add():
    return add(6, 9)
a = new_add()
=================================
In [5]: def new_add(): 
   ...:     return add(6,9) 
   ...:                                                                      

In [6]: a = new_add()                                                        
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-6-3072819d1adb> in <module>
----> 1 a = new_add()

<ipython-input-5-97d8213b487f> in new_add()
      1 def new_add():
----> 2     return add(6,9)
      3 

NameError: name 'add' is not defined

4 函式的引數定義和傳遞

4.1 函式的引數分類

  • 位置引數
    • 普通位置引數
    • 可變位置引數
  • 關鍵字引數(目前僅python適用)
    • 普通關鍵字引數
    • keyword-only引數
    • 可變關鍵字引數

4.2 位置引數、關鍵字引數的定義和傳遞

# 定義
def fn(x,y,z)    # 沒有設定預設值
def fn(x,y,z=5)  # 定義的時候,關鍵字設定的引數值叫做預設值,要在放在位置之後。
# 呼叫
fn(1,2,3)        # 適用於上面兩種定義方法的呼叫,位置引數的位置順序是一一對應的
fn(1,2)          # 只適用於上面有預設值定義方法的呼叫,這裡沒有為z傳參,使用的是預設的5
fn(x=1,y=2,z=3)  # 也可以全部使用關鍵字為位置引數傳參,此時傳參的順序沒有限制。
  • 傳參時候的注意點:
    • 位置引數必須要在關鍵引數之前傳入,位置引數是按位置一一對應的
    • 函式呼叫的時候傳入的引數個數和函式定義的時候的引數的個數要相匹配(可變引數除外)。
    • 不能為已經傳過的引數重複傳參
    • 關鍵字傳參是按名稱對應的,與傳參定義的順序沒有關係。
fn(1,2,z=3,x=4)  # 這種就是為x重複傳參,前面已經 x = 1 了,後面又來了 x = 5.

引數的預設值的作用:

  • 在未傳入足夠數量的實參的時候,對沒有給定的引數賦值為預設值
  • 當引數非常多的時候,避免使用者重複輸入相同的引數,簡化函式的呼叫

預設引數的定義和呼叫的典型示例

# 定義
def longin(host="127.0.0.1",port=80,username='test',password='youdonotknow'):
# 呼叫
login()    # 全部使用預設引數
login(host='192.168.200.1')   # 只有host使用自定義的引數,其他使用預設引數

4.3 可變位置引數的定義和傳遞

  • 在形參之前使用 * 表示該形參是可變的引數,一般使用 *args表示,可以匹配任意多個位置引數
  • 貪婪收集多個位置引數為一個tuple

定義

def add(*args)
    total = 0
    for i in args:
        tatal += i
        return total

傳遞

add(1,2,3)   # 1,2,3傳遞進函式之後會被 *args 全部接受並組成一個元組(1,2,3) 

4.4 可變關鍵字引數的定義和傳遞

  • 形參前使用 ** 符號,表示可以接收多個關鍵字引數
  • 收集的實參名稱和值組成一個字典

定義

def fn(**kwargs):
    for k,v in kwargs.items():
        print(k,v)

傳參

fn(host='127.0.0.1',port=80,username='test',password='test')

4.5 可變引數的混用

def fn(username, password, **kwargs):
def fn(username, *args, **kwargs):
def fn(username, **kwargs, *args):   # 錯誤的寫法,*args 不能寫在 **kwargs之後。

4.6 可變引數的總結

  • 可變的引數分為可變的位置引數和可變的關鍵字引數
  • 位置可變引數在形參前使用一個星號 *
  • 關鍵字可變引數在形參前使用兩個星號 **
  • 位置可變引數和關鍵字可變引數都可以收集若干個實參,位置可變引數收整合一個tuple,關鍵字可變引數收整合一個字典
  • 混合使用引數的時候,普通引數需要放在引數列表前面,可變引數要放在引數列表後面,位置可變引數需要在關鍵字可變引數之前
def fn(x,y,*args,**kwargs):

fn(3,5,7,9,10,a=1,b="python")
fn(3,5)
fn(3,5,7)
fn(x=3,y=4,5,6,a=2,b="test") # 錯誤的傳參,5,6不能放在x=3,y=4之後
fn(7,9,y=5,x=3,a=3,b='test') # 錯誤的傳參,7,9分別賦值給了x,y,但是y=5,x=3又重複賦值了。

4.7 keyword-only 引數

  • 在 *, *args 之後的引數只能是關鍵字形式的引數
  • 如果在一個星號 * 引數之後或者一個位置可變引數之後,出現的普通引數,實際上以及各不是普通引數,而是一個keyword-only引數。

定義

def fn(*args,x=1,y,**kwargs):   # x和y都是keyword-only引數,keyword-only引數可以設定預設值。

傳參

def dn(*args,x):
# args可以看作是已經截獲了所有的位置引數,x不是用關鍵字引數就不可能拿到實參。

思考:下面的定義方式是否可以?

def fn(**kwargs,y): 

上面的定義是不可以的,因為在一個 * 號之後的都是keyword 型別的引數,也就是說 y 也是一個keyword引數。
所以實際上,傳參的時候應該是 y=1 這樣的,但是這樣傳參後 y=1就會被 **keyword截獲,y 還是拿不到值。所以語法是錯誤的。

keYword-only引數的另一種形式

def fn(*, x,y):
    print(x,y)
fn(x=t=5,y=6)

* 號之後,普通的引數都變成了keyword-only引數

5 可變引數的預設值

  • 引數列表引數一般順序是,普通引數,預設引數,可變位置引數,keyword-only引數(可帶預設值)、可變關鍵字引數
  • 程式碼中的引數定義應該是見名知意,應該有好的程式碼習慣

下面是幾種函式可變引數以及引數預設值的定義以及傳參的示例

def fn(*args,x=5):
    print(x)
    print(args)

fn()      # 等價於fn(x=5), x=5  args=()
fn(5)     # 等價於fn(x=5), 傳參後,x=5  args=()
fn(x=6)   # 傳參後,x=6  args=()
fn(1,2,3,x=10)  # 傳參後,x=10  args=(1,2,3)

====================================================
def fn(y,*args,x=5):
    print('x={},y={}'.format(x,y))
    print(args)

fn()     # y 是一個普通的位置引數,必須傳入一個
fn(5)    # y=5, x=5,
fn(x=6)  # 沒有為 y 設定變數
fn(1,2,3,x=10)  # y=1,x=10 ,args = (2,3)
fn(1,2,y=3,x=10)  # y 重新賦值了
=====================================================
def fn(x=5,**kwargs):
    print('x={}'.format(x))
    print(kwargs)

fn()          # x=5, kwargs = {}
fn(5)         # x=5, kwargs = {}
fn(x=6)       # x=6, kwargs = {}
fn(y=3,x=10)  # x=10, kwargs = {"y":3}
fn(3,y=10)    # x=3, kwargs = {"y":10}
=====================================================
def fn(x,y,z=3,*args,m=4,n,**kwargs):   # 此函式必須為x,y,n傳入引數
    print(x,y,z,m,n)    
    print(args)        
    print(kwargs)
=====================================================
def connect(host='127.0.0.1',port=80,user='test',password='test',**kwargs):
    print(host,port)  
    print(user,password)
    print(kwargs)

connect(db='cmdb')    # 127.0.0.1 80 test test 

In [2]: connect(db='cmdb')
127.0.0.1 80
test test
{'db': 'cmdb'}

In [3]: connect(host='192.168.1.1',db='cmdb')
192.168.1.1 80
test test
{'db': 'cmdb'}

In [4]: connect(host='192.168.1.1',db='cmdb',password='mysql')
192.168.1.1 80
test mysql
{'db': 'cmdb'}

6 引數的解構

  • 給引數提供引數的時候,可以在集合型別前使用 * 或者 ** , 把集合型別的解構開,取出所有元素作為函式的實參。
  • 非字典型別使用 * 解構成為之引數
  • 字典型別使用 ** 解構成關鍵字引數
  • 提取出來的元素要和引數的要求匹配,也要和引數的型別匹配

通過幾個簡單的加法函式來演示

def add(x,y):
    return x + y

add(4,5)     # 正確
add((4,5))   # 不正確,需要解構,(4,5) 是一個引數

t = (4,5)
add(t[0],t[1])
add(*t) 或 add(*(4,5)) add(*[4,5]) add(*(4,6))
add(*range((1,3))
def add(x,y):
    return x+y

add(*(4,5))   # 9
add((*[4,5])) # 9
add(*{4,6})   # 10
d = {"x":1,"y":2}
add(**d)    # 3
add(**{"x":1,"y":2})  # 3
add(**{"a":1,"b":2})  # 報錯,因為解構之後是 a=1,b=2,而函式定義的時候是 x,y
add(*{"a":1,"b":2})   # 通過一個 * 解構之後,是隻對第一層的 key 解構,解構是 字串相加 'ab'
def add(*args):
    total = 0
    for i in args:
        total += i
    return total

add(1,2,3)     # 6
add(*(1,2,3))  # 6
add(*range(10))  # 45

7 函式引數的定義及傳參示例

7.1 示例1

定義

def fn(x,y,*args,**kwargs):
    print('x={}; y={}'.format(x,y))
    print('args   is {}'.format(args))
    print('kwargs is {}'.format(kwargs))

呼叫傳參

fn(1,2,3,4,5)
--------------------------------------------------------
C:\python36\python.exe D:/pyporject/test/test6.py
x=1; y=2
args   is (3, 4, 5)
kwargs is {}
fn(1,2,3,4,x=5)
--------------------------------------------------------
C:\python36\python.exe D:/pyporject/test/test6.py
Traceback (most recent call last):
  File "D:/pyporject/test/test6.py", line 8, in <module>
    fn(1,2,3,4,x=5)
TypeError: fn() got multiple values for argument 'x'
fn(y=2,x=1,z=3)
--------------------------------------------------------
C:\python36\python.exe D:/pyporject/test/test6.py
x=1; y=2
args   is ()
kwargs is {'z': 3}

7.2 示例2

定義

此時的x,y是keyword-only引數

def fn(*args,x,y,**kwargs):
    print('x={}; y={}'.format(x,y))
    print('args   is {}'.format(args))
    print('kwargs is {}'.format(kwargs))

呼叫傳參

fn(1,2,3,4,5)
--------------------------------------------------------
C:\python36\python.exe D:/pyporject/test/test6.py
Traceback (most recent call last):
  File "D:/pyporject/test/test6.py", line 7, in <module>
    fn(1,2,3,4,5)
TypeError: fn() missing 2 required keyword-only arguments: 'x' and 'y'
fn(1,2,3,4,5,x=6,y=7)
--------------------------------------------------------
C:\python36\python.exe D:/pyporject/test/test6.py
x=6; y=7
args   is (1, 2, 3, 4, 5)
kwargs is {}
fn(1,2,3,4,5,x=6,y=7,z=9)
--------------------------------------------------------
C:\python36\python.exe D:/pyporject/test/test6.py
x=6; y=7
args   is (1, 2, 3, 4, 5)
kwargs is {'z': 9}
fn(x=6,y=7,z=9)
--------------------------------------------------------
C:\python36\python.exe D:/pyporject/test/test6.py
x=6; y=7
args   is ()
kwargs is {'z': 9}

7.3 示例3

定義

此時的x,y是keyword-only引數

def fn(*,x,y):
    print('x={}; y={}'.format(x,y))

呼叫傳參

fn(x=6,y=7)
--------------------------------------------------------
C:\python36\python.exe D:/pyporject/test/test6.py
x=6; y=7

7.4 示例4

定義

此時的x,y是keyword-only引數

def fn(x,y,*args, m=9, n, **kwargs):  # 由於 m和n是keyword_only引數,所以可以寫成 m=9, n 的形式,順序沒有關係,因為傳參的時候是按照名稱對應傳參的。
    print('x={}; y={}'.format(x,y))
    print('args   is {}'.format(args))
    print('m={}; n={}'.format(m,n))
    print('kwargs is {}'.format(kwargs))

呼叫傳參

fn(1,2,3,4,5)
--------------------------------------------------------
C:\python36\python.exe D:/pyporject/test/test6.py
Traceback (most recent call last):
  File "D:/pyporject/test/test6.py", line 8, in <module>
    fn(1,2,3,4,5)
TypeError: fn() missing 1 required keyword-only argument: 'n'
fn(1,2,3,4,5,n=6)
--------------------------------------------------------
C:\python36\python.exe D:/pyporject/test/test6.py
x=1; y=2
args   is (3, 4, 5)
m=9; n=6
kwargs is {}
fn(1,2,3,4,5,m=9,n=119,z=555)
--------------------------------------------------------
C:\python36\python.exe D:/pyporject/test/test6.py
x=1; y=2
args   is (3, 4, 5)
m=9; n=119
kwargs is {'z': 555}
fn(1,2,m=9,n=119,z=555)
--------------------------------------------------------
C:\python36\python.exe D:/pyporject/test/test6.py
x=1; y=2
args   is ()
m=9; n=119
kwargs is {'z': 555}

8 *** 在函式定義和函式呼叫時候的區別 ※※※

  • *args, 和 **kwargs 在函式定義的時候叫做可變位置引數和可變關鍵詞引數,用來收集任意數量的位置引數和關鍵字引數
  • * 和 ** 在函式呼叫的時候叫做引數解構,只能用在函式對實參進行解構時,不能單獨使用。

在片函式的原始碼中就有類似的用法

函式的示例

def fn(*args,**kwargs):
    print(args)
    print(kwargs)

函式傳參後 *args**kwargs 分別收集位置引數和關鍵字引數

fn(1, 2, 3, a=4, b=5)
----------------------------------------------------------
C:\python36\python.exe D:/pyporject/test/test6.py
(1, 2, 3)
{'a': 4, 'b': 5}

函式傳參的時候,實參中使用 *** 的時候,* 將對應的對可解構的物件(list,set,range,str,tuple等)解構為位置引數,實參中使用 * 只可以對字典的key進行解構,** 將對應的字典解構為關鍵字引數
實參解構後還是會被函式的 *args**kwargs分別將位置引數和關鍵數引數收集

fn(*(1, 2, 3), **{'x': 'love', 'y': 'you'})
# === 等價於 ===
# fn(1, 2, 3, x='love', y='you')
----------------------------------------------------------
C:\python36\python.exe D:/pyporject/test/test6.py
(1, 2, 3)
{'x': 'love', 'y': 'you'}
fn(*[1, 2, 3], **{'x': 'love', 'y': 'you'})
# === 等價於 ===
# fn(1, 2, 3, x='love', y='you')
----------------------------------------------------------
C:\python36\python.exe D:/pyporject/test/test6.py
(1, 2, 3)
{'x': 'love', 'y': 'you'}
fn(*'123', **{'x': 'love', 'y': 'you'})
# === 等價於 ===
# fn("1", "2", "3", x='love', y='you')
----------------------------------------------------------
C:\python36\python.exe D:/pyporject/test/test6.py
('1', '2', '3')
{'x': 'love', 'y': 'you'}

等價的效果

fn(1, 2, 3, x='love', y='you')
----------------------------------------------------------
C:\python36\python.exe D:/pyporject/test/test6.py
(1, 2, 3)
{'x': 'love', 'y': 'you'}
fn("1", "2", "3", x='love', y='you')
----------------------------------------------------------
C:\python36\python.exe D:/pyporject/test/test6.py
('1', '2', '3')
{'x': 'love', 'y': 'you'}

在函式傳參的時候,我們可以使用 * 將多個可解構的物件解構為位置引數,使用 ** 將多個字典解構為關鍵自引數

fn(*'123', *(4, 5, 6), **{'x': 'love', 'y': 'you'})
----------------------------------------------------------
C:\python36\python.exe D:/pyporject/test/test6.py
('1', '2', '3', 4, 5, 6)
{'x': 'love', 'y': 'you'}
fn(*'123', *(4, 5, 6), **{'x': 'love', 'y': 'you'}, **{'m': 666, 'n': 999})
----------------------------------------------------------
C:\python36\python.exe D:/pyporject/test/test6.py
('1', '2', '3', 4, 5, 6)
{'x': 'love', 'y': 'you', 'm': 666, 'n': 999}

9 封裝解構中的解構和函式引數解構中的引數解構的區別

  • 封裝解構中的解構使用的是 *, 不支援 **
  • 封裝解構中的解構使用的是 *,對字典來說只能對key解構
  • 函式中的引數解構同時支援 ***
  • 函式中的引數解構,* 可以理解為是對可解構物件(list,set,range,str,tuple等)進行解構,解構之後就是一個一個的位置引數,對字典只能對key進行解構。
  • 函式中的引數解構,** 是對字典的解構,解構之後就是一個一個的key value 對兒。

本文連結: https://www.cnblogs.com/shichangming/p/10383404.html

相關文章