可接受任意数量参数的函数

在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 givenperson("张三", age=12)  #True

这时候用第一种方式调用函数就会报错了,必须用关键字age=12的形式。
使用keyword-only参数可以提高代码可读性。

闭包(closure)

有时对于一些只有一个方法的类(除__init__()外),我们希望用一个函数来替代它以简化代码。

from urllib.request import urlopenclass 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 timefrom functools import wrapsdef 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# 使用示例@logdef f(x):    while x < 1000000:        x += 1

调用结果:

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

如果不使用装饰器,我们要在f()函数中编写代码,不仅原本的函数变得面目全非,如果我们想在其它函数上也增加记录功能,又得复制代码过去,不如使用装饰器方便。

上面使用装饰器的部分,实质上相当于:

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

装饰器函数接受被装饰的函数做为参数,并将原函数名这个变量重新赋值为返回后的新函数。

扫码关注公众号: