【Python Cookbook】第七章 函数


由于第五章(文件 I/O)与第六章(数据编码与处理)内容需要文件支撑,所以先跳过这两章的学习了。


一、函数

函数中的参数主要分为:位置参数、关键字参数、默认参数、可变参数。
a) 当有位置参数时,位置参数必须在关键字参数的前面,但关键字参数不存在先后顺序。
b) 默认参数,也即提前定义arg=1,位置参数必须出现在默认参数之前。
c) 可变参数*args,它会根据传进参数的位置合并为一个元组tuple
d) 关键字参数,是以字典类似的关键字形式输入的一种形式。

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

要编写 一个可接受任意数量的位置参数,可以使用以*开头的参数,如下:

def avg(first, *rest):
    return (first + sum(rest)) / (1 + len(rest))

avg(1, 2, 3, 4)
2.5

如果要接受任意数量的关键字参数,可以使用**开头的参数,如下:

import html
def make_element(name, value, **attrs):
    keyvals = [' %s="%s"' % item for item in attrs.items()]
    attr_str = ''.join(keyvals)
    element = '<{name}{attrs}>{value}</{name}>'.format(
        name = name,
        attrs = attr_str,
        value = html.escape(value)
    )
    return element
make_element('item', 'Albatross', size='large', quantity=6)
'<item size="large" quantity="6">Albatross</item>'

在上面的代码中,attrs是一个字典,它包含了所有传递过来的关键字参数。

如果想要函数能够同时接收任意数量的位置参数与关键字参数,只要联合使用***即可,如下:

def anyargs(*args, **kwargs):
    print(args)
    print(kwargs)

在这个函数中,所有的位置参数都会被放置在元组args中,而所有的关键字参数都被放在字典kwargs中。

1.2 只接受关键字参数的函数

如果将关键字参数放置在*参数之后,可以实现关键字函数形式,如下:

def recv(maxsize, *, block):
    pass
recv(1024, True)
#TypeError: recv() takes 1 positional argument but 2 were given
recv(1024, block=True)
#√
def mininum(*values, clip=None):
    return 0 if clip is None else min(values)

mininum(1,-2,3, clip=1)
-2

如果想要了解某一函数的输入参数格式,可以使用help()方法,如下:

help(recv)
Help on function recv in module __main__:
recv(maxsize, *, block)

1.3 函数的参数注释

函数的参数注释能够快速帮助其他人应用理解这个函数的输入与输出格式,函数注释保存在函数的__annotations__属性中,如下:

def add(x:int, y:int) -> int:
    return x + y
help(add)
Help on function add in module __main__:
add(x: int, y: int) -> int

add.__annotations__
{
    
    'x': int, 'y': int, 'return': int}

1.4 多值返回的函数

函数返回多个值,实际上其返回的是一个元组,如下:

def myfun():
    return 1,2,3

a,b,c = myfun()
b
2

1.5 带有默认参数的函数

我们想定义一个函数,其中有一个或多个参数是可选的并且带有默认值的。简单情况下,只要在定义中为参数赋值,并确保默认参数出现在最后即可,如下:

def spam(a, b=42):
    print(a, b)
    
spam(1)
1 42
spam(1, 2)
1 2

如果想使得默认参数是可变容器的话,比如列表、集合或者字典,应该把None作为默认值,如下:

def spam(a, b=None):
    print(a, b)

如果不打算提供一个默认值,可以采用如下的方法:

_no_value = object()

def spam(a, b=_no_value):
    if b is _no_value:
        print('No b value support')

spam(1)
No b value support
spam(1, None) # b = None
spam(1, 2) # b = 2

1.6 定义匿名或内联函数

当我们在编写一些比较简单的(可能只需要一行代码)函数时,我们不想通过def语句编写一个单行的函数,我们更希望通过一种“内联”式的函数,这个时候,我们可以使用lambda表达式来替代,如下:

add = lambda x,y: x + y
add(2, 3)
5
add('2', '3')
'23'

但是,lambda表达式存在一定的局限,比如多行语句、条件分支、迭代和异常值处理这些情况下,无法使用lambda表达式。


1.7 在匿名函数中绑定变量的值

我们使用lambda表达式定义了一个匿名函数,但是也希望在函数定义的同时完成对特定变量的绑定,考虑一下的代码:

x = 10
a = lambda y: x + y
x = 20
b = lambda y: x + y

a(10)
30
b(10)
30

上面的例子中x是一个自由变量,因此是在运行的时候绑定,而不是在定义的时候绑定的

如果希望匿名函数在定义的时候绑定变量,并且保持不变,那么可以将该值作为默认参数实现,如下:

x = 10
a = lambda y, x=x: x + y
x = 20
b = lambda y, x=x: x + y

a(10)
20
b(10)
30

1.8 让带有N个参数的对象以较少的参数形式调用

我们有一个调用对象,比如def方法,但是这个对象所需要的参数过多,如果直接调用会产生异常。如果要减少函数的参数数量,应该使用functools.partial(),该函数允许我们给一个或多个参数指定的值,以此减少需要提供的参数数量。可以通过下面这个例子来进行说明:

def spam(a, b, c, d):
    print(a, b, c, d)
    
from functools import partial
s1 = partial(spam, 1) # a = 1
s1(5, 6, 7)
1 5 6 7

s2 = partial(spam, d=42)
s2(2,2,3)
2 2 3 42

s3 = partial(spam, 1, 2, d=42) # a=1, b=2
s3(5)
1 2 5 42

本节的内容对于看似不兼容的代码结合起来使用有一定好处,下面会给一些示例来演示。

第一个例子,假设有一列以元组(x,y)来表示的点坐标,可以用下面的函数来计算两点之间的距离,如下:

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)

from functools import partial
pt = (4, 3)
points.sort(key=partial(distance, pt))
points
[(3, 4), (1, 2), (5, 6), (7, 8)]

1.9 在回调函数中携带额外的状态

我们正在编写需要使用回调函数的代码(比如,事件处理历程,完成回调等),但是希望回调函数可以携带额外的状态以便在回调函数内部使用。为了能够更好地说明,我们定义了如下的函数,它会调用一个回调函数:

def apply_async(func, args, *, callback):
    result = func(*args)
    
    callback(result)
    
def print_result(result):
    print('Got: ', result)
    
def add(x, y):
    return x + y

apply_async(add, (2,3), callback=print_result)
Got:  5

另一种使用回调函数的方法是使用绑定方法(bound-method),而不是普通的函数。比如下面这个类保存了一个内部的序列号码,每当接收到一个结果时就递增这个号码:

class ResultHandler:
    def __init__(self):
        self.sequence = 0
    def handler(self, result):
        self.sequence += 1
        print('[{}] Got: {}'.format(self.sequence, result))
        
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

handler = make_handler()
apply_async(add, (2,3), callback=handler)
[1] Got: 5

除此之外,还可以通过利用协程(coroutine)来完成同样的任务,对于协程来说,使用它的send()方法来回调函数,如下:

def make_handler():
    sequence = 0
    while True:
        result = yield
        sequence += 1
        print('[{}] Got: {}'.format(sequence, result))

handler = make_handler()
next(handler)
apply_async(add, (2,3), callback=handler.send)
[1] Got: 5

最后一种方法,可以通过额外参数在回调函数中携带状态,然后用partial()来处理参数个数的问题,如下:

class SequenceNo:
    def __init__(self):
        self.sequence = 0
 
def handler(result, seq):
    seq.sequence += 1
    print('[{}] Got: {}'.format(seq.sequence, result))
    
seq = SequenceNo()
from functools import partial
apply_async(add, (2,3), callback=partial(handler, seq=seq))
[1] Got: 5

1.10 访问定义在闭包内的变量

我们希望通过含函数来扩展闭包,使得闭包内层定义的变量可以被访问和修改。(一般来说,闭包内层定义的变量对于外界来说完全是隔离的,但可以通过编写存取函数进行读取或修改)具体如下:

def sample():
    n = 0
    def func():
        print('n = ', n)
        
    def get_n():
        return n
    
    def set_n(value):
        nonlocal n
        n = value
        
    func.get_n = get_n
    func.set_n = set_n
    return func

# Test
f = sample()
f()
n =  0

f.set_n(10)
f()
n = 10

f.get_n()
10

nonlocal关键字用来在函数或其他作用域中使用外层(非全局)变量。


总结

查漏补缺~

参考:《Python cookbook 中文版》[美]David Beazley&Brian K. Jones 著

猜你喜欢

转载自blog.csdn.net/weixin_47691066/article/details/127329621