函式的定義和呼叫
定義:def 關鍵詞開頭,空格之后接函式名稱和圓括號(),最后還有一個英文冒號":",
函式名:在Python中函式即變數,所以函式名也同樣遵循變數的命名約束,數字字母下劃線組成,不能以數字開頭且應具有描述函式功能的作用,
括號:是必須加的,先別問為啥要有括號,總之加上括號就對了!
注釋:每一個函式都應該對功能和引數進行相應的說明,應該寫在函式下面第一行,以增強代碼的可讀性,
呼叫:就是函式名() 函式名加括號 !不加不執行
來個函式
>>> def bar():
... print("hello function!\n")
...
>>> bar # 不加括號給的是這個函式的地址,
<function bar at 0x10da670d0>
>>> bar() # 加了括號才會被執行
hello function!
我們現在想讓一個函式完成兩個數的加和操作,即我們自己來實作sum()這個內建函式的功能,
def mysum(a,b):
sum = a+b
result = mysum(1,2)
print(result)
結果為:None
怎么樣才能讓他能像sum()一樣呢? 現在的函式功能已經完美的實作了,但是沒人知道這個函式已經把任務完成了! 怎么證明自己呢,函式要把執行的結果回傳讓大家看到!
在函式的最后加上 return關鍵字
def mysum(a,b):
sum = a+b
return sum
result = mysum(1,2)
print(result)
結果為:3
詳細討論這個回傳值
1、沒有return關鍵字
這樣的情況在Python中是被允許的,默認的Python回傳了 None
2、有return但是后面沒有值
def bar():
print(“I’m bar!”)
return
print(bar)
結果為:None
3、有return有回傳值,當然上述例子mysum就是例子,顯然是被允許的,并且這個回傳值能被一個變數名參考到,
4、有return 有多個回傳值
def mysum(a,b):
sum = a+b
return a,b,sum
result = mysum(1,2)
print(result)
結果為:(1, 2, 3)
當回傳值為多個值的時候Python會把幾個物件封裝成一個元組,同時我們可以用多個變臉分別一一對應的取到每一個回傳值,也可以用一個變數取到這個元組
注意 在Python中,將用逗號 "," 分割的連續的幾個值,認為是一個元組
>>> 1,2,3
(1, 2, 3)
序列解壓
>>> a,_,_,_,b = [i for i in range(5)]
>>> a
0
>>> b
4
>>> a,*_,b,c = 'unpack string'
>>> a
'u'
>>> b
'n'
>>> c
'g'
>>> _
['n', 'p', 'a', 'c', 'k', ' ', 's', 't', 'r', 'i']
>>> type(_)
<class 'list'>
>>> a,_,c = {1:'a',2:'b',3:'c'}
>>> a
1
>>> c
3
>>> *_,end = (1,2,3,4,5,6)
>>> end
6
return的作用:
當函式被呼叫時,Python解釋器會由上到下的執行函式體中的陳述句,當執行到 return關鍵字時即終止函式并回傳結果:之后的代碼不會被執行!
def mysay():
print("hello first!")
print('我是華麗的分割線'.center(30, "*"))
return
print("hello second!")
mysay()
結果為:
hello first!
***********我是華麗的分割線***********
由此可見,return 什么都不寫跟 直接不寫return 還是有區別的,return可以指明函式在哪里結束,如果沒有return 也沒用中途報錯 函式將執行完整個函式體里的代碼,
函式引數
在兩數字之和的時候我們看到在函式名后的括號里有兩個引數a,b,這兩個數稱為函式的形參,當函式呼叫時傳入具體的引數稱為函式執行時候的實參,
引數可以有多個,定義和呼叫時,引數都需要用逗號分割,
函式的引數分為:位置引數,關鍵字引數,默認引數,動態引數
注意:函式傳參必須是一個不可變物件的參考或物件本身,因為Python的傳參遵循了主流面向物件的傳參方式:形參實參共享物件,
就實參而言
位置傳參
這種方式,必須嚴格的按照形參的位置來傳遞實參,不然,結果將不是我們期望的樣子,引數多了少了都會報錯
def func(arg1,arg2,arg3)
pass
func('name','age','salary') # 此時 :arg1 = 'name' arg2 = ’age' arg3 = ’salary'
關鍵字傳引數
這種方式,絲毫不需要在意位置,并且有默認值,即使我們不傳入實參,也不會報錯,
def func(arg1,arg2,arg3)
pass
func(arg3=’name',arg2 = ’age', arg1 = ’salary') # 此時 :arg1 = 'salary’ arg2 = ’age' arg3 = ’name’
混用
在兩種方式混用時,必須遵循,位置傳入的引數在前,關鍵字傳入的引數在后,并且,一個形參只能被傳入唯一的一個值,
def func(arg1,arg2,arg3)
pass
func('name',arg3 = ’age',arg2 = ’salary') # 此時 :arg1 = 'name' arg2 = ’salary’ arg3 = ’age’
就形參而言
位置引數:位置引數必須被傳入單一的對應值,無論用何種方式傳參,
關鍵字引數:形參的關鍵字引數,也就是形參的默認值,多數時候不需要修改的值我們多設為默認值,
def style(id, color="blue"):
"""這是一個樣式控制函式,大多數時候背景顏色為藍色
此時 將color 設定為 藍色
"""
return id, color
print(style(3,'red'))
print(style(3))
注意:盡可能的避開可變型別稱為默認值的情況(避開容器型別),因為它們是物件的參考的集合,當引數被傳入函式時候,所做的一切操作都不會修改容器本身,而是修改了其中的物件,造成了變化的累積效應,
def init_data(name,lst =[]):
lst.append(name)
return lst
print(init_data('monkey'))
print(init_data('JIAJIA'))
['monkey']
['monkey', 'JIAJIA']
# 不是想象中的初始化兩個串列,而是在同一個表上累積
形參和實參是共享物件的,lst 是一個可變物件的參考,函式每次呼叫時默認傳的是同一個可變的物件,是同一個地址,每次修改的都是相同的可變物件,
動態引數
def bar(id,age=18,*args,**kwargs):
print(id,age,args,kwargs)
bar(1608,'誰收留我1','誰收留我2','看看誰要我3',sex='famale')
# 1608
# 誰收留我1
# ('誰收留我2', '看看誰要我3')
# {'sex': 'famale'}
位置引數必須要按照位置傳參,多余的位置引數會被封裝成元組的形式保存在args中,多余的關鍵字引數會被打包成字典封裝在kwargs中,
函式嵌套
函式的嵌套呼叫就是在函式中呼叫另一個函式,
def func():
print('func')
def bar():
print('The bar')
func()
bar() # 在bar中呼叫func()
定義
def func():
funcname = 'func'
print(funcname)
def bar():
funcname = 'bar'
print(funcname)
def foo():
funcname = 'foo'
print(funcname)
foo()
bar()
func()
nonlocal 在嵌套函式中使用 nonloacl 關鍵字 宣告變數為上層函式變數,而非本層函式,
nonlocal的使用規則:
1、他必須在某函式的內嵌函式中使用,
2、在他宣告的變數之前當前函式中不能有同名的變數存在,
注意 : 無法系結到全域變數,只能是區域變數,
def outfunc():
name = 'outfunc'
def infunc():
nonlocal name
name = 'infunc'
print(name)
infunc()
print(name)
outfunc()
作用鏈域
name = 'error! '
def outfunc():
print('Outfunc name ="{}"'.format(name))
def func0():
name = 'monkey'
def func1():
print('In func1 name ="{}"'.format(name))
def func2():
outfunc()
print('In func2 name ="{}"'.format(name))
func2()
func1()
outfunc()
func0()
# In func1 name ="monkey"
# Outfunc name ="error! "
# In func2 name ="monkey"
# Outfunc name ="error! "
注意
函式會在執行前檢查函式體中變數的定義,如果在本層沒有找到,就往它的外一層找,還沒找到就再往外層,直到找到全域,還沒找到就報錯,找到了就使用,
高階函式
定義:函式的引數或回傳值是函式的函式,就稱為高階函式(當回傳值為函式本身,則稱為函式的遞回),
函式的引數是另一個函式
def bar():
print(" This is bar!")
def foo(func):
print("This is foo:")
func()
foo(bar)
將 bar 作為函式 foo 的引數傳入,可以在foo中執行bar 即 foo 中的func,
函式的回傳值是另一個函式
def foo():
print("This is foo:")
def foo_inner():
print("This is foo_inner")
return foo_inner
ret = foo()
ret()
將 foo_inner 作為回傳值傳出,在函式外執行,
函式閉包
嵌套函式中,內部函式呼叫外部函式的變數
def outer():
name = 'outer'
def inner():
print(name)
print(inner.__closure__)
outer()
# (<cell at 0x100f28408: str object at 0x100e51538>,)
用closure方法來判斷是否為閉包
以cell 開頭,這就是一個閉包 回傳的是一個 None 則不是閉包
0x100e51538 是inner的地址
str是使用外層變數的型別 后面是其記憶體地址
閉包的正確姿勢
def outer():
name = 'outer'
def inner():
print(name)
return inner
func = outer()
func()
這樣子來,name 會被長久的保存下來,因為func接受了outer內部函式的地址,而內部函式使用了outer下的變數 name 所以,這個變數得以在記憶體中長久的保留,
如果在一個內部函式里對在外部作用域(但不是在全域作用域)的變數進行參考,但不在全域作用域里,則這個內部函式就是一個閉包,
實際上,閉包的用處/優點有兩條:
從函式外可以讀取函式內部的變數,或直接執行內層函式
讓這些變數的值始終保持在記憶體中(也可以理解為保留當前運行環境)
裝飾器
裝飾器是什么?完成怎么樣的功能?
裝飾器要求再不修改函式代碼,不修改函式呼叫方式的前提下,為函式添加新的功能,
裝飾器的本質是函式
在Python中函式和變數其實本質上是一樣的,變數可以指向一個函式物件,函式即變數!
初級版本裝飾器
定義一個被裝飾的函式:
def func():
print("被裝飾的函式")
版本一
我們想到定義一個 decrator_v1 函式 接受被裝飾的函式,加上新功能后回傳這個函式的地址,然后再將func變數指向 decrator_v1 函式,引數為 func 貌似大功告成,
def decrator_v1(foo):
print('這是新功能')
foo()
return foo
func = decrator_v1(func)
func()
# 這是新功能
# 被裝飾的函式
# 被裝飾的函式
哎呀~ 為什么 跟預期的結果不一樣??? 從頭到尾的捋了一遍!臥槽 func 函式被執行了兩邊,并且 第二遍壓根就沒鳥我們的新功能!
1 當執行func = decrator_v1(func)的時候,呼叫了decratoe_v1這時執行了我們想要的結果,
2 但是當回傳foo 被變數func接受時,我們執行func,就執行了foo,此時的foo就是原來我們定義時候的func函式,因此,出現了兩次執行,第二次只執行了func,
3 我們希望把 新功能和func本身封裝在一起,要執行一起執行,就不會出現這樣扯淡的情況了很顯然,很自然我們想到了函式的閉包,封裝到一個函式 那么 來吧 讓我們封裝一下,
版本二
def add_way(func): #1
def wrapper(): #3
print('這是新功能') #6
func() #7
return wrapper #4
func = add_way(func) #2
func()
# 這是新功能
# 被裝飾的函式
完美~ 終于完成了使命~ !
解釋一下代碼:
相對于版本一來說,定義的wrapper函式就是用來解決版本一中呼叫就立即執行add_way()的問題的,
現在基本的滿足了功能上的需求,沒有改變呼叫方式,也沒有改變原函式的代碼,Python提供了一個 語法糖 "@" 來幫我們完成func = add_way(func)這件事情!
語法糖版本
def add_way(func):
def wrapper():
print('這是新功能')
func()
return wrapper
@add_way
def func():
print("語法糖版本:被裝飾函式")
func()
到現在為止,裝飾器就基本上成型了~!
回顧一下我們都做了什么:
1、利用高階函式把 被裝飾函式當成引數出入裝飾器物件,然后 (函式的嵌套)用函式封裝 新增方法(功能)之后把 封裝后的物件
2、作為裝飾器物件(函式)的回傳值,然后將 被裝飾函式名作為裝飾器物件的參考,這樣 就實作了裝飾器
不改變函式的代碼
不改變方法的呼叫方式
實作新增功能
再來打磨一下:
被裝飾函式屬性變化,依靠某些屬性作業的模塊可能無法作業
print('裝飾之后的函式檔案',func.__doc__)
print('裝飾之后的函式名',func.__name__)
print('裝飾之后的函式哈希值',func.__hash__)
結果與被裝飾之前是不一樣的~ 那么依靠__name__ 屬性作業的模塊就無法正常作業了~
被裝飾函式如何傳參?
終極版本裝飾器
打磨后的裝飾器:
注意
1、from functions import wraps 用wraps裝飾器 來 裝飾 傳入我們自定義裝飾器要裝飾的函式,即接收的那個函式,
2、wrapper 要接受func(被裝飾函式的)的所有引數
3、wrapper函式要回傳func(被裝飾函式的)回傳值,
#!/usr/bin/env python3
#_*_ coding: utf-8 _*_
__author__ = "monkey"
from functools import wraps
# 真正完備的裝飾器
# 先來看一下解釋器對wraps函式的注釋
"""Decorator factory to apply update_wrapper() to a wrapper function
Returns a decorator that invokes update_wrapper() with the decorated
function as the wrapper argument and the arguments to wraps() as the
remaining arguments. Default arguments are as for update_wrapper().
This is a convenience function to simplify applying partial() to
update_wrapper()."""
def func( *args,**kwargs):
'''
:param args:接受多余的位置引數
:param kwargs: 接受多余的關鍵字引數
:return: 回傳拿到的所有引數
'''
print('我是func')
return args,kwargs
print('裝飾之前的函式檔案',func.__doc__)
print('裝飾之前的函式名',func.__name__)
print('裝飾之前的函式哈希值',func.__hash__)
def decrator_end(func): #1
'''
:param func: 接受被裝飾函式物件,以便與在裝飾器內部執行
:return: 一個封裝新功能的物件的地址
'''
@wraps(func) #在這里會把wrap 函式的性質完完整整的轉變成func函式的樣子
def wrapper(*args,**kwargs): #3
'''
wrapper 因為這里的wrapper要接受func傳入的所有引數
:param args: 我的含義大家都知道
:param kwargs: 我跟上面一樣
:return: 被裝飾函式的執行結果
'''
print('這是新功能') #6
ret = func(*args,**kwargs) #7
return ret
return wrapper #4
func = decrator_end(func) #2
x = func('hook','monkey',name='monkey')
print(x)
print('裝飾之后的函式檔案',func.__doc__)
print('裝飾之后的函式名',func.__name__)
print('裝飾之后的函式哈希值',func.__hash__)
結果:
裝飾之前的函式檔案
:param args:接受多余的位置引數
:param kwargs: 接受多余的關鍵字引數
:return: 回傳拿到的所有引數
裝飾之前的函式名 func
裝飾之前的函式哈希值 <method-wrapper '__hash__' of function object at 0x10969ce18>
這是新功能
我是func
(('hook', 'monkey'), {'name': 'monkey'})
裝飾之后的函式檔案
:param args:接受多余的位置引數
:param kwargs: 接受多余的關鍵字引數
:return: 回傳拿到的所有引數
裝飾之后的函式名 func
裝飾之后的函式哈希值 <method-wrapper '__hash__' of function object at 0x1098bf7b8>
注意觀察 裝飾之后的屬性變化,雖然貌似完全是被裝飾函式本身,但是通過__hash__方法我們知道那并不是原來的__hash__事情總不是那么盡善盡美,達到需求即可!這樣就可以了,真的要修改__hash__ 可以自己在函式中 重寫 類的__hash__方法即使這樣 他仍然不是原來的函式,記憶體不一樣,怎么改 都只是很像 改變不了他們是兩個物件的事實,但是對于使用而言,對用戶 或 呼叫者透明,封裝之后他們是不是一個物件這個問題沒人會去關注!所以不必要非苛求完美!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/119158.html
標籤:Python
上一篇:單例模式
下一篇:Python集合操作 筆記
