可接受任意数量参数的函数
问题:
你想构造一个可接受任意数量参数的函数
解决方案:
为了能让一个函数接受任意数量的位置参数,可以使用一个* 参数。例如:
1 # 求平均数的函数, 参数中的*代表可以接收一个可变长度的参数,支持所有的类型 2 def avg(first, *rest): 3 return (first + sum(rest)) / (1 + len(rest)) 4 5 r1 = avg(1,2) 6 print('r1的结果为:', r1) 7 8 r2 = avg(1,2,3,4,5) 9 print('r2的结果为:', r2) 10 11 12 # 接收关键字的参数 13 import html 14 15 def make_element(name, value, **attrs): 16 keyvals = [' %s="%s"' % item for item in attrs.items()] 17 attr_str = ''.join(keyvals) 18 element = '<{name}{attrs}>{value}</{name}>'.format( 19 name=name, 20 attrs=attr_str, 21 value=html.escape(value) 22 ) 23 return element 24 25 r3 = make_element('item', 'Albatross', size='large', quantity=6) 26 print(r3) 27 28 #同时使用可变参数和关键字参数 29 def anyargs(*args, **kwargs): 30 print('arrgs:', args) 31 print('kwargs:', kwargs) 32 33 r4 = anyargs(1,2,3,4,name='demon', age=18) 34 r4
以上代码执行的结果为:
r1的结果为: 1.5 r2的结果为: 3.0 <item size="large" quantity="6">Albatross</item> arrgs: (1, 2, 3, 4) kwargs: {'name': 'demon', 'age': 18}
总结:
一个* 参数只能出现在函数定义中最后一个位置参数后面,而**参数只能出现在最后一个参数。有一点要注意的是,在* 参数后面仍然可以定义其他参数。
只接受关键字参数的函数
问题:
你希望函数的某些参数强制使用关键字参数传递
解决方案:
将强制关键字参数放到某个* 参数或者当个* 后面就能达到这种效果。比如:
1 def recv(maxsize, *, block): 2 print('maxsize:{:<10} block:{:<10}'.format(maxsize, block)) 3 4 #执行报错 5 #recv(1024, True) 6 7 #执行成功 8 recv(1024, block=8) 9 10 #接受任意多个位置参数的函数中指定关键字参数 11 def minimum(*values, clip=None): 12 #去可变参数中最小的值 13 m = min(values) 14 if clip is not None: 15 #三元表达式,判断条件为真,取clip得值,否则取后面m 16 m = clip if clip > m else m 17 return m 18 19 print(minimum(1, 5, 2, -5, 10)) #return -5 20 print(minimum(1, 5, 2, -5, 10, clip=0)) #return 0
以上代码执行的结果为:
maxsize:1024 block:8
-5
0
给函数参数增加元信息
问题:
你写好了一个函数,然后想为这个函数的参数增加一些额外的信息,这样的话其他使用者就能清楚的知道这个函数应该怎么使用
解决方案:
使用函数参数注解是一个很好的办法,它能提示程序员应该怎样正确使用这个函数。例如,下面有一个被注解了的函数:
1 def add(x:int ,y:int) -> int: 2 return x + y 3 4 print('add函数执行的结果:', add(10,20)) 5 6 print('add函数的帮助信息,会有自己的注释信息'.center(60, '*')) 7 help(add) 8 9 #函数注解只存储在函数的annotations 属性中 10 print(add.__annotations__)
以上代码执行的结果为:
add函数执行的结果: 30 ********************add函数的帮助信息,会有自己的注释信息******************** Help on function add in module __main__: add(x:int, y:int) -> int {'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
返回多个值的函数
问题:
你希望构造一个可以返回多个值的函数
解决方案:
为了能返回多个值,函数直接return 一个元组就行了。例如:
1 def myfun(): 2 return 1, 2, 3, 4 #等同于(1, 2, 3, 4) 3 4 print('调用myfunc函数的返回值:', myfun()) 5 6 #通过解包的方法,把函数返回值赋值给多个变量 7 print('函数返回值解包处理'.center(50, '*')) 8 a , *b , c = myfun() 9 print('a:', a) 10 print('b:', b) 11 print('c:', c)
以上代码执行的结果为:
调用myfunc函数的返回值: (1, 2, 3, 4) ********************函数返回值解包处理********************* a: 1 b: [2, 3] c: 4
定义有默认参数的函数
问题:
你想定义一个函数或者方法,它的一个或多个参数是可选的并且有一个默认值
解决方案:
定义一个有可选参数的函数是非常简单的,直接在函数定义中给参数指定一个默认值,并放到参数列表最后就行了。例如:
1 def spam(x, y=20): 2 return x + y 3 4 print(spam(1)) 5 print(spam(12.4)) 6 7 #如果默认参数是一个可修改的容器比如一个列表、集合或者字典,可以使用None作为默认值 8 def spam(a, b=None): 9 if b is None: 10 b = [] 11 pass 12 13 #这里有个小坑,默认参数的值仅仅自爱函数定义的时候赋值一次 14 x = 42 15 def spam(a, b=x): 16 print('函数内部a的值:', a) 17 print('函数内部b的值:', b) 18 19 print('函数外部x的值:', x) 20 spam(1) 21 22 print(''.center(50, '*')) 23 x = 23 24 print('函数外部x的值:', x) 25 spam(1)
以上代码执行的结果为:
21 32.4 函数外部x的值: 42 函数内部a的值: 1 函数内部b的值: 42 ************************************************** 函数外部x的值: 23 函数内部a的值: 1 函数内部b的值: 42
总结:
注意到当我们改变x 的值的时候对默认参数值并没有影响,这是因为在函数定义的时候就已经确定了它的默认值了
默认参数的值应该是不可变的对象,比如None、True、False、数字或字符串。特别的,千万不要像下面这样写代码:
1 #来来来 这里面有个小坑哦~ 2 def spam(a, b=[]): 3 print(b) 4 return b 5 6 #执行x函数,会打印默认参数b的值 7 spam(1) 8 9 #我们再次调用spam然后给函数添加几个值看下坑 10 x = spam(1) 11 x.append(99) 12 x.append(100) 13 x.append('Yo~') 14 #这个时候在调用,发现函数里面添加了值,这显然不是我们想要的效果,除非你知道现在在干什么! 15 print(x) 16 17 #修改这个bug,对于传递进来的默认值是0 False '' 空的字典字符串等都是让b的值为空列表,如果是正常的值,就不用处理 18 print(''.center(50, '*')) 19 def spam(a, b=None): 20 if not b: 21 b = [] 22 print('函数内部b的值:', b) 23 return b 24 25 spam(1) 26 x = [] 27 spam(1, x) 28 spam(1, 0) 29 spam(1, '') 30 spam(1, {}) 31 spam(1, 10)
以上代码执行的结果为:
[] [] [99, 100, 'Yo~'] ************************************************** 函数内部b的值: [] 函数内部b的值: [] 函数内部b的值: [] 函数内部b的值: [] 函数内部b的值: [] 函数内部b的值: 10
定义匿名或内联函数
问题:
你想为sort() 操作创建一个很短的回调函数,但又不想用def 去写一个单行函数,而是希望通过某个快捷方式以内联方式来创建这个函数
解决方案:
当一些函数很简单,仅仅只是计算一个表达式的值的时候,就可以使用lambda 表达式来代替了。比如:
1 add = lambda x,y:x+y 2 print(add(1.5,205)) 3 print(add('hello',',world')) 4 5 #lambda等同于下面的写法 6 def add(x, y): 7 return x + y 8 print(add(12,30)) 9 10 #常用在的场景如sort排序或者reduce的高阶函数中 11 print('lambda常用场景'.center(50, '-')) 12 names = ['David Beazley', 'Brian Jones','Raymond Hettinger', 'Ned Batchelder'] 13 print("按着列表中的每个元素中的第一个字母排序:", sorted(names, key=lambda x:x[0])) 14 print("按着名称的小写字母排序:", sorted(names, key=lambda name:name.split()[-1].lower())) 15 16 #另外一种用法,在高阶函数中使用 17 from functools import reduce 18 print('求100的和:', reduce(lambda x,y:x+y, range(1,101))) 19 print('求2的1-10的次方:',list(map(lambda x:2**x, range(1,11)))) 20 print('只保留姓名D或者R开头的名称:', list(filter(lambda name:name.startswith(('D','R')), names)))
以上代码执行的结果为:
206.5 hello,world 42 --------------------lambda常用场景-------------------- 按着列表中的每个元素中的第一个字母排序: ['Brian Jones', 'David Beazley', 'Ned Batchelder', 'Raymond Hettinger'] 按着名称的小写字母排序: ['Ned Batchelder', 'David Beazley', 'Raymond Hettinger', 'Brian Jones'] 求100的和: 5050 求2的1-10的次方: [2, 4, 8, 16, 32, 64, 128, 256, 512, 1024] 只保留姓名D或者R开头的名称: ['David Beazley', 'Raymond Hettinger']
注意:
lambda 表达式允许你定义简单函数,但是它的使用是有限制的。你只能指定单个表达式,它的值就是最后的返回值。也就是说不能包含其他的语言特性了,包括多个语句、条件表达式、迭代以及异常处理等等。
匿名函数捕获变量值
问题:
你用lambda 定义了一个匿名函数,并想在定义时捕获到某些变量的值
解决方案:
如果你想让某个匿名函数在定义时就捕获到值,可以将那个参数值定义成默认参数即可,就像下面这样:
x = 10 add = lambda y=10:x + y print("使用全局x并且有默认参数y的结果:", add()) add = lambda x,y=20:x + y print("传递一个位置参数x,并且使用默认参数的结果:", add(100))
以上代码执行的结果为:
使用全局x并且有默认参数y的结果: 20
传递一个位置参数x,并且使用默认参数的结果: 120
有些新手可能会不恰当的lambda 表达式。比如,通过在一个循环或列表推导中创建一个lambda 表达式列表,并期望函数能在定义时就记住每次的迭代值。例如:
1 #另外一个坑,发现返回的值都是4,也就是记录的最后一次的结果 2 funcs = [lambda x:x+n for n in range(5)] 3 for f in funcs: 4 print('有问题的结果:', f(0)) 5 6 #修正上面的问题 7 print('*'*50) 8 funcs = [lambda x,n=n:x+n for n in range(5)] 9 for f in funcs: 10 print('修正以后的结果:', f(0))
以上代码执行的结果为:
有问题的结果: 4 有问题的结果: 4 有问题的结果: 4 有问题的结果: 4 有问题的结果: 4 ************************************************** 修正以后的结果: 0 修正以后的结果: 1 修正以后的结果: 2 修正以后的结果: 3 修正以后的结果: 4
减少可调用对象的参数个数
问题:
你有一个被其他python 代码使用的callable 对象,可能是一个回调函数或者是一个处理器,但是它的参数太多了,导致调用时出错
解决方案:
如果需要减少某个函数的参数个数, 你可以使用functools.partial() 。partial() 函数允许你给一个或多个参数设置固定的值,减少接下来被调用时的参数个数。为了演示清楚,假设你有下面这样的函数:
1 from functools import partial 2 3 def spam(a, b, c, d): 4 print(a, b, c, d) 5 6 print('s1'.center(30, '*')) 7 s1 = partial(spam, 1) #位置参数a的值为1 8 s1(2, 3, 4) 9 s1(10, 11, 20) 10 11 print('s2'.center(30, '*')) 12 s2 = partial(spam, d=100) #位置参数d的默认参数为10 13 s2(2, 3, 4) 14 s2(10, 11, 20) 15 16 print('s3'.center(30, '*')) 17 s3 = partial(spam, 1, 2, d=42) # a = 1, b = 2, d = 42 18 s3(3) 19 s3(4) 20 s3(5)
以上代码执行的结果为:
**************s1************** 1 2 3 4 1 10 11 20 **************s2************** 2 3 4 100 10 11 20 100 **************s3************** 1 2 3 42 1 2 4 42 1 2 5 42
总结:
可以看出partial() 固定某些参数并返回一个新的callable 对象。这个新的callable接受未赋值的参数,然后跟之前已经赋值过的参数合并起来,最后将所有参数传递给原始函数
将单方法的类转换为函数
问题:
你有一个除init () 方法外只定义了一个方法的类。为了简化代码,你想将它转换成一个函数
解决方案:
大多数情况下,可以使用闭包来将单个方法的类转换成函数。举个例子,下面示例中的类允许使用者根据某个模板方案来获取到URL 链接地址
1 from urllib.request import urlopen 2 3 class UrlTemplate: 4 def __init__(self, template): 5 self.template = template 6 7 def open(self, **kwargs): 8 return urlopen(self.template.format_map(kwargs)) 9 10 yahoo = UrlTemplate('http://finance.yahoo.com/d/quotes.csv?s={names}&f={fields}') 11 for line in yahoo.open(names='IBM,AAPL,FB', fields='sl1c1v'): 12 print(line.deocde('utf-8')) 13 14 #可以把上面的代码修改成闭包的方式 15 def urltemplate(template): 16 def opener(**kwargs): 17 return urlopen(template.format_map(kwargs)) 18 return opener 19 20 yahoo = urltemplate('http://finance.yahoo.com/d/quotes.csv?s={names}&f={fields}') 21 for line in yahoo(names='IBM,AAPL,FB', fields='sl1c1v') 22 print(line.deocde('utf-8'))
带额外状态信息的回调函数
问题:
你的代码中需要依赖到回调函数的使用(比如事件处理器、等待后台任务完成后的回调等),并且你还需要让回调函数拥有额外的状态值,以便在它的内部使用到
解决方案:
这一小节主要讨论的是那些出现在很多函数库和框架中的回调函数的使用——特别是跟异步处理有关的。为了演示与测试,我们先定义如下一个需要调用回调函数的函数:
1 #这玩法还是很牛逼的~ 2 def apply_async(func, args, *, callback): 3 #计算返回值 4 result = func(*args) 5 #用于回调的函数 6 callback(result) 7 8 def print_result(result): 9 print('Got:', result) 10 11 def add(x, y): 12 return x + y 13 14 apply_async(add, (2, 3), callback=print_result) 15 16 apply_async(add, ('hello', ',world'), callback=print_result)
以上代码执行的结果为:
Got: 5
Got: hello,world
内联回调函数
问题:
当你编写使用回调函数的代码的时候,担心很多小函数的扩张可能会弄乱程序控制流。你希望找到某个方法来让代码看上去更像是一个普通的执行序列
解决方案:
通过使用生成器和协程可以使得回调函数内联在某个函数中。为了演示说明,假设你有如下所示的一个执行某种计算任务然后调用一个回调函数的函数
1 from queue import Queue 2 from functools import wraps 3 4 5 def apply_async(func, args, *, callback): 6 result = func(*args) 7 8 callback(result) 9 10 11 class Async: 12 def __init__(self, func, args): 13 self.func = func 14 self.args = args 15 16 17 def inlined_async(func): 18 @wraps(func) 19 def wrapper(*args): 20 f = func(*args) 21 result_queue = Queue() 22 result_queue.put(None) 23 while True: 24 result = result_queue.get() 25 try: 26 a = f.send(result) 27 apply_async(a.func, a.args, callback=result_queue.put) 28 except StopIteration: 29 break 30 31 return wrapper 32 33 34 def add(x, y): 35 return x + y 36 37 @inlined_async 38 def test(): 39 r = yield Async(add, (2, 3)) 40 print(r) 41 r = yield Async(add, ('hello', ',world')) 42 print(r) 43 for n in range(10): 44 r = yield Async(add, (n, n)) 45 print(r) 46 print('Goodbye') 47 48 test()
以上代码执行的结果为:
5 hello,world 0 2 4 6 8 10 12 14 16 18 Goodbye
访问闭包中定义的变量
问题:
你想要扩展函数中的某个闭包,允许它能访问和修改函数的内部变量
解决方案:
通常来讲,闭包的内部变量对于外界来讲是完全隐藏的。但是,你可以通过编写访问函数并将其作为函数属性绑定到闭包上来实现这个目的。例如:
1 def sample(): 2 n = 0 3 def func(): 4 print('n=', n) 5 6 def get_n(): 7 return n 8 9 def set_n(value): 10 nonlocal n 11 n = value 12 13 func.get_n = get_n #等同于外面调用func().get_n() 14 func.set_n = set_n #等同于外部调用func()func.set_n(10) 15 return func 16 17 f = sample() #等于调用func 18 f() #等同于func() 19 f.set_n(10) #func.set_n等同调用set_n(10) 20 f() 21 print(f.get_n())#func.get_n等同调用get_n()
以上代码执行的结果为:
n= 0
n= 10
10