程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

Python學習3(函數、裝飾器)

編輯:Python

千峰教育b站的課程

函數

函數定義

參數可有可無

def 函數名([參數...]):
代碼

直接打印函數名,會看到函數所在的內存地址,函數名相當於一個變量,指向了這個內存地址

def gen():
print(1)
print(gen) # <function gen at 0x000002B1092DC268>

調用,函數名()

調用函數時參數的順序

>>> def test(a,b):
... print(a,b)
...
>>> test(1,2) # 位置參數
1 2
>>> test(b=1,a=2) # 關鍵字參數
2 1
>>>
>>> test(b=1,2) # 關鍵字參數寫在位置參數之前會導致出錯
File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument

怎麼保證傳入的參數符合類型要求
isinstance(變量, 類型)

def get_sum(a,b):
if isinstance(a, int) and isinstance(b, int):
print(a + b)
else:
print('type error')
get_sum(1.2,2)

參數默認值

在定義參數的時候可以給參數賦默認值,這樣在調用的時候可以不傳遞這個參數
且默認值參數必須在普通參數的後面(其實也很好理解,因為調用函數傳遞的時候是按位置傳遞的)

# def get_sum(a, c = 100,b): # 報錯
def get_sum(a,b, c = 100): # c給定了默認值100
if isinstance(a, int) and isinstance(b, int):
print(a + b + c)
else:
print('type error')
get_sum(1,2) #103

關鍵字參數

調用的時候明確使用參數名賦值,因為默認是按位置順序賦值的,為了防止因為位置傳遞發生錯誤,可以使用關鍵字參數賦值
例如下面例子中,如果c想用默認值,而d要傳遞參數,就需要用到關鍵字參數

def get_sum(a,b, c = 100,d=200):
if isinstance(a, int) and isinstance(b, int):
print(a + b + c + d)
else:
print('type error')
get_sum(1,2, d = 5)

可變參數

*args 接收單獨的變量
**kwargs

*args

考慮場景:如果想用一個函數求兩個數、三個數…多個數的和
可以使用可變參數,這樣將傳入的多個參數形成一個元組,傳遞給函數
也可以什麼都不傳,相當於一個空的元組
這種自動形成元組的行為成為裝包

def get_sum(*args):
print(args)
get_sum(1,2) # (1, 2) 輸出的是元組
get_sum(1,2,3,4,5) # (1, 2, 3, 4, 5)
get_sum() # ()

這裡是列表

a,*b,c,d = 1,2,3,4,5,6
print(a,b,c,d)
# 1 [2, 3, 4] 5 6

如果有兩個可變參數,報錯

*a,*b,c,d = 1,2,3,4,5,6
# SyntaxError: two starred expressions in assignment

拆包

a,b,c = (1,2,3)
print(a,b,c) # 1 2 3
# 先拆包後裝包
*a,b,c = (1,2,3,5,6,7)
print(a,b,c) # [1, 2, 3, 5] 6 7

在可變參數的函數中傳入一個列表,可以看到輸出的是只有一個元素的元組
那麼怎麼才能使得傳入的函數的時候是分開的幾個數呢?
可以在調用的時候進行拆包,即在調用的時候加*號

def get_sum(*args):
print(args)
get_sum([1,2,3]) # ([1, 2, 3],)
get_sum(*[1,2,3]) # (1, 2, 3)
def get_sum(a, *args):
print(a,args)
get_sum(1,2) # 1 (2,)
get_sum(1,2,3,4,5) # 1 (2, 3, 4, 5)
get_sum([1,2,3]) # [1, 2, 3] ()
get_sum(*[1,2,3]) # 1 (2, 3)

**kwargs

輸出是一個字典,而傳遞給兩個位置參數的話會報錯,顯示需要0個位置參數
那麼應該怎麼傳遞呢?
可以傳遞關鍵詞參數,會轉換為字典
kw指的是key word
必須傳遞關鍵字參數,會將其轉換成key:value的形式,封裝到字典裡

def show_book(**kwargs):
print(kwargs)
show_book() # {}
show_book('a','b') # 報錯:TypeError: show_book() takes 0 positional arguments but 2 were given
show_book(name ='a') # {'name': 'a'}
# 拆包傳字典
dict1 = {
'name':'a', 'num': 2}
show_book(**dict1) # {'name': 'a', 'num': 2}
# 用一個*拆包,拆出來是兩個key值
print(*dict1) # name num
# 直接輸出**dict1會報錯
# print(**dict1) # TypeError: 'name' is an invalid keyword argument for print()

兩種一起用

def show(*args, **kwargs):
print(args)
print(kwargs)
show(1,2) # (1, 2)/n {}
dict1 = {
'name':'a', 'num': 2}
show(1,2,**dict1)
# (1, 2)
# {'name': 'a', 'num': 2}

如果很多個值都是不定長參數,那麼這種情況下,可以將缺省參數放到 args的後面, 但如果有*kwargs的話,kwargs必須是最後的

def sum_nums_3(a, *args, b=22, c=33, **kwargs):
print(a)
print(b)
print(c)
print(args)
print(kwargs)
sum_nums_3(100, 200, 300, 400, 500, 600, 700, b=1, c=2, mm=800, nn=900)

一般情況下默認值參數會放在*args的後面

# c在前面的話,傳參的時候第三個參數會賦值給c
def fun(a, b, c = 10, *args, **kwargs):
print(a,b,c,args,kwargs)
fun(1,2,3,4,5,name='a')
# 1 2 3 (4, 5) {'name': 'a'}
# c在可變參數後面,賦值的時候c需要用關鍵字參數賦值方法
def fun(a, b, *args, c = 10, **kwargs):
print(a,b,c,args,kwargs)
fun(1,2,3,4,5,name='a')
# 1 2 10 (3, 4, 5) {'name': 'a'}

kwargs必須在最後面

返回值

python中函數可以返回多個返回值
如果返回多個值,會將多個值封裝到一個元組裡返回

def returning():
return 1,2
res = returning()
a,b = returning() # 1,2
print(res) # (1, 2)

全局變量和局部變量

當函數內出現局部變量和全局變量相同名字時,函數內部中的 變量名 = 數據 此時理解為定義了一個局部變量,而不是修改全局變量的值
(這裡和之前學其他語言好像不太一樣,在java中的一個方法中可以直接對全局變量進行更改,而在python中不能對函數外的變量直接進行修改,只能做查看的操作;為什麼呢,剛剛去寫了一段java代碼感受了一下,可以做一個不專業的直觀的解釋,因為java中對變量的聲明是需要加變量類型的,所以如果在函數內聲明了和全部變量一樣的變量,那麼在函數內使用的就是這個局部變量,簡單來說就是修改變量和聲明變量代碼是不一樣的;而在python中聲明變量不需要加變量類型,同樣如果在函數中聲明了一個和全局變量一樣的變量,那麼這只能認為是一個聲明變量的操作,而不能作為一個修改全局變量的操作,否則就會出現問題,所以在python中函數不能直接對全局變量進行修改)

那麼怎麼才能在函數中修改全局變量呢?
需要在函數中聲明我用的就是全局變量

a = 10
def check():
global a # 如果不加這句話會報錯,因為不能直接對全局變量進行修改
a -= 10
print(a)
check() # 0

如果在函數中出現global 全局變量的名字 那麼這個函數中即使出現和全局變量名相同的變量名 = 數據 也理解為對全局變量進行修改,而不是定義局部變量
如果在一個函數中需要對多個全局變量進行修改,那麼可以一次性全部聲明,也可以分開聲明

# 可以使用一次global對多個全局變量進行聲明
global a, b
# 還可以用多次global聲明都是可以的
# global a
# global b

查看所有的全局變量和局部變量

Python提供了兩個內置函數globals()和locals()可以用來查看所有的全局變量和局部變量。

def test():
a = 100
b = 40
print(locals()) # {'a': 100, 'b': 40}
test()
x = 'good'
y = True
print(globals()) # {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x101710630>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Users/jiangwei/Desktop/Test/test.py', '__cached__': None, 'test': <function test at 0x101695268>, 'x': 'good', 'y': True}

可變數據類型和不可變數據類型

所謂可變類型與不可變類型是指:數據能夠直接進行修改,如果能直接修改那麼就是可變,否則是不可變
可變類型(修改數據,內存地址不會發生變化)有: 列表、字典、集合
不可變類型(修改數據,內存地址必定發生變化)有:int、float、bool、字符串、元組
(python中有小整數對象池,字符串緩存池)
只有不可變數據類型才需要加global

函數中的注釋

在函數的第一行用一個字符串作為函數文檔

>>> def test(a,b):
... "用來完成對2個數求和" # 函數第一行寫一個字符串作為函數文檔
... print("%d"%(a+b))
...
>>>
>>> test(11,22) # 函數可以正常調用
33
>>>
>>> help(test) # 使用 help 查看test函數的文檔說明
Help on function test in module __main__:
test(a, b)
用來完成對2個數求和

在函數內加一對三引號,會自動出現param…類似的,與java中的/** */相似

def get_info(name: str, age: int):
""" 接收用戶的名字和年齡,拼接一個字符串並返回 :param name: 接收一個名字 :param age: 接收用戶的年齡,必須是 0-200 間的一個整數 :return: 返回拼接好的字符串 """
return "我的名字叫 %s,今年是 %d 歲" % (name, age)
get_info("吳彥祖", 19)
get_info(520, 19) # 注意,形參上標注的類型只是提高代碼的可讀性,並不會限制實參的類型
help(get_info)

引用

sys.getrefcount() 獲取引用個數(本身調用這個方法也有一個引用指向目標地址),例如下面的例子 3 + 1 = 4

import sys
list1 = [1,2,3,4]
list2 = list1
list3 = list1
print(sys.getrefcount(list1)) # 4
# del list3
# print(sys.getrefcount(list1)) # 3
del list1
print(sys.getrefcount(list2)) # 3

嵌套函數

函數只是一段可執行代碼,編譯後就“固化”了,每個函數在內存中只有一份實例,得到函數的入口點便可以執行函數了。函數還可以嵌套定義,即在一個函數內部可以定義另一個函數,有了嵌套函數這種結構,便會產生閉包問題。

函數嵌套
函數也是一個變量,所以函數內部也可以定義函數,相當於聲明了一個變量

如果要修改外部函數的變量,需要在內部函數中聲明對外部變量的使用,即加關鍵字nonlocal

def outer():
a = 10
print('outer----hello')
def inner(): # inner這個函數是在outer函數內部定義的
b = 20
nonlocal a # nonlocal表示引用內部函數外側的局部變量a
a += b
print('inner----hello')
inner() # inner函數只在outer函數內部可見
print(inner) # <function outer.<locals>.inner at 0x00000134E031AEA0>
# 用來查看局部變量
print(locals()) # {'a': 10, 'inner': <function outer.<locals>.inner at 0x00000223594EAEA0>}
outer()
# inner() 這裡會報錯,在outer函數外部無法訪問到inner函數

閉包

符合以下三個條件是閉包:

是一個嵌套函數
內部函數引用了外部函數的變量
返回值是內部函數

閉包是由函數及其相關的引用環境組合而成的實體(即:閉包=函數塊+引用環境)。

下面這段代碼前兩個調用中,因為內部函數返回的是函數名,我們知道,相當於一個變量,打印出來是它的地址
而在後兩個調用中,因為outer(x)返回的是一個函數名,用函數名調用內部函數,所以返回的是內部函數的返回值,相當於res = outer(x), t = res()

因為無法直接在外部調用內部的inner()函數,所以這裡在外部函數中將inner()當做返回值返回,就可以在外部使用這個內部函數

def outer(n):
num = n
def inner():
return num+1
return inner
print(outer(3)) # <function outer.<locals>.inner at 0x0000020E2905AEA0>
print(outer(5)) # <function outer.<locals>.inner at 0x0000020E2905AEA0>
print(outer(3)()) # 4
print(outer(5)()) # 6

在這段程序中,函數 inner 是函數 outer 的內嵌函數,並且 inner 函數是outer函數的返回值。我們注意到一個問題:內嵌函數 inner 中引用到外層函數中的局部變量num,Python解釋器會這麼處理這個問題呢? 先讓我們來看看這段代碼的運行結果,當我們調用分別由不同的參數調用 outer 函數得到的函數時,得到的結果是隔離的(相互不影響),也就是說每次調用outer函數後都將生成並保存一個新的局部變量num,這裡outer函數返回的就是閉包。 如果在一個內部函數裡,對在外部作用域(但不是在全局作用域)的變量進行引用,那麼內部函數就被認為是閉包(closure).

閉包一般在裝飾器中使用

裝飾器

首先明白,函數名也是一個變量
如下面代碼中,將函數名b賦值給a,a變量指向了b函數的地址,因此運行結果是b

def a():
print('a')
def b():
print('b')
a = b
a() # b

為了在不修改原來已有函數的基礎上,為函數添加新的功能(遵循開放封閉原則),出現了裝飾器

為了給房子刷牆,寫了一個匿名函數,直接執行發現,貌似沒有調用任何函數,但是出現了12的結果,說明@decorator 執行了裝飾器函數,裝飾器函數的參數是house,即將house函數作為參數傳入裝飾器中,因此會打印那兩行橫線,然後return 返回了wapper,house接收這個返回值,house = wrapper
此時,house函數經過了裝飾,已經變成了新的函數wrapper,這時候執行house會輸出wrapper函數的執行結果

def decorator(func):
print('--------------1')
print(func)
def wrapper():
print('---------')
func()
print('刷牆')
print('--------------2')
return wrapper
# house = decorator(house)
@decorator
def house():
print('毛坯房')
# 輸出
# --------------1
# <function house at 0x000002112911AEA0> (這句話可以看出在執行之前,會進行裝飾,將house函數傳入裝飾器中)
# --------------2
# 裝飾後的house變成了wrapper
print(house) # <function decorator.<locals>.wrapper at 0x000002112911C1E0> 
house()
# 輸出
# ---------
# 毛坯房
# 刷牆

裝飾器的功能

  • 引入日志
  • 函數執行時間統計
  • 執行函數前預備處理
  • 執行函數後清理功能
  • 權限校驗等場景
  • 緩存

帶有參數的裝飾器

如果原函數有參數,則裝飾器內部函數也需要有參數

def check_time(action):
def do_action(a,b):
action(a,b)
return do_action
@check_time
def go_to_bed(a,b):
print('{}去{}睡覺'.format(a,b))
go_to_bed("zhangsan","床上")

不定長參數

def test(cal):
def do_cal(*args,**kwargs):
# 此時傳入內部函數的是一個元組,需要拆包
cal(*args,**kwargs)
return do_cal
# 體會多個參數的傳遞,需要*args
@test
def demo(a, b):
sum = a + b
print(sum)
# 體會關鍵字參數的傳遞,需要**kwargs
@test
def demo2(a, b, c = 4):
sum = a + b + c
print(sum)
demo(1, 2)
demo2(1, b = 2)

所以一般情況下,裝飾器中的參數都會寫成這兩個可變參數

裝飾器修飾有返回值的函數

一般情況下為了讓裝飾器更通用,可以有return

def t_2(cal):
def do_cal(*args,**kwargs):
return cal(*args,**kwargs) # 需要再這裡寫return語句,表示調用函數,獲取函數的返回值並返回
return do_cal
@t_2
def demo(a,b):
return a + b
print(demo(1, 2)) #3

裝飾器帶參數

了解

執行順序如下,先檢測到outer_check(23),將參數23帶入outer_check函數中,執行該函數,返回check_time
然後執行check_time,將play_game傳入函數中,返回do_action,此時play_game = check_time

def outer_check(time):
def check_time(action):
def do_action():
if time < 22:
return action()
else:
return '對不起,您不具有該權限'
return do_action
return check_time
@outer_check(23)
def play_game():
return '玩兒游戲'
print(play_game())

帶有多個裝飾器

可見,先加載最靠近的裝飾器,再加載較遠的裝飾器

# 定義函數:完成包裹數據
def makeBold(fn):
print(1)
def wrapped():
return "<b>" + fn() + "</b>"
print(2)
return wrapped
# 定義函數:完成包裹數據
def makeItalic(fn):
print(3)
def wrapped():
return "<i>" + fn() + "</i>"
print(4)
return wrapped
def make(fn):
print(5)
def wrapped():
return "<z>" + fn() + "</z>"
print(6)
return wrapped
@make
@makeBold
@makeItalic
def t_3():
return "hello world-3"
print(t_3())
# 輸出:3 4 1 2 5 6
# <z><b><i>hello world-3</i></b></z>

匿名函數

一般不用

用lambda關鍵詞能創建小型匿名函數。這種函數得名於省略了用def聲明函數的標准步驟。

lambda函數的語法只包含一個語句,如下:
lambda 參數列表: 返回值表達式

sum = lambda arg1, arg2: arg1 + arg2
# 調用sum函數
print("Value of total : %d" % sum( 10, 20 ))
print("Value of total : %d" % sum( 20, 20 ))

Lambda函數能接收任何數量的參數但只能返回一個表達式的值

匿名函數可以執行任意表達式(甚至print函數),但是一般認為表達式應該有一個計算結果供返回使用。

python在編寫一些執行腳本的時候可以使用lambda,這樣可以接受定義函數的過程,比如寫一個簡單的腳本管理服務器。

應用場合:

可以作為參數傳遞

>>> def fun(a, b, opt):
... print("a = " % a)
... print("b = " % b)
... print("result =" % opt(a, b))
...
>>> add = lambda x,y:x+y
>>> fun(1, 2, add) # 把 add 作為實參傳遞
a = 1
b = 2
result = 3

Python中使用函數作為參數的內置函數和類:

系統高階函數

在Python中,函數其實也是一種數據類型。
函數對應的數據類型是 function,可以把它當做是一種復雜的數據類型。
既然同樣都是一種數據類型,我們就可以把它當做數字或者字符串來處理。

既然變量可以指向函數,函數的參數能接收變量,那麼一個函數就可以接收另一個函數作為參數,同樣,我們還可以把一個函數當做另一個函數的返回值。這種函數的使用方式我們稱之為高階函數。

函數作為另一個函數的參數如上已經說過,作為返回值就如閉包中返回內部函數一樣,將函數名作為返回值

def test():
print('我是test函數裡輸入的內容')
def demo():
print('我是demo裡輸入的內容')
return test # test 函數作為demo函數的返回值
result = demo() # 我是demo裡輸入的內容 調用 demo 函數,把demo函數的返回值賦值給 result
print(type(result)) # <class 'function'> result 的類型是一個函數
result() # 我是demo裡輸入的內容 我是test函數裡輸入的內容 既然result是一個函數,那麼就可以直接使用() 調用這個函數
demo()() # 我是demo裡輸入的內容 我是test函數裡輸入的內容

系統高階函數:
max,min,sorted
如要對以下內容找出年齡最大的一項

students = [
{
'name': 'zhangsan', 'age': 18, 'score': 92},
{
'name': 'lisi', 'age': 20, 'score': 90},
{
'name': 'wangwu', 'age': 19, 'score': 95},
{
'name': 'jerry', 'age': 21, 'score': 98},
{
'name': 'chris', 'age': 17, 'score': 100},
]

直接調用max不可行,max中的key關鍵字可以傳入一個函數,表示排序的依據

students = [
{
'name': 'zhangsan', 'age': 18, 'score': 92},
{
'name': 'lisi', 'age': 20, 'score': 90},
{
'name': 'wangwu', 'age': 19, 'score': 95},
{
'name': 'jerry', 'age': 21, 'score': 98},
{
'name': 'chris', 'age': 17, 'score': 100}
]
print(max(students, key=lambda x: x['age']))

filter() 過濾,filter裡的匿名函數要求返回值必須是布爾類型
選出年齡大於等於20的人

res = filter(lambda x : x.get('age') >= 20, students)
print(list(res))
# [{'name': 'lisi', 'age': 20, 'score': 90}, {'name': 'jerry', 'age': 21, 'score': 98}]

map() 函數,通過匿名函數想提取的內容,並對內容進行加工,放在一個可迭代的對象裡

print(list(map(lambda x: x['score'] / 2, students)))
# [46.0, 45.0, 47.5, 49.0, 50.0]

reduce,在functools模塊裡,對序列進行壓縮運算


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved