python基礎--函式全解析

AndreasZhou發表於2020-06-18

函式(重點)

(1)初始函式

在認識函式之前,我們先做如下的需求:

讓你列印10次“我愛中國,我愛祖國”。我們在接觸函式之前是這樣寫的。

print('我愛中國,我愛祖國')
print('我愛中國,我愛祖國')
print('我愛中國,我愛祖國')
print('我愛中國,我愛祖國')
print('我愛中國,我愛祖國')
print('我愛中國,我愛祖國')
print('我愛中國,我愛祖國')
print('我愛中國,我愛祖國')
print('我愛中國,我愛祖國')
print('我愛中國,我愛祖國')

那麼如果再出現一個需求,讓你列印100和1000次“我愛中國,我愛祖國”。那麼你是不是要寫100和1000次的print('我愛中國,我愛祖國')呢?這樣寫的程式碼是不是合理呢?

那麼這樣寫好麼? 當然不好了,為什麼呢? 重複程式碼太多了。 所以我們能否將這些程式碼放到一個地方,想用這些程式碼了,我就通過一個指令,呼叫過來,不想用就不寫這個指令就行了,這樣就能極大限度的減少程式碼的重複率,那麼我們們看下面:

def country():
    print('我愛中國,我愛祖國')

那麼這裡,我寫了一個可以列印的功能,我將上面的那些重複程式碼封裝到這個所謂的函式中,這樣,我什麼時候需要使用這個功能,我通過一個指令呼叫即可。

def country():
    print('我愛中國,我愛祖國')

country() # 這個就是函式的一個指令的呼叫,通過這個我們就可以執行函式。

上面這個就是一個函式,我們接下來就要研究一下函式,從上面的對比我們看一下函式的優勢:

1.減少程式碼的重複性,降低程式碼的冗餘程度。使得我們編寫的程式碼更加的簡潔明瞭。

2.使程式碼可讀性更好,程式設計師的編碼更加友好。

# 我們可以將一模一樣的方法用函式進行封裝,減少程式碼的冗餘程度
# 重複的程式碼的數量太多了,導致我們寫的程式碼是low的,重複的程式碼很多
# 程式碼的可讀性是比較差的。

(2)函式的結構與呼叫

1)函式的結構

# def 函式名():
#     函式體
def func():
    print('你好,我是Andreas!')

def是關鍵字,用來定義函式,是固定不變的,以def這個關鍵字開頭,空格之後接的是函式名和圓括號(),最後還有一個":"。

空格:為了將def關鍵字和函式名分開。

函式名:函式名和變數的命名是一樣的。函式名只能包含字串、下劃線和數字且不能以數字開頭。雖然函式名可以隨便起,但我們給函式起名字還是要儘量簡短,並且要具有可描述性

括號:是必須加的,一定要加上括號。不然會報錯。

下面的函式體一定全部都要縮排,這代表是這個函式的程式碼。(pycharm會自動縮排)

2)函式的呼叫

那麼我們現在討論一下,函式什麼時候開始執行的呢?是在我們定義了這個函式,就會執行嗎?還是?

使用函式名加小括號就可以呼叫了。寫法是:函式名()。這個函式的函式體才會被執行。只有直譯器讀到函式名() 時,才會執行此函式,如果沒有這條指令,不管多少行程式碼,都是不會被執行的。當'函式名()'你寫幾次,函式體裡面的程式碼就執行幾次。

def func():
    print('你好,我是Andreas!')

3)函式的結構和呼叫的總結

'''
結構:def  關鍵字 定義函式
func是函式名,與變數的設定是相同的,具有可描述性  login()
函式體:縮排。函式中儘量不要出現print函式
函式什麼時候執行?
當函式遇到函式名()時, 函式才會執行!!!
或者說當函式被呼叫了,那麼才會被執行!!!
'''

(3)函式的返回值

在實際的開發的過程中,我們所定義的一個函式封裝了所對應的一個功能,這個功能一般都會有一個最終結果的。比如寫一個登入函式,最終登入成功與否是不是需要返回你一個結果?還有我們們是不是都用過len這個函式,他是獲取一個物件的元素的總個數,最終肯定會返回一個元素個數這樣的結果:

s1 = 'abfdas'
print(len(s1))  # 6
'''
return:return有兩個功能
第一個功能是:在函式中遇到return直接結束函式。return後面的程式碼就不會執行了。
第二個功能是:可以在函式中返回處理後的最終的結果值。將資料返回給函式的執行者,呼叫者func()
return 返回多個元素是以元組的形式返回給函式的執行者,可以採用元組的拆包,獲取到元組
中的每一個資料,呼叫者可以直接使用元組的解構獲取多個變數。
'''

'''
: return 總結:
    1.在函式中,終止函式
    2.return 可以給函式的執行者返回值
        1.return 返回單個值    單個值
        2.return 返回多個值    (多個值,),以元組的形式展示多個值
    3.
'''

(4)函式的引數

1)引數的概念

函式的結構,函式的執行,以及函式的返回值。對函式有一個初步的瞭解,那麼接下來就是一個非常重要的知識點,函式的引數。我們先看一個例子:

s1 = 'dsadccafdcscfwe'
l1 = [1, 2, 3]
a = len(s1)
b = len(l1)
print(a, b)
# 輸出的結果為:15 3

從上可知,len是python系統定義的函式,程式設計師呼叫函式時,傳入了s1和l1。那麼這個為什麼要傳入呢?這裡就是我們要學習的引數。

上面就是函式傳參的示例,函式的引數可以從兩個角度劃分:

1.形參:寫在函式宣告的位置的變數叫形參,形式上的一個完整.表示這個函式需要xxx

2.實參:在函式呼叫的時候給函式傳遞的值.加實參,實際執行的時候給函式傳遞的資訊.表示給函式xxx

3.函式的傳參就是函式將實際引數交給形式引數的過程.

2)實際引數

1.實參的第一種:位置引數

位置引數就是從左至右,實參與形參一一對應。

編寫函式,給函式傳遞兩個引數a,b a,b相加 返回a引數和b引數相加的和

def f(a,b):
    c = a+b
    return c
num_sum = f(5,8)
print(num_sum)
結果: 13

編寫函式,給函式傳遞兩個引數a,b 比較a,b的大小 返回a,b中最大的那個數

def f(a,b):
    if a>b:
        return a
    else:
        return b
num_sum = f(5,8)
print(num_sum)
結果:8

比較大小的這個寫法有點麻煩,我們在這裡學一個三元運算子

def f(a,b):
    c = a if a > b else b  #當a>b就把a賦值給c,否則就把b賦值給c
    return c
msg = f(5,7)
print(msg)
結果:
7

2.實參的第二種:關鍵字引數 

位置引數好不好呢? 如果是少量的引數還算OK, 沒有問題. 但是如果函式在定義的時候引數非常多怎麼辦? 程式設計師必須記住, 我有哪些引數, 而且還有記住每個引數的位置, 否則函式就不能正常呼叫了. 那則麼辦呢? python提出了一種叫做關鍵字引數. 我們不需要記住每個引數的位置. 只要記住每個引數的名字就可以了

def date(sex, age, hobby):
    print('設定篩選條件:性別: %s,年齡:%s,愛好:%s' %(sex, age, hobby))
date(hobby='唱歌',sex='女',age='25~30',)

搞定, 這樣就不需要記住繁瑣的引數位置了.

3.實參的第三種:混合引數

可以把上面兩種引數混合著使用. 也就是說在呼叫函式的時候即可以給出位置引數, 也可以指定關鍵字引數.

混合引數一定要記住:關鍵字引數一定在位置引數後面。
def date(sex, age, hobby):
    print('設定篩選條件:性別: %s,年齡:%s,愛好:%s' %(sex, age, hobby))
date('女',hobby='唱歌',age='25~30',)

綜上: 在實參的⾓角度來看引數分為三種:

1. 位置引數
2. 關鍵字引數
3. 混合引數,  位置引數必須在關鍵字引數前面

接下來我們從形參角度分析,函式的引數

3)形式引數

1.形參的第一種:位置引數

  位置引數其實與實參角度的位置引數是一樣的,就是按照位置從左至右,一一對應

def date(sex, age, hobby):
    print('設定篩選條件:性別: %s,年齡:%s,愛好:%s' %(sex, age, hobby))
date('女','25~30','唱歌')

2.形參的第二種:預設值引數

  在函式宣告的時候, 就可以給出函式引數的預設值. 預設值引數一般是這個引數使用率較高,才會設定預設值引數,可以看看open函式的原始碼,mode=‘r’就是預設值引數. 比如, 我們錄入我們們班學生的基本資訊. 通過調查發現. 我們班大部分學生都是男生. 這個時 候就可以給出⼀一個sex='男'的預設值.

def stu_info(name, age, sex='男'):   
    print("錄入學生資訊")
    print(name, age, sex)   
    print("錄入完畢")
stu_info("張強", 18)

注意:必須先宣告在位置引數,才能宣告關鍵字引數

綜上:在形參的角度來看

  1. 位置引數
  2. 預設認值引數

3.形參的第三種:動態引數(args和*kwargs)

在說明*args和**kwargs的用法之前,我們先討論一下以下的一種情況。

一個是位置引數,位置引數主要是實參與形參從左至右一一對應,一個是預設值引數,預設值引數,如果實參不傳參,則形參使用預設引數。那麼無論是位置引數,還是預設引數,函式呼叫時傳入多少實參,我必須寫等數量的形參去對應接收, 如果不這樣,那麼就會報錯:

def eat(a,b,c,):
print('我請你吃:',a,b,c) 
eat('蒸羊羔','蒸熊掌','蒸鹿尾兒','燒花鴨') # 報錯
# 如果我們在傳引數的時候不很清楚有哪些的時候,或者說給一個函式傳了很多實參,我們就要對應# 寫很多形參,這樣很麻煩,怎麼辦?,我們可以考慮使用動態引數也叫萬能引數
# 我們現在急需要一種形參,可以接受所有的實參。 # 萬能引數
# def eat(*args):
#     print(args, type(args))  
# ('1', '2', '3', '4', '5', '6') <class 'tuple'>
#     print('我請你吃:%s, %s, %s, %s, %s, %s' % args) 
# 我請你吃:1, 2, 3, 4, 5, 6


# 萬能引數:*args  約定俗稱:args。
# * 函式定義時,* 代表聚合。他將所有的位置引數聚合成一個元組,賦值給了args
# *args是萬能引數,args是元組型別
# eat('1', '2', '3', '4', '5', '6')


# 輸出的結果為:我請你吃:1, 2, 3, 4, 5, 6


# 寫一個函式:計算你傳入函式的所有的數字的和

# def func(*args):
#     count = 0
#     for i in args:
#         count += i
#     return count


# print(func(1, 2, 3, 4, 5, 6, 7))
# 輸出的結果為:28


# **kwargs
# 函式的定義時: **kwargs將所有的關鍵字引數聚合到一個字典中,將這個字典賦值給了kwargs。
# def func(**kwargs):
#     print(kwargs, type(kwargs))  # {'name': 'zhouqian', 'age': 23, 'sex': 'boy'} <class 'dict'>
#
#
# func(name='zhouqian', age=23, sex='boy')

如果一個引數設定了動態引數,那麼他可以接受所有的位置引數,以及關鍵字引數,這樣就會大大提升函式擴充性,針對於實參引數較多的情況下,解決了一一對應的麻煩。

4.形參的第四種引數:僅限關鍵字引數

僅限關鍵字引數是python3x更新的新特性,他的位置要放在*args後面,kwargs前面(如果有kwargs),也就是預設引數的位置,它與預設引數的前後順序無所謂,它只接受關鍵字傳的引數:

# 這樣傳參是錯誤的,因為僅限關鍵字引數c只接受關鍵字引數
def func(a,b,*args,c):
print(a,b) # 1 2
print(args) # (4, 5)
# func(1, 2, 3, 4, 5)
# 這樣就正確了:
def func(a,b,*args,c):
print(a,b) # 1 2
print(args) # (3, 4)
print(c)
func(1, 2, 3, 4, c=5)

這個僅限關鍵字引數從名字定義就可以看出他只能通過關鍵字引數傳參,其實可以把它當成不設定預設值的預設引數而且必須要傳引數,不傳就報錯。

4)*的高階用法

*的用法有兩種:

1.函式中分為打散和聚合。

2.函式外可以處理剩餘的元素。

# * 在函式的呼叫是,*代表打散
def func(*args, **kwargs):
    # print(args, type(args))  # ([1, 2, 3], [22, 33]) <class 'tuple'>
    print(args, type(args))  # (1, 2, 3, 22, 33) <class 'tuple'>
    print(kwargs, type(kwargs))


# * 在函式的呼叫是,*代表打散
func([1, 2, 3], [22, 33])
func(*[1, 2, 3], *[22, 33])
func({'name': 'zhouqian'}, {'age': 18})
func(*{'name': 'zhouqian'}, *{'age': 18})
func(**{'name': 'zhouqian'}, **{'age': 18})
'''
輸出的結果如下:
([1, 2, 3], [22, 33]) <class 'tuple'>
{} <class 'dict'>

(1, 2, 3, 22, 33) <class 'tuple'>
{} <class 'dict'>

({'name': 'zhouqian'}, {'age': 18}) <class 'tuple'>
{} <class 'dict'>

('name', 'age') <class 'tuple'>
{} <class 'dict'>

() <class 'tuple'>
{'name': 'zhouqian', 'age': 18} <class 'dict'>

'''
# *在函式定義的時候表示聚合,*在函式呼叫的時候表示打散

# *處理剩下的元素

# *除了在函式中可以這樣打散,聚合外,函式外還可以靈活的運用:

# 之前講過的分別賦值
a,b = (1,2)
print(a, b) # 1 2
# 其實還可以這麼用:
a,*b = (1, 2, 3, 4,)
print(a, b) # 1 [2, 3, 4]
*rest,a,b = range(5)
print(rest, a, b) # [0, 1, 2] 3 4
print([1, 2, *[3, 4, 5]]) # [1, 2, 3, 4, 5]

5)形參的順序(難點)

直接給出結論:形參的最終的順序:位置引數,*args,預設引數,僅限關鍵字引數c,**kwargs。

討論的過程如下:

# 形參角度的引數的順序
# 我們通過下面的幾個簡單的例子,來討論形參的順序。

# *args的位置?*args放在最前面,那麼a,b永遠收不到引數 TypeError: func() missing 2 required keyword-only arguments: 'a' and 'b'
# def func(*args, a, b, sex='男'):
#     print(a, b)
#
#
# func(1, 2)


# args得到實參的前提,sex必須被覆蓋了。但是有時候不想sex變化掉,會實用預設的引數值,顯然這個是不合理的做法。
# 所以我們的args的引數放的位置是有問題的。
# def func(a, b, sex='男', *args):
#     print(a, b)
#     print(sex)
#     print(args)
#
#
# func(1, 2, 3, 4, 5, 6, 7)
"""
輸出的結果為:這裡的3會把sex的預設值給覆蓋掉
1 2
3
(4, 5, 6, 7)
"""

# 這個是存放args的正確方式
# 這個是正確的args引數放的位置
# def func(a, b, *args, sex='男'):
#     print(a, b)
#     print(sex)
#     print(args)
#
#
# func(1, 2, 3, 4, 5, 6, 7, sex='女')
'''
輸出的結果為:
1 2
女
(3, 4, 5, 6, 7)
'''


# **kwargs 位置?

# def func(a, b, *args, **kwargs, sex='男'):
#     print(a, b)
#     print(sex)
#     print(args)
#     print(kwargs)
#
#  def func(a, b, *args, **kwargs, sex='男'):                                #    ^
# SyntaxError: invalid syntax
# 
# func(1, 2, 3, 4, 5, 6, 7, sex='女', name='zhouqian', age=82)


# 下面是**kwargs的正確位置
# def func(a, b, *args, sex='男', **kwargs):
#     print(a, b)
#     print(sex)
#     print(args)
#     print(kwargs)
#
#
# func(1, 2, 3, 4, 5, 6, 7, sex='女', name='zhouqian', age=82)

'''
輸出的結果為:
1 2
女
(3, 4, 5, 6, 7)
{'name': 'zhouqian', 'age': 82}
'''

# 補充,作為了解。形參角度的第四個引數:僅限關鍵字引數
# 寫在args和kwargs之間,僅限關鍵字引數,必須傳值,並且只能用關鍵字引數傳值
# def func(a, b, *args, sex='男', c, **kwargs):
#     print(a, b)
#     print(sex)
#     print(args)
#     print(kwargs)
#     print(c)
#
#
# func(1, 2, 3, 4, 5, 6, 7, sex='女', name='zhouqian', age=82, c=666)
'''
輸出的結果為:
1 2
女
(3, 4, 5, 6, 7)
{'name': 'zhouqian', 'age': 82}
666

func() missing 1 required keyword-only argument: 'c'
'''


# 形參的最終的順序:位置引數,*args,預設引數,僅限關鍵字引數c,**kwargs

6)名稱空間、作用域

# 名稱空間,名稱空間
# 全域性名稱空間(全域性名稱空間)
# a = 1
# b = 2
# 
# 
# def func():
# 
#     print(666)
# 
# 
# c = 3
# func()

# 區域性名稱空間(臨時名稱空間)


# 內建名稱空間:print input len
# 內建名稱空間:python原始碼給你提供的一些內建的函式,print input
# print(666)


# python分為三個名稱空間,三個空間是完全獨立的,是完全沒有關係的
# 1.內建名稱空間(print input)
# 2.全域性名稱空間(當前py檔案)
# 3.區域性名稱空間(函式,函式執行時才開始)

# 三個空間的載入順序
# 永遠都是第一個載入內建名稱空間,然後載入的是全域性的名稱空間。
# 最後載入的是區域性的名稱空間
# 1.內建--》全域性--》區域性(函式執行的時候才載入)


# 取值順序(就近原則)  單向不可逆
# input = '太白金星'


# def func():
#     # input = 'alex'
#     print(input)  # alex  太白金星  <built-in function input>
#
#
# func()
# 就近原則:從區域性開始找。LEGB原則  單向不可逆
# (從區域性找時)區域性名稱空間 ---》全域性名稱空間 ---》內建名稱空間

# input = 'zhouqian'
#
#
# def func():
#     input = 'alex'
#
#
# print(input)  # zhouqian  <built-in function input>
# func()

三個空間的載入順序:內建--》全域性--》區域性(函式執行的時候才載入)

a = 1
b = 2
def func():
    f = 5
    print(f)
c = 3
func()

三個空間的取值順序:(從區域性找時)區域性名稱空間 ---》全域性名稱空間 ---》內建名稱空間

# 作用域:
# python中的作用域分為兩個作用域:
# 1.全域性作用域 :內建名稱空間 全域性名稱空間

# 2.區域性作用域:區域性名稱空間

# 區域性作用域可以獲得到全域性作用域中使用變數,重新建立變數,操作變數的賦值,可以用來對變數進行操作,改變變數的值。
# 區域性作用域不能改變全域性作用域的變數。當python直譯器讀取到區域性作用域時,發現你對一個變數進行了修改的操作,直譯器會認為你在區域性作用域已經定義過這個區域性變數,他就從區域性找這個區域性變數,報錯了。# UnboundLocalError: local variable 'count' referenced before assignment
# 但是全域性作用域不可以獲得區域性作用域,不能操作區域性作用域的變數,不能操作區域性作用域的變數值。(單向不可逆)
data = '週五'


def func():
    a = 666
    print(data)  # 週五


print(a) # NameError: name 'a' is not defined
func()




#使用可以,不能改變

def func():
    count = 1
    def inner():
        print(count)  # 1
    inner()
func()


def func():
    count = 1
    def inner():
        count+=1 # UnboundLocalError: local variable 'count' referenced before assignment
        print(count)  # 1
    inner()
func()

7)高階函式

8)內建函式:globals()和locals()

a = 1
b = 2


def func():
    name = 'alex'
    age = 73
    print(globals())  # 返回的是字典,字典裡面的鍵值對是全域性作用域的所有內容
    print(locals())  # 返回的是字典,字典裡面的鍵值對是當前作用域的所有內容


# print(globals()) # 返回的是字典,字典裡面的鍵值對是全域性作用域的所有內容
# print(locals())# 返回的是字典,字典裡面的鍵值對是當前作用域的所有內容

func()
'''
{'__name__': '__main__', '__doc__': '\n本檔案:研究內建函式:globals locals\n\n', '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000018822BBEA88>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'D:/Program Files (x86)/DjangoProjects/basic/day10/05 globals locals.py', '__cached__': None, 'a': 1, 'b': 2, 'func': <function func at 0x0000018822C0ED38>}
{'name': 'alex', 'age': 73}
'''

相關文章