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)

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

扫码关注公众号:

Python函数使用技巧

相关推荐