python3 运维开发

Python3 函数的更高级的话题(一)

2018-11-24  本文已影响28人  运维开发_西瓜甜

本次我要给大家分享一些更加高级和不常见的函数定义与使用模式。

涉及到的内容包括默认参数、任意数量参数、强制关键字 参数、注解和闭包等。

* 表达式

我们知道在定义函数的时候,用 * 表达式来收集所有未指明的位置参数。

def avg(first, *rest):
    """求平均值"""
    return (first + sum(rest)) / (1 + len(rest))
    
# 简单使用
avg(1, 2)  # 1.5 
avg(1, 2, 3, 4)  # 2.5

当然在调用函数的时候,也可以使用 * 表达式来把一个序列类型数据中的元素一一解开。

def show_args(*args):
    print(args)

show_args(['a','b'])  # (['a', 'b'],)
show_args(*['a','b'])  # ('a', 'b')

print() 函数的另外一种用法

print(['a','b'], sep='\n') 
# ['a', 'b']

print(*['a','b'], sep='\n')
# a
# b

print(*'ab', sep='\n')
# a
# b

其实 print() 函数的原型是这样定义的

def print(self, *args, sep=' ', end='\n', file=None):
    pass

print() 函数中的 sep 关键字参数定义的是当打印多个参数时,它们中间的分隔符是什么

** 表达式

def foo(**kwargs):
    print(kwargs)  # 是一个字典

一个 * 参数只能出现在函数定义中最后一个位置参数的后面,而 ** 参数只能出现在最后一个参数都位置。
有一点要注意的是,在 * 参数后面其实还可以定义其他参数。下面就会用到。

在函数中实现强制关键字参数

有的时候你希望在调用函数的时候,必须用关键字参数,因为这样更易懂。
可以将要限定的强制关键字参数放到某个 *参数 或者单个 * 后面就能达到这种效果。
像下面这样。

def query_keyword(max_file, *, servers):
    pass

query_keyword(65535, servers=False)  # ok
query_keyword(65535)  # TypeError

使用函数注解

函数注解是为了让看这个函数源码的人更能清楚参数的类型和用法

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

python 解释器不会对这些注解添加任何的语义。它们不会被类型检查,运行时跟 没有加注解之前的效果也没有任何差距。

这些注解存储在函数的 __annotations__ 属性中。
print(add.__annotations__)

感受 return 的强大

def foo():
    return '千锋', 8, 1000000

name, *nums = foo()

print(name)  # 千锋
print(nums)  # [8, 1000]

其实在函数返回之前,先创建了一个元组,之后的赋值就是我们之前讲的元组解包

在定义函数的默认参数时,不要用可变类型的数据

假如你的确需要一个默认参数是一个可边类型的数据(比如列表)
可以把默认参数的值先定义为 None

def foo(a, b=None): 
    if b is None:
        b = []

验证同一性

假如你想在一个函数中判断使用者有没有给一个参数传参,你可能想到这样:

def spam(a, b=None)
    if not b:  # 判断是否是 False
        print("用户没有传递变量")

这样显然会有问题的,因为对于 python 来说, 长度为 0 的字符串 ''、列表 []、元组 ()、字典 {} 都会认为是 False, 并且数字 0 和 布尔值的 False 都认为是假的。
也就是说用户传入这些参数是属于合法的参数。

解决办法:

_no_value = object()
def spam(a, b=_no_value): 
    if b is _no_value:
        print("用户没有传递变量")

_no_valueobject() 的是个实例, 这样可以判断变量 b 的值和 _no_value 的值是否是同一个对象来判断用户是否传入了值。

匿名函数

在匿名函数中使用了一个变量的值,这会是很有意思的一件事。

li = [lambda n=30: n for n in range(10)]

现在你回答下面几个问题

  1. li[0] 是什么类型的对象?

li 是一个列表,其中的元素都是匿名函数

  1. li[0]()li[1]() 分别都是什么值?

其实 li[0]()li[1]() 的值都是 9

lambda 表达式中的 n 是一个自由变量,是在运行时给其绑定值,而不是在定义时就给其绑定值,这跟函数的默认值参数定义是不同的。
因此,在调用这个 lambda 表达式的时候,n 的值是执行时的值。
还有要考虑到, 在 Python 中只有在函数中定义的变量才是局部变量,其他都是全局的变量。 for 循环到最后 n 的值被绑定为 9, 所以 li 所用函数中的 n 的值在运行时都是 9

现在对 lambda 函数稍作修改就会有不一样的效果

funcs = [lambda n=n: n for n in range(10)]
funcs[0]()  # 0
funcs[1]()  # 1

等号左边的 n 是函数的形参,右边是函数的实参,实参也就是迭代的变量的值。
这里利用默认参数,就可以在定义函数时,把值绑定给变量。

未完, 待续...

上一篇下一篇

猜你喜欢

热点阅读