def语句定义的函数是所有程序的基石。
7.1 编写可接受任意数量参数的函数
(1)可接受任意数量的位置参数的函数,使用*开头的参数。例如:
>>> def avg(first, *rest):
... return (first + sum(rest)) / (1 + len(rest))
...
>>> avg(1,2)
1.5
>>> avg(1,2,3,4)
2.5
这个示例中,rest是一个元祖,它包含了其他所有传递过来的位置参数。代码在之后的计算中会将其视为一个序列来处理。
(2)接受任意数量的关键字参数,使用**开头的参数。例如:
>>> 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','Abcd', size='large', quet=6)
'<item size="large" quet="6">Abcd</item>'
>>> make_element('div', '小试牛刀', style='color:red;')
'<div style="color:red;">小试牛刀</div>'
这里的attrs是一个字典,它包含了所有传递过来的关键字参数。
(3)函数能同时接受任意数量的位置参数和关键字参数,只要联合使用*和**即可。例如:
>>> def anyargs(*args, **kwargs):
... print(args) #tuple
... print(kwargs) #dict
...
>>> anyargs(2,3,4,param1="a",param2="b")
(2, 3, 4)
{'param1': 'a', 'param2': 'b'}
这个函数中所有的位置参数都放置在元祖args中,所有的关键字参数都会放置在字典kwargs中。
总结:在函数定义中,以*打头的参数只能作为最后一个位置参数出现,而以**打头的参数只能作为最后一个参数出现。(在*打头的参数后仍然可以有其他参数出现,但是出现在*打头的参数后,只能作为关键字参数使用)例如:
>>> def anyargs(x, *args, y, **kwargs):
... print(args)
... print(kwargs)
... print(x)
... print(y)
...
>>> anyargs(2,3,4,param1="a",y="1,2",param2="b")
(3, 4)
{'param1': 'a', 'param2': 'b'}
2
1,2
>>> anyargs(2,3,4,y="1,2",param1="a",param2="b")
(3, 4)
{'param1': 'a', 'param2': 'b'}
2
1,2
7.2 带参数注解的函数,增加代码可读性。函数注解会保存在__annotations__属性中。例如:
>>> def addInt(x:int, y:int) -> int:
... return x + y
...
>>> addInt(2,9)
>>> addInt.__annotations__
{'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
7.4 从函数中返回多个值
>>> def myfun():
... return 1,2,3 #实际上返回的是一个元祖
...
>>> a,b,c = myfun() #元祖解包赋值给变量
>>> a
1
>>> b
2
>>> c
3
>>> d = myfun() #代表了整个元祖
>>> d
(1, 2, 3)
7.5 定义默认参数
默认参数赋值,应该总是不可变的对象,例如:None、True、False、数字、或者字符串。特别要注意,绝对不要编写这样的代码:
def fun(a, b=[]): # NO! 会陷入各种麻烦中,默认值在函数体外被修改时,调用会产生持续影响
...
#None会被判定为False,还有长度为0的字符串,列表,元祖,字典 也会被判断为False
>>> def fun(a, b=None):
... if not b: # NO! 可以使用 b is None 代替这句
... b = []
...
>>>
7.6 定义匿名或内联函数
>>> b = lambda x,y: x + y #等价于 def b(x, y)
>>> b(3, 4)
7
>>> def b(x, y):
... return x + y
...
>>> b(3, 4)
7
下面lambda表达式中x是一个自由变量,此实例可以验证,lambda在运行时才进行绑定,而不是定义的时候绑定(与def不同)
>>> x = 10
>>> a = lambda y: x + y
>>> a(10)
20
>>> x = 20
>>> a(10)
30
再来看一下易出错的循环迭代,示例如下:
>>> funcs = [lambda x: x+n for n in range(5)] #执行时候n始终为最后一次的赋值
>>> for f in funcs:
... print(f(0))
...
4
4
4
4
4
>>> funcs = [lambda x, n=n: x+n for n in range(5)] #执行时候n每次赋值
>>> for f in funcs:
... print(f(0))
...
0
1
2
3
4
7.7 访问定义在闭包中的变量
通过函数来扩展闭包,使得闭包内层定义的变量可以背访问和修改。一般来说,在闭包内层定义的变量对于外界来说是完全隔离的。但是可以通过编写来存取函数,并将它们作为函数属性附加到闭包上来提供对内层变量的访问。例如:
>>> def sample():
... n = 0
... def fun():
... print('n = ', n)
... def get_n():
... return n
... def set_n(value):
... nonlocal n #nonlocal 声明使得编写函数来修改内层变量成为可能
... n = value
... fun.get_n = get_n #函数属性能够将存取函数以直接的方式附加到闭包函数上
... fun.set_n = set_n
... return fun
...
>>> f = sample()
>>> f()
n = 0
>>> f.set_n(10)
>>> f()
n = 10
>>> f.get_n()
10
闭包模拟成类实例,速度比class定义的实例,测试出大约快8%,测试中的大部分时间都花在对实例变量的直接访问上,闭包要更快一些,这是因为不用涉及额外的self变量。
>>> import sys
>>> class aaa():
... def __init__(self, locals = None):
... if locals is None:
... locals = sys._getframe(1).f_locals
... self.__dict__.update((key,value) for key, value in locals.items() if callable(value))
... def __len__(self):
... return self.__dict__['__len__']()
...
>>> def Stack(): #闭包模拟成类实例
... items = []
... def push(item):
... items.append(item)
... def pop():
... items.pop()
... def __len__():
... return len(items)
... return aaa()
...
>>> s = Stack()
<__main__.aaa object at 0x7f89f34773c8>
>>> s.push(10)
>>> s.push(20)
>>> s.push('hello')
>>> len(s)
3
>>> s.pop() #pop出'hello'
>>> len(s)
2
>>> s.pop() #pop出20
>>> s.pop() #pop出10
>>> len(s)
0
>>> class Stack2: #实例
... def __init__(self):
... self.items = []
... def push(self, item):
... self.items.append(item)
... def pop(self):
... return self.items.pop()
... def __len__(self):
... return len(self, items)
...
#性能对比
>>> from timeit import timeit
>>> s = Stack()
>>> timeit('s.push(1);s.pop()', 'from __main__ import s') #闭包耗用时间
0.3924146047793329
>>> s = Stack2()
>>> timeit('s.push(1);s.pop()', 'from __main__ import s') #实例耗用时间
0.4104466247372329
>>>