Python函數使用技巧

可接受任意數量參數的函數

在Python中定義函數非常簡單,例如一個計算二次冪的函數:

def power(x):
    return x * x

如果我們想要一個可以接受任意數量參數的函數,該怎麼定義呢?
比如說想要一個函數,接受一組數據,並計算它們的平方值的和。
當然可以直接傳遞列表或者元組做函數的參數:

def sum_power(x):
    sum = 0
    for i in x:
        sum += i * i
    return sum

但是這樣做的話,如果想使用這個函數,必須先搞一個列表或元組出來,像這樣調用:

sum_power([1,2,3,4])

使用下面這個方法就簡單多了:

def sum_power(*x):
    sum = 0
    for i in x:
        sum += i * i
    return sum

使用*開頭的參數,那麼在這之後的位置參數都會被當作一個序列來處理。
但是如果本身就有一個列表arr = [1,2,3,4,5]要怎麼處理呢?
可以這樣調用這個函數:

sum_power(*arr)

在調用函數時給列表之類的參數加一個*,就相當於把這個列表拆開,所有元素做爲函數的參數傳遞進去。

如果要使用任意數量的關鍵字參數,可以使用**開頭的參數。

>>> def a(**attrs):
...     print(attrs)
...
>>> a(name="張三", age="18")
{'name': '張三', 'age': '18'}
>>>

可以發現,以**開頭的參數被包裝成了字典來處理。

def a(*args, **kwargs):
    pass

像這樣的函數就能接受任意數量的位置參數和關鍵字參數了。

keyword-only參數

在定義函數時,*開頭的參數必須是最後一個位置參數,而**開頭的參數則必須是最後一個參數。思考一下這是爲什麼?
但是呢,在*開頭的參數後面,還是可以接參數的,將某個關鍵字參數放在*開頭的函數或者一個單獨的*後面,這個參數就會變成keyword-only參數,只能用關鍵字的形式使用。
例如:

def person(name, *, age):
    pass
    
person("張三", 12)  #錯誤:TypeError: person() takes 1 positional argument but 2 were given
person("張三", age=12)  #True

這時候用第一種方式調用函數就會報錯了,必須用關鍵字age=12的形式。
使用keyword-only參數可以提高代碼可讀性。

閉包(closure)

有時對於一些只有一個方法的類(除__init__()外),我們希望用一個函數來替代它以簡化代碼。

from urllib.request import urlopen


class UrlTemplate:
    def __init__(self, template):
        self.template = template
    def open(self, **kwargs):
        return urlopen(self.template.format_map(kwargs))
        
# 使用示例
yahoo = UrlTemplate('http://finance.yahoo.com/d/quotes.csv?s={names}&f={fields}')
for line in yahoo.open(names='IBM,AAPL,FB', fields='sllclv'):
    print(line.decode('utf-8'))

這裏的UrlTemplate類可以使用函數來替代:

def urltemplate(template):
    def opener(**kwargs):
        return urlopen(template.format_map(kwargs))
    return opener
    
# 使用示例
yahoo = urltemplate('http://finance.yahoo.com/d/quotes.csv?s={names}&f={fields}')
for line in yahoo(names='IBM,AAPL,FB', fields='sllclv'):
    print(line.decode('utf-8'))

閉包是一種嵌套形式的函數,外部函數直接把內部函數當作返回值返回。閉包顧名思義,就像一個包裹,它可以記住定義閉包時的環境。像示例中,opener()函數可以記住urltemplate()參數template的值,並在之後的調用中使用。

裝飾器

有時候我們想擴展函數的功能,但是又不想修改函數的代碼,這時候就可以使用到裝飾器。裝飾器本質上是一個函數(或類),它接受一個函數做爲輸入並返回一個新的函數做爲輸出。
例如我們現在需要給我們的函數添加一個日誌功能,打印出函數執行時間:

import time
from functools import wraps


def log(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("calling:{}".format(func.__name__))
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end-start)
        return result
    return wrapper

# 使用示例
@log
def f(x):
    while x < 1000000:
        x += 1

調用結果:

>>> f(1)
calling:f
f 0.11393427848815918
>>> 

如果不使用裝飾器,我們要在f()函數中編寫代碼,不僅原本的函數變得面目全非,如果我們想在其它函數上也增加記錄功能,又得複製代碼過去,不如使用裝飾器方便。

上面使用裝飾器的部分,實質上相當於:

def f(x):
    ...
f = log(f)

裝飾器函數接受被裝飾的函數做爲參數,並將原函數名這個變量重新賦值爲返回後的新函數。

掃碼關注公衆號:

clipboard.png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章