Python中的函式(轉自linhaifeng)
阿新 • • 發佈:2019-02-11
一 函式知識體系
1 什麼是函式? 2 為什麼要用函式? 3 函式的分類:內建函式與自定義函式 4 如何自定義函式 語法 定義有引數函式,及有參函式的應用場景 定義無引數函式,及無參函式的應用場景 定義空函式,及空函式的應用場景 5 呼叫函式 - 如何呼叫函式 - 函式的返回值 - 函式引數的應用:形參和實參,位置引數,關鍵字引數,預設引數,*args,**kwargs 6 高階函式(函式物件) 7 函式巢狀 8 作用域與名稱空間 9 裝飾器 10 迭代器與生成器及協程函式 11 三元運算,列表解析、生成器表示式 12 函式的遞迴呼叫 13 內建函式 14 面向過程程式設計與函數語言程式設計
二 函式基礎
引子
一 為何要用函式之不用函式的問題
1、程式碼的組織結構不清晰,可讀性差
2、遇到重複的功能只能重複編寫實現程式碼,程式碼冗餘
3、功能需要擴充套件時,需要找出所有實現該功能的地方修改之,無法統一管理且維護難度極大
二 函式是什麼
針對二中的問題,想象生活中的例子,修理工需要實現準備好工具箱裡面放好錘子,扳手,鉗子等工具,然後遇到錘釘子的場景,拿來錘子用就可以,而無需臨時再製造一把錘子。 修理工===>程式設計師 具備某一功能的工具===>函式 要想使用工具,需要事先準備好,然後拿來就用且可以重複使用 要想用函式,需要先定義,再使用
三 函式分類
#1、內建函式
為了方便我們的開發,針對一些簡單的功能,python直譯器已經為我們定義好了的
函式即內建函式。對於內建函式,我們可以拿來就用而無需事先定義,如len(),
sum(),max()
ps:我們將會在最後詳細介紹常用的內建函式。
#2、自定義函式
很明顯內建函式所能提供的功能是有限的,這就需要我們自己根據需求,事先定製
好我們自己的函式來實現某種功能,以後,在遇到應用場景時,呼叫自定義的函式
即可。例如
二 定義函式
一 如何自定義函式?
#語法
def 函式名(引數1,引數2,引數3,...):
'''註釋'''
函式體
return 返回的值
#函式名要能反映其意義
def auth(user:str,password:str)->int:
'''
auth function
:param user: 使用者名稱
:param password: 密碼
:return: 認證結果
'''
if user == 'egon' and password == '123':
return 1
# print(auth.__annotations__) #{'user': <class 'str'>, 'password': <class 'str'>, 'return': <class 'int'>}
user=input('使用者名稱>>: ').strip()
pwd=input('密碼>>: ').strip()
res=auth(user,pwd)
print(res)
二 函式使用的原則:先定義,再呼叫
函式即“變數”,“變數”必須先定義後引用。未定義而直接引用函式,就相當於在引用一個不存在的變數名
#測試一
def foo():
print('from foo')
bar()
foo() #報錯
#測試二
def bar():
print('from bar')
def foo():
print('from foo')
bar()
foo() #正常
#測試三
def foo():
print('from foo')
bar()
def bar():
print('from bar')
foo() #會報錯嗎?
#結論:函式的使用,必須遵循原則:先定義,後呼叫
#我們在使用函式時,一定要明確地區分定義階段和呼叫階段
#定義階段
def foo():
print('from foo')
bar()
def bar():
print('from bar')
#呼叫階段
foo()
三 函式在定義階段都幹了哪些事?
#只檢測語法,不執行程式碼
也就說,語法錯誤在函式定義階段就會檢測出來,而程式碼的邏輯錯誤只有在執行時
才會知道
四 定義函式的三種形式
#1、無參:應用場景僅僅只是執行一些操作,比如與使用者互動,列印
#2、有參:需要根據外部傳進來的引數,才能執行相應的邏輯,比如統計長度,求最
大值最小值
#3、空函式:設計程式碼結構
無參、有參
#定義階段
def tell_tag(tag,n): #有引數
print(tag*n)
def tell_msg(): #無引數
print('hello world')
#呼叫階段
tell_tag('*',12)
tell_msg()
tell_tag('*',12)
'''
************
hello world
************
'''
#結論:
#1、定義時無參,意味著呼叫時也無需傳入引數
#2、定義時有參,意味著呼叫時則必須傳入引數
空函式
def auth(user,password):
'''
auth function
:param user: 使用者名稱
:param password: 密碼
:return: 認證結果
'''
pass
def get(filename):
'''
:param filename:
:return:
'''
pass
def put(filename):
'''
:param filename:
:return:
'''
def ls(dirname):
'''
:param dirname:
:return:
'''
pass
#程式的體系結構立見
三 呼叫函式
一 呼叫函式
函式的呼叫:函式名加括號
1 先找到名字
2 根據名字呼叫程式碼
二 函式返回值
無return->None
return 1個值->返回1個值
return 逗號分隔多個值->元組
什麼時候該有返回值?
呼叫函式,經過一系列的操作,最後要拿到一個明確的結果,則必須要有返回值
通常有參函式需要有返回值,輸入引數,經過計算,得到一個最終的結果
什麼時候不需要有返回值?
呼叫函式,僅僅只是執行一系列的操作,最後不需要得到什麼結果,則無需有返回值
通常無參函式不需要有返回值
三 函式呼叫的三種形式
1 語句形式:foo()
2 表示式形式:3*len('hello')
3 當中另外一個函式的引數:range(len('hello'))
四 函式的引數
一 形參與實參
#形參即變數名,實參即變數值,函式呼叫時,將值繫結到變數名上,函式呼叫結束,
解除繫結
二 具體應用
乃重點知識!!!
#1、位置引數:按照從左到右的順序定義的引數
位置形參:必選引數
位置實參:按照位置給形參傳值
#2、關鍵字引數:按照key=value的形式定義的實參
無需按照位置為形參傳值
注意的問題:
1. 關鍵字實參必須在位置實參右面
2. 對同一個形參不能重複傳值
#3、預設引數:形參在定義時就已經為其賦值
可以傳值也可以不傳值,經常需要變得引數定義成位置形參,變化較小的引數定義成預設引數(形參)
注意的問題:
1. 只在定義時賦值一次
2. 預設引數的定義應該在位置形參右面
3. 預設引數通常應該定義成不可變型別
#4、可變長引數:
可變長指的是實參值的個數不固定
而實參有按位置和按關鍵字兩種形式定義,針對這兩種形式的可變長,形參對應有兩種解決方案來完整地存放它們,分別是*args,**kwargs
===========*args===========
def foo(x,y,*args):
print(x,y)
print(args)
foo(1,2,3,4,5)
def foo(x,y,*args):
print(x,y)
print(args)
foo(1,2,*[3,4,5])
def foo(x,y,z):
print(x,y,z)
foo(*[1,2,3])
===========**kwargs===========
def foo(x,y,**kwargs):
print(x,y)
print(kwargs)
foo(1,y=2,a=1,b=2,c=3)
def foo(x,y,**kwargs):
print(x,y)
print(kwargs)
foo(1,y=2,**{'a':1,'b':2,'c':3})
def foo(x,y,z):
print(x,y,z)
foo(**{'z':1,'x':2,'y':3})
===========*args+**kwargs===========
def foo(x,y):
print(x,y)
def wrapper(*args,**kwargs):
print('====>')
foo(*args,**kwargs)
#5、命名關鍵字引數:*後定義的引數,必須被傳值(有預設值的除外),且必須按照關鍵字實參的形式傳遞
可以保證,傳入的引數中一定包含某些關鍵字
def foo(x,y,*args,a=1,b,**kwargs):
print(x,y)
print(args)
print(a)
print(b)
print(kwargs)
foo(1,2,3,4,5,b=3,c=4,d=5)
結果:
2
(3, 4, 5)
3
{'c': 4, 'd': 5}
此乃重點知識!!!
三 函式物件、函式巢狀、名稱空間與作用域、裝飾器
一 函式物件
一 函式是第一類物件,即函式可以當作資料傳遞
#1 可以被引用
#2 可以當作引數傳遞
#3 返回值可以是函式
#3 可以當作容器型別的元素
二 利用該特性,優雅的取代多分支的if
def foo():
print('foo')
def bar():
print('bar')
dic={
'foo':foo,
'bar':bar,
}
while True:
choice=input('>>: ').strip()
if choice in dic:
dic[choice]()
二 函式巢狀
一 函式的巢狀呼叫
def max(x,y):
return x if x > y else y
def max4(a,b,c,d):
res1=max(a,b)
res2=max(res1,c)
res3=max(res2,d)
return res3
print(max4(1,2,3,4))
二 函式的巢狀定義
def f1():
def f2():
def f3():
print('from f3')
f3()
f2()
f1()
f3() #報錯,為何?請看下一小節
三 名稱空間與作用域
一 什麼是名稱空間?
#名稱空間:存放名字的地方,三種名稱空間,(之前遺留的問題x=1,1存放於記憶體
中,那名字x存放在哪裡呢?名稱空間正是存放名字x與1繫結關係的地方)
二 名稱空間的載入順序
python test.py
#1、python直譯器先啟動,因而首先載入的是:內建名稱空間
#2、執行test.py檔案,然後以檔案為基礎,載入全域性名稱空間
#3、在執行檔案的過程中如果呼叫函式,則臨時產生區域性名稱空間
三 名字的查詢順序
區域性名稱空間--->全域性名稱空間--->內建名稱空間
#需要注意的是:在全域性無法檢視區域性的,在區域性可以檢視全域性的,如下示例
# max=1
def f1():
# max=2
def f2():
# max=3
print(max)
f2()
f1()
print(max)
四 作用域
#1、作用域即範圍
- 全域性範圍(內建名稱空間與全域性名稱空間屬於該範圍):全域性存活,全域性有效
- 區域性範圍(區域性名稱空間屬於該範圍):臨時存活,區域性有效
#2、作用域關係是在函式定義階段就已經固定的,與函式的呼叫位置無關,如下
x=1
def f1():
def f2():
print(x)
return f2
x=100
def f3(func):
x=2
func()
x=10000
f3(f1())
#3、檢視作用域:globals(),locals()
LEGB 代表名字查詢順序: locals -> enclosing function -> globals -> __builtins__
locals 是函式內的名字空間,包括區域性變數和形參
enclosing 外部巢狀函式的名字空間(閉包中常見)
globals 全域性變數,函式定義所在模組的名字空間
builtins 內建模組的名字空間
五 global與nonlocal關鍵字
四 閉包函式
一 什麼是閉包?
#內部函式包含對外部作用域而非全域性作用域的引用
#提示:之前我們都是通過引數將外部的值傳給函式,閉包提供了另外一種思路,包起來嘍,包起呦,包起來哇
def counter():
n=0
def incr():
nonlocal n
x=n
n+=1
return x
return incr
c=counter()
print(c())
print(c())
print(c())
print(c.__closure__[0].cell_contents) #檢視閉包的元素
二 閉包的意義與應用
#閉包的意義:返回的函式物件,不僅僅是一個函式物件,在該函式外還包裹了一層作用域,這使得,該函式無論在何處呼叫,優先使用自己外層包裹的作用域
#應用領域:延遲計算(原來我們是傳參,現在我們是包起來)
from urllib.request import urlopen
def index(url):
def get():
return urlopen(url).read()
return get
baidu=index('http://www.baidu.com')
print(baidu().decode('utf-8'))
五 裝飾器
裝飾器就是閉包函式的一種應用場景
一 為何要用裝飾器
#開放封閉原則:對修改封閉,對擴充套件開放
二 什麼是裝飾器
裝飾器他人的器具,本身可以是任意可呼叫物件,被裝飾者也可以是任意可呼叫物件。
強調裝飾器的原則:1 不修改被裝飾物件的原始碼 2 不修改被裝飾物件的呼叫方式
裝飾器的目標:在遵循1和2的前提下,為被裝飾物件新增上新功能
三 裝飾器的使用
無參裝飾器
import time
def timmer(func):
def wrapper(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs)
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
return res
return wrapper
@timmer
def foo():
time.sleep(3)
print('from foo')
foo()
無參裝飾器
有參裝飾器
def auth(driver='file'):
def auth2(func):
def wrapper(*args,**kwargs):
name=input("user: ")
pwd=input("pwd: ")
if driver == 'file':
if name == 'egon' and pwd == '123':
print('login successful')
res=func(*args,**kwargs)
return res
elif driver == 'ldap':
print('ldap')
return wrapper
return auth2
@auth(driver='file')
def foo(name):
print(name)
foo('egon')
有參裝飾器
四 裝飾器語法
被裝飾函式的正上方,單獨一行
@deco1
@deco2
@deco3
def foo():
pass
foo=deco1(deco2(deco3(foo)))
五 裝飾器補充:wraps
from functools import wraps
def deco(func):
@wraps(func) #加在最內層函式正上方
def wrapper(*args,**kwargs):
return func(*args,**kwargs)
return wrapper
@deco
def index():
'''哈哈哈哈'''
print('from index')
print(index.__doc__)
四 迭代器、生成器、面向過程程式設計
一 迭代器
一 迭代的概念
#迭代器即迭代的工具,那什麼是迭代呢?
#迭代是一個重複的過程,每次重複即一次迭代,並且每次迭代的結果都是下一次迭代的初始值
while True: #只是單純地重複,因而不是迭代
print('===>')
l=[1,2,3]
count=0
while count < len(l): #迭代
print(l[count])
count+=1
二 為何要有迭代器?什麼是可迭代物件?什麼是迭代器物件?
#1、為何要有迭代器?
對於序列型別:字串、列表、元組,我們可以使用索引的方式迭代取出其包含的元素。但對於字典、集合、檔案等型別是沒有索引的,若還想取出其內部包含的元素,則必須找出一種不依賴於索引的迭代方式,這就是迭代器
#2、什麼是可迭代物件?
可迭代物件指的是內建有__iter__方法的物件,即obj.__iter__,如下
'hello'.__iter__
(1,2,3).__iter__
[1,2,3].__iter__
{'a':1}.__iter__
{'a','b'}.__iter__
open('a.txt').__iter__
#3、什麼是迭代器物件?
可迭代物件執行obj.__iter__()得到的結果就是迭代器物件
而迭代器物件指的是即內建有__iter__又內建有__next__方法的物件
檔案型別是迭代器物件
open('a.txt').__iter__()
open('a.txt').__next__()
#4、注意:
迭代器物件一定是可迭代物件,而可迭代物件不一定是迭代器物件
三 迭代器物件的使用
dic={'a':1,'b':2,'c':3}
iter_dic=dic.__iter__() #得到迭代器物件,迭代器物件即有__iter__又有__next__,但是:迭代器.__iter__()得到的仍然是迭代器本身
iter_dic.__iter__() is iter_dic #True
print(iter_dic.__next__()) #等同於next(iter_dic)
print(iter_dic.__next__()) #等同於next(iter_dic)
print(iter_dic.__next__()) #等同於next(iter_dic)
# print(iter_dic.__next__()) #丟擲異常StopIteration,或者說結束標誌
#有了迭代器,我們就可以不依賴索引迭代取值了
iter_dic=dic.__iter__()
while 1:
try:
k=next(iter_dic)
print(dic[k])
except StopIteration:
break
#這麼寫太醜陋了,需要我們自己捕捉異常,控制next,python這麼牛逼,能不能幫我解決呢?能,請看for迴圈
四 for迴圈
#基於for迴圈,我們可以完全不再依賴索引去取值了
dic={'a':1,'b':2,'c':3}
for k in dic:
print(dic[k])
#for迴圈的工作原理
#1:執行in後物件的dic.__iter__()方法,得到一個迭代器物件iter_dic
#2: 執行next(iter_dic),將得到的值賦值給k,然後執行迴圈體程式碼
#3: 重複過程2,直到捕捉到異常StopIteration,結束迴圈
五 迭代器的優缺點
#優點:
- 提供一種統一的、不依賴於索引的迭代方式
- 惰性計算,節省記憶體
#缺點:
- 無法獲取長度(只有在next完畢才知道到底有幾個值)
- 一次性的,只能往後走,不能往前退
二 生成器
一 什麼是生成器
#只要函式內部包含有yield關鍵字,那麼函式名()的到的結果就是生成器,並且不會執行函式內部程式碼
def func():
print('====>first')
yield 1
print('====>second')
yield 2
print('====>third')
yield 3
print('====>end')
g=func()
print(g) #<generator object func at 0x0000000002184360>
二 生成器就是迭代器
g.__iter__
g.__next__
#2、所以生成器就是迭代器,因此可以這麼取值
res=next(g)
print(res)
三 協程函式
#yield關鍵字的另外一種使用形式:表示式形式的yield
def eater(name):
print('%s 準備開始吃飯啦' %name)
food_list=[]
while True:
food=yield food_list
print('%s 吃了 %s' % (name,food))
food_list.append(food)
g=eater('egon')
g.send(None) #對於表示式形式的yield,在使用時,第一次必須傳None,g.send(None)等同於next(g)
g.send('蒸羊羔')
g.send('蒸鹿茸')
g.send('蒸熊掌')
g.send('燒素鴨')
g.close()
g.send('燒素鵝')
g.send('燒鹿尾')
三 面向過程程式設計
#1、首先強調:面向過程程式設計絕對不是用函式程式設計這麼簡單,面向過程是一種程式設計思路、思想,而程式設計思路是不依賴於具體的語言或語法的。言外之意是即使我們不依賴於函式,也可以基於面向過程的思想編寫程式
#2、定義
面向過程的核心是過程二字,過程指的是解決問題的步驟,即先幹什麼再幹什麼
基於面向過程設計程式就好比在設計一條流水線,是一種機械式的思維方式
#3、優點:複雜的問題流程化,進而簡單化
#4、缺點:可擴充套件性差,修改流水線的任意一個階段,都會牽一髮而動全身
#5、應用:擴充套件性要求不高的場景,典型案例如linux核心,git,httpd
#6、舉例
流水線1:
使用者輸入使用者名稱、密碼--->使用者驗證--->歡迎介面
流水線2:
使用者輸入sql--->sql解析--->執行功能