8. 函数的奇淫巧技

  1. 函数中任意数量参数

如果希望能够接受任意数量的参数,可以使用一个 * 参数

def avg(first, *rest):

return (first + sum(rest)) / (1 + len(rest))

这样我们可以使用avg(1,2) avg(1,2,3,4)

first为1,剩下的都加入rest,而rest实际上是一个元组,我们可以将其当作序列来使用

如果希望接受任意数量的关键字参数,则可以使用 ** 开头的参数,比如

def make_element(name, value, **attrs):

keyvals = [‘ %s=”%s”‘ % item for item in attrs.items()]

attr_str = ”.join(keyvals)

print(attr_str)

传入一个字典,其中key是关键字,value是实际参数

我们还可以同时在一个函数中使用 * **

def anyargs(*args, **kwargs):

print(args) # A tuple

print(kwargs) # A dict

需要注意的是 ** 参数必须要在在最后 而*参数后仍然可以定义其他的参数

那么* 参数后面的参数,就必须要使用关键字进行传输

def recv(maxsize, *, block):

‘Receives a message’

pass

recv(1024, block=True)

  1. 函数参数的元数据

如果希望给函数的参数增加一些额外信息,那么可以使用函数参数注解

def add(x:int, y:int) -> int:

return x + y

虽然这些注解不会进行类型检查,但是方便阅读源码,知道这个函数参数的含义

  1. 一个函数返回多个值

在return的时候直接返回就行,只不过会被封装为一个元组

>>> def myfun():

… return 1, 2, 3

  1. 函数中的默认参数

如果想要定义默认参数,直接在参数中指定就可以了

def spam(a, b=42):

print(a, b)

当然默认参数可以传入变量,但是需要注意,默认参数的值只有在函数定义的时候赋值一次,比如

>>> x = 42

>>> def spam(a, b=x):

… print(a, b)

>>> spam(1)

1 42

>>> x = 23 # Has no effect

>>> spam(1)

1 42

而且默认参数理应不能变,不然会进行记录变化,比如默认参数是一个数组

>>> def spam(a, b=[]):

… print(b)

… return b

>>> x.append(99)

>>> x.append(‘Yow!’)

>>> spam(1) # Modified list gets returned!

[99, ‘Yow!’

  1. 定义匿名函数

这里简单说下lambda的使用方式

>>> add = lambda x, y: x + y

>>> add(2,3)

5

这样的一个简单的匿名函数,非常适合表达计算表达式

不过需要注意,这样的一个匿名表达式,他的变量传递跟一般函数是不一样的,只有运行时的时候赋值变量,就比如

>>> x = 10

>>> a = lambda y: x + y

>>> x = 20

>>> b = a

>>> a(10)

30

>>> b(10)

30

如果想要进行值的绑定的话,则应该在定义的时候就捕获对应的值

>>> x = 10

>>> a = lambda y, x=x: x + y

>>> x = 20

>>> b = lambda y, x=x: x + y

>>> a(10)

20

>>> b(10)

30

如果是在循环中使用lambda的话,那么应该进行值的捕获

>>> funcs = [lambda x, n=n: x+n for n in range(5)]

>>> for f in funcs:

… print(f(0))

减少方法的参数个数

如果希望减少某个函数的参数类型,可以使用functools.partial() 其可以给一个函数设置一到多个固定值

def spam(a, b, c, d):

print(a, b, c, d)

>>> s2 = partial(spam, d=42) # d = 42

>>> s2(1, 2, 3)

1 2 3 42

这个函数有什么用呢?

主要就是兼容一些要求不同数量的函数

比如我们想要进行排序,排序只需要一个参数的传入,但是我们的计算函数需要传入两个参数

points = [ (1, 2), (3, 4), (5, 6), (7, 8) ]

import math

def distance(p1, p2):

x1, y1 = p1

x2, y2 = p2

return math.hypot(x2 – x1, y2 – y1)

>>> pt = (4, 3)

>>> points.sort(key=partial(distance,pt))

>>> points

[(3, 4), (1, 2), (5, 6), (7, 8)]

>>>

这样就可以进行兼容了

  1. 单方法的类作为函数

如果我们有一个只有一个方法的类,这个类一般为了记录某些属性,方便在方法中使用,那么我们可以使用闭包来将其转换为函数

class UrlTemplate:

def __init__(self, template):

self.template = template

def open(self, **kwargs):

return urlopen(self.template.format_map(kwargs))

# Example use. Download stock data from yahoo

yahoo = UrlTemplate(‘http://finance.yahoo.com/d/quotes.csv?s={names}&f={fields}’)

for line in yahoo.open(names=’IBM,AAPL,FB’, fields=’sl1c1v’):

print(line.decode(‘utf-8’))

这样的一个类,我们在声明之后就为了使用其open函数

那么我们可以使用闭包进行转换

def urltemplate(template):

def opener(**kwargs):

return urlopen(template.format_map(kwargs))

return opener

# Example use

yahoo = urltemplate(‘http://finance.yahoo.com/d/quotes.csv?s={names}&f={fields}’)

for line in yahoo(names=’IBM,AAPL,FB’, fields=’sl1c1v’):

print(line.decode(‘utf-8’))

我们需要一个地方进行存储这个类,我们不如直接使用闭包,opener函数就记住了传入的template的值,并且在接下来的调用时使用

  1. 回调函数保存额外信息

一般来说我,我们的回调函数一般都是

>>> def add(x, y):

… return x + y

>>> apply_async(add, (2, 3), callback=print_result)

Got: 5

如果希望这种回调函数带有额外的信息,最简单的是创建一个类保存额外变量

class ResultHandler:

def __init__(self):

self.sequence = 0

def handler(self, result):

self.sequence += 1

print(‘[{}] Got: {}’.format(self.sequence, result))

之后就可以创建一个类的实例,然后handler()绑定方法作为回调函数

>>> r = ResultHandler()

>>> apply_async(add, (2, 3), callback=r.handler)

[1] Got: 5

或者使用闭包进行保存状态值

def make_handler():

sequence = 0

def handler(result):

nonlocal sequence

sequence += 1

print(‘[{}] Got: {}’.format(sequence, result))

return handler

需要注意的是我们使用nolocal语句指示变量会在解析来被修改

或则考虑使用partial() 以及 lambda进行修改

>>> apply_async(add, (2, 3), callback=lambda r: handler(r, seq))

  1. 访问闭包中定义的变量

如何访问闭包中的内部变量呢?可以考虑编写访问函数进行访问

def sample():

n = 0

# Closure function

def func():

print(‘n=’, n)

# Accessor methods for n

def get_n():

return n

def set_n(value):

nonlocal n

n = value

# Attach as function attributes

func.get_n = get_n

func.set_n = set_n

return func

仍然需要注意使用nolocal进行修改

我们甚至可以使用闭包来模拟出一个类的实例,

def Stack():

items = []

def push(item):

items.append(item)

def pop():

return items.pop()

def __len__():

return len(items)

return ClosureInstance()

能够基本和一个类一样使用,而且由于不用维护self,所以访问的更快,但是需要注意,在很多类的特性上,模拟起来会很麻烦

  1. 内联回调函数

类似一种异步转同步的思路

from queue import Queue

from functools import wraps

def apply_async(func, args, *, callback):

# Compute the result

result = func(*args)

# Invoke the callback with the result

callback(result)

class Async:

def __init__(self, func, args):

self.func = func

self.args = args

def inlined_async(func):

@wraps(func)

def wrapper(*args):

f = func(*args)

result_queue = Queue()

result_queue.put(None)

while True:

result = result_queue.get()

try:

a = f.send(result)

apply_async(a.func, a.args, callback=result_queue.put)

except StopIteration:

break

return wrapper

def add(x, y):

return x + y

@inlined_async

def test():

r = yield Async(add, (2, 3))

print(r)

r = yield Async(add, (‘hello’, ‘world’))

print(r)

for n in range(10):

r = yield Async(add, (n, n))

print(r)

print(‘Goodbye’)

if __name__ == ‘__main__’:

test()

利用了send会让yield语句在之后进行运行

发表评论

邮箱地址不会被公开。 必填项已用*标注