1:可接受任意数量参数的函数 *args
# 为了能让一个函数接受任意数量的位置参数,可以使用一个*参数 def avg(first, *args): return (first + sum(args)) / (1 + len(args)) print(avg(1, 2)) # 1.5 print(avg(1, 2, 3, 4)) # 2.5 # *args接收后面的所有的位置参数封装成一个元组到args这个变量里 # args是由所有其他位置参数组成的元组。然后我们在代码中把它当成了一个序列来进行后续的计算
# 接受任意数量的关键字参数的函数,**kwargs import html def make_element(name, value, **attrs): keyvals = [‘ %s="%s"‘ % item for item in attrs.items()] # [‘ size="large"‘, ‘ quantity="6"‘] attr_str = ‘‘.join(keyvals) # size="large" quantity="6" str类型 element = ‘<{name}{attrs}>{value}</{name}>‘.format( name=name, attrs=attr_str, value=html.escape(value)) return element # 创建 ‘<item size="large" quantity="6">Albatross</item>‘ print(make_element(‘item‘, ‘Albatross‘, size=‘large‘, quantity=6)) # 创建 ‘<p><spam></p>‘ print(make_element(‘p‘, ‘<spam>‘)) # **attrs接收所有的关键字参数封装成一个字典到attrs里面 # attrs是一个包含所有被传入进来的关键字参数的字典
# 同时接受任意数量的位置参数和关键字参数,同时使用*和** def anyargs(*args, **kwargs): print(args) # args是一个元组 print(kwargs) # kwargs是一个字典 使用这个函数时,所有位置参数会被放到args元组中,所有关键字参数会被放到字典kwargs中。
# 一个*参数只能出现在函数定义中最后一个位置参数后面,而 **参数只能出现在最后一个参数。在*参数后面仍然可以定义其他参数 def a(x, *args, y): pass
print(a(1, 2, 3, 4, 5, y=10)) # 只能这么调用 def b(x, *args, y, **kwargs): pass print(a(1, 2, 3, 4, 5, y=10, c=20, b=30)) # 只能这么调用 强制关键字参数
2:只接受关键字参数的函数 希望函数的某些参数强制使用关键字参数传递
# 将强制关键字参数放到某个*参数或者单个*后面就能达到这种效果 def recv(maxsize, *, block): pass # recv(1024) # TypeError 还需要传一个参数 # recv(1024, 2048, block=True) # TypeError 报错,*不是*args,*不接受参数只是强制后面得参数和关键字参数 recv(1024, block=True) # Ok # 在接受任意多个位置参数的函数中指定关键字参数 def minimum(*values, clip=None): m = min(values) if clip is not None: m = clip if clip > m else m return m print(minimum(1, 5, 2, -5, 10)) # -5 print(minimum(1, 5, 2, -5, 10, clip=0)) # 0 # *vaues接收多个位置参数,clip=None接收一个关键字参数
# 使用强制关键字参数会比使用位置参数表意更加清晰,程序也更加具有可读性 msg = recv(1024, False) # 如果调用者对recv函数并不是很熟悉,那他肯定不明白那个False参数到底来干嘛用的。 但是,如果代码变成下面这样子的话就清楚多了 msg = recv(1024, block=False)
# 使用强制关键字参数也会比使用**kwargs参数更好,因为在使用函数help的时候输出也会更容易理解
help(recv)
3:给函数参数增加元信息 写好了一个函数,然后想为这个函数的参数增加一些额外的信息,让其他使用者就能清楚的知道这个函数应该怎么使用
# 函数参数注解提示程序员应该怎样正确使用这个函数 def add(x: int, y: int) -> int: return x + y x是int,y是int ->返回是int # python解释器不会对这些注解添加任何的语义。它们不会被类型检查,运行时跟没有加注解之前的效果也没有任何差距。
然而,对于那些阅读源码的人来讲就很有帮助。第三方工具和框架可能会对这些注解添加语义。同时它们也会出现在文档中 输出: Help on function add in module __main__: add(x: int, y: int) -> int None
可以使用任意类型的对象给函数添加注解(例如数字,字符串,对象实例等等),不过通常来讲使用类或者字符串会比较好点
# 函数注解只存储在函数的 __annotations__ 属性中 def add(x: int, y: int) -> int: return x + y print(add.__annotations__) 输出: {‘x‘: <class ‘int‘>, ‘y‘: <class ‘int‘>, ‘return‘: <class ‘int‘>} # 注解的使用方法可能有很多种,但是它们的主要用途还是文档。 因为python并没有类型声明,
通常来讲仅仅通过阅读源码很难知道应该传递什么样的参数给这个函数。
这时候使用注解就能给程序员更多的提示,让他们可以正确的使用函数
4: 返回多个值的函数 构造一个可以返回多个值的函数 return一个元组就行了
# return一个元组就行了 def func(): return 1, 2, 3 a = func() print(a) # (1, 2, 3) a, b, c = func() print(a, b, c) # 1 2 3 # 尽管myfun()看上去返回了多个值,实际上是先创建了一个元组然后返回的。 # 这个语法看上去比较奇怪,实际上我们使用的是逗号来生成一个元组,而不是用括号 a = (1, 2) b = 1, 2 print(a == b, b) # True (1, 2) # 当我们调用返回一个元组的函数的时候 ,通常我们会将结果赋值给多个变量,
其实这就是元组解包。返回结果也可以赋值给单个变量, 这时候这个变量值就是函数返回的那个元组本身了 def myfun(): return 1, 2, 3 x = myfun() print(x) # (1, 2, 3) a, b, c = x print(a, b, c) # 1 2 3
5:定义有默认参数的函数 定义一个函数或者方法,它的一个或多个参数是可选的并且有一个默认值
# 定义一个有可选参数的函数是非常简单的,直接在函数定义中给参数指定一个默认值,并放到参数列表最后就行了 def spam(a, b=42): print(a, b) spam(1) # 1, 42 spam(1, 2) # 1, 2 # 如果默认参数是一个可修改的容器比如一个列表、集合或者字典,可以使用None作为默认值 # 使用列表作为默认值 def spam(a, b=None): if b is None: b = [] # 不想提供一个默认值,而是想仅仅测试下某个默认参数是不是有传递进来, _no_value = object() def spam(a, b=_no_value): if b is _no_value: print(‘No b value supplied‘) spam(1) 输出:No b value supplied spam(1, 2) # b = 2 spam(1, None) # b = None # 传递一个None值和不传值两种情况是有差别的
# 默认参数的值仅仅在函数定义的时候赋值一次后面不会复制了 x = 42 def spam(a, b=x): print(a, b) spam(1) # 1 42 x = 23 spam(1) # 1 42 # 改变x的值的时候对默认参数值并没有影响,这是因为在函数定义的时候就已经确定了它的默认值了
# 默认参数的值应该是不可变的对象,比如None、True、False、数字或字符串,不要写个列表[] def spam(a, b=[]): pass # 默认值b=[]这样写了,当默认值在其他地方被修改后你将会遇到各种麻烦。这些修改会影响到下次调用这个函数时的默认值 def spam(a, b=[]): print(b) return b x = spam(1) print(x) # 输出:[]现在函数返回的是[] x.append(99) x.append(‘hello‘) print(x) # 输出:[99, ‘hello‘] # 为了避免这种情况的发生,最好是将默认值设为None,
# 测试None值时使用 is 操作符是很重要的 def spam(a, b=None): if not b: # 不改用“b为无” b = [] print(b) # 尽管None值确实是被当成False, 但是还有其他的对象(比如长度为0的字符串、列表、元组、字典等)都会被当做False。 # 因此,上面的代码会误将一些其他输入也当成是没有输入。 spam(1) x = [] spam(1, x) # 无声错误。默认情况下会覆盖x值 spam(1, 0) # 无声错误。0被忽略 spam(1, ‘‘) # 无声的错误,忽略
# 一个函数需要测试某个可选参数是否被使用者传递进来。 这时候需要小心的是你不能用某个默认值比如None、
0或者False值来测试用户提供的值(因为这些值都是合法的值,是可能被用户传递进来的)。 因此,你需要其他的解决方案了
# 创建一个独一无二的私有对象实例,就像上面的_no_value变量那样。
在函数里面,你可以通过检查被传递参数值跟这个实例是否一样来判断。
这里的思路是用户不可能去传递这个_no_value实例作为输入。 因此,这里通过检查这个值就能确定某个参数是否被传递进来了
# object 是python中所有类的基类。 你可以创建 object 类的实例,
但是这些实例没什么实际用处,因为它并没有任何有用的方法, 也没有任何实例数据(因为它没有任何的实例字典,你甚至都不能设置任何属性值)。
你唯一能做的就是测试同一性。这个刚好符合要求,因为我在函数中就只是需要一个同一性的测试而已
6:定义匿名或内联函数 lambda表达式定义简单函数
add = lambda x, y: x + y add(2, 3) # 5 add(‘hello‘, ‘world‘) # ‘helloworld‘ # 这个lambda函数类似 def add(x, y): return x + y add(2, 3) # 5
# lambda表达式典型的使用场景是排序或数据reduce names = [‘David Beazley‘, ‘Brian Jones‘, ‘Raymond Hettinger‘, ‘Ned Batchelder‘] print(sorted(names)) # [‘Brian Jones‘, ‘David Beazley‘, ‘Ned Batchelder‘, ‘Raymond Hettinger‘] print(sorted(names, key=lambda name: name.split()[-1].lower())) # [‘Ned Batchelder‘, ‘David Beazley‘, ‘Raymond Hettinger‘, ‘Brian Jones‘] a = [5, 4, 2, 3, 1] a.sort() print(a) # [1, 2, 3, 4, 5] # sorted(xxx)不会改变原数据,会返回一个排序后的数据对象 # xxx.sort()会改变原始数据,
7:匿名函数捕获变量值 lambda定义了一个匿名函数,并想在定义时捕获到某些变量的值
x = 10 a = lambda y: x + y x = 20 b = lambda y: x + y print(a(10)) # 30 print(b(10)) # 30 # 都是返回30, # lambda表达式中的x是一个*变量, 在运行时绑定值,而不是定义时就绑定,这跟函数的默认值参数定义是不同的。
因此,在调用这个lambda表达式的时候,x的值是执行时的值,执行两个lambda匿名函数的数据x=20 x = 10 a = lambda y: x + y x = 20 b = lambda y: x + y print(a(10)) # 30 print(b(10)) # 30 x = 15 print(a(10)) # 25 x = 3 print(a(10)) # 3 # 匿名函数在定义时就捕获到值,将那个参数值定义成默认参数即可 x = 10 a = lambda y, x=x: x + y x = 20 b = lambda y, x=x: x + y print(a(10)) # 20 print(b(10)) # 30
类似函数定义下面情况
x = 10
def func1(y):
print(x+y)
x = 20
def func2(y):
print(x+y)
func1(10) # 30
func2(10) # 30
# x只有再调用的时候才会赋值,而不会在定义的时候赋值,所有是30,30
x = 10
def func1(y, x=x):
print(x+y)
x = 20
def func2(y, x=x):
print(x+y)
func1(10) # 20
func2(10) # 30
# x在定义的时候就被赋值了,而不是等到func1()被调用的时候赋值,所有书20,30
# 通过在一个循环或列表推导中创建一个lambda表达式列表,并期望函数能在定义时就记住每次的迭代值 funcs = [lambda x: x+n for n in range(5)] for f in funcs: print(f(0)) 输出: 4 4 4 4 4 # 实际效果是运行是n的值为迭代的最后一个值 列表生成式只是定义了5个lambda函数,n的值没有赋值到x+n的n里面去 定义五个lambda函数函数后现在n=4了,这个列表里面存储了5个lambda函数,for循环遍历取出每个lambda传递参数0,所以结果是0+4=4 # 如果想结果改变可以写个n的关键字参数 funcs = [lambda x, n=n: x+n for n in range(5)] for f in funcs: print(f(0)) 输出: 0 1 2 3 4 # 这样写每个lambda函数就会先把每个n的值定义好了,每个lambda存储的n的值都不同
8:减少可调用对象的参数个数