函数
Python 的函数没有 return 语句, 隐式会返回一个 None 值
函数是可调用的对象, callable()
函数参数
参数调用时传入的参数要和定义的个数相匹配(可变参数例外)
位置参数
def f(x, y, z) 调用使用 f(1, 3, 5)
按照参数定义顺序传入实参
关键字参数
def f(x, y, z) 调用使用 f(x=1, y=3, z=5)
使用形参的名字来出入实参的方式, 如果使用了形参名字, 那么传参顺序就可和定义顺序不同
传参
要求位置参数必须在关键字参数之前传入, 位置参数是按位置对应的
函数参数默认值
定义时, 在形参后跟上一个值
作用
参数的默认值可以在未传入足够的实参的时候, 对没有给定的参数赋值为默认值
参数非常多的时候, 并不需要用户每次都输入所有的参数, 简化函数调用
可变参数
一个形参可以匹配任意个参数
在形参前使用 * 表示该形参是可变参数, 可以接收多个实参
收集多个实参为一个 tuple
关键字参数的可变参数
形参前使用 ** 符号, 表示可以接收多个关键字参数
收集的实参名称和值组成一个字典
有位置可变参数和关键字可变参数
位置可变参数在形参前使用一个星号 *
关键字可变参数在形参前使用两个星号 **
位置可变参数和关键字可变参数都可以收集若干个实参, 位置可变参数收集形成一个 tuple, 关键字可变参数收集形成一个 dict
混合使用参数的时候, 可变参数要放到参数列表的最后, 普通参数需要放到参数列表前面, 位置可变参数需要在关键字可变参数之前
keyword-only 参数
如果在一个星号参数后, 或者一个位置可变参数后, 出现的普通参数, 实际上已经不是普通的参数了, 而是 keyword-only 参数
- def fn(*args, x):
- print(x)
- print(args)
- def(**kwargs, x):
- print(x)
- print(kwargs)
直接报语法错误
可以理解为 kwargs 会截获所有的关键字参数, 就算你写了 x=5,x 也永远得不到这个值, 所以语法错误
keyword-only 参数另一种形式
- def fn(*, x,y):
- print(x,y)
- fn(x=5,y=6)
* 号之后, 普通形参都变成了必须给出的 keyword-only 参数
可变参数和参数默认值
- def fn(*args, x=5):
- print(x)
- print(args)
参数规则
参数列表参数一般顺序是, 普通参数, 缺省参数, 可变位置参数, keyword-only 参数(可带缺省值), 可变关键字参数
参数解构
给函数提供实参的时候, 可以在集合类型前使用 * 或者 **, 把集合类型的结构解开, 提取出所有元素作为函数的实参
非字典类型使用 * 解构成位置参数
字典类型使用 ** 解构成关键字参数
提取出来的元素数目要和参数的要求匹配, 也要和参数的类型匹配
参数解构和可变参数
给函数提供实参的时候, 可以在集合类型前使用 * 或者 **, 把集合类型的结构解开, 提取出所有元素作为函数的实参
函数返回值与作用域
函数的返回值
Python 函数使用 return 语句返回 "返回值"
所有函数都有返回值, 如果没有 return 语句, 隐式调用 return None
return 语句并不一定是函数的语句块的最后一条语句
一个函数可以存在多个 return 语句, 但是只有一条可以被执行. 如果没有一条 return 语句被执行到, 隐式调用 return None
如果有必要, 可以显示调用 return None, 可以简写为 return
如果函数执行了 return 语句, 函数就会返回, 当前被执行的 return 语句之后的其它语句就不会被执行了
作用: 结束函数调用, 返回值
返回多个值
函数不能同时返回多个值
return [1, 3, 5] 是指明返回一个列表, 是一个列表对象
return 1, 3, 5 看似返回多个值, 隐式的被 python 封装成了一个元组
- def showlist():
- return 1, 3, 5
x, y, z = showlist() # 使用解构提取更为方便
函数嵌套
在一个函数中定义了另外一个函数
函数有可见范围, 这就是作用域的概念
内部函数不能在外部直接使用, 会抛 NameError 异常, 因为它不可见
作用域
全局作用域
在整个程序运行环境中都可见
局部作用域
在函数, 类等内部可见
局部变量使用范围不能超过其所在的局部作用域
外层变量作用域在内层作用域可见
内层作用域 inner 中, 如果定义了 o=97, 相当于当前作用域中重新定义了一个新的变量 o, 但是这个 o 并没有覆盖外层作用域 outer 中的 o
全局变量 global
- #x = 5
- def foo():
- global x
- x = 10
- x += 1 # 报错吗?
- print(x) # 打印什么?
- print(x) #打印什么?
使用 global 关键字的变量, 将 foo 内的 x 声明为使用外部的全局作用域中定义的 x
但是, x = 10 赋值即定义, 在内部作用域为一个外部作用域的变量 x 赋值, 不是在内部作用域定义一个新变量, 所以 x+=1 不会报错. 注意, 这里 x 的作用域还是全局的
x+=1 这种是特殊形式产生的错误的原因? 先引用后赋值, 而 python 动态语言是赋值才算定义, 才能被引用. 解决办法, 在这条语句前增加 x=0 之类的赋值语句, 或者使用 global 告诉内部作用域, 去全局作用域查找变量定义
内部作用域使用 x = 5 之类的赋值语句会重新定义局部作用域使用的变量 x, 但是, 一旦这个作用域中使用 global 声明 x 为全局的, 那么 x=5 相当于在为全局作用域的变量 x 赋值
global 使用原则
外部作用域变量会内部作用域可见, 但也不要在这个内部的局部作用域中直接使用, 因为函数的目的就是为了封装, 尽量与外界隔离
如果函数需要使用外部全局变量, 请使用函数的形参传参解决
一句话: 不用 global. 学习它就是为了深入理解变量作用域
闭包(重要概念)
自由变量: 未在本地作用域中定义的变量. 例如定义在内存函数外的外层函数的作用域中的变量
闭包: 就是一个概念, 出现在嵌套函数中, 指的是内层函数引用到了外层函数的自由变量, 就形成了闭包. 很多语言都有这个概念, 最熟悉就是 JavaScript
使用 global 可以解决, 但是这使用的是全局变量, 而不是闭包
如果要对普通变量的闭包, Python3 中可以使用 nonlocal
nonlocal 关键字
使用了 nonlocal 关键字, 将变量标记为不在本地作用域定义, 而在上级的某一级局部作用域中定义, 但不能是全局作用域中定义
默认值的作用域
函数也是对象, python 把函数的默认值放在了属性中, 这个属性就伴随着这个函数对象的整个生命周期
属性 defaults 中使用元组保存所有位置参数默认值, 它不会因为在函数体内使用了它而发生改变
属性 kwdefaults 中使用字典保存所有 keyword-only 参数的默认值
使用可变类型作为默认值, 就可能修改这个默认值
有时候这个特性是好的, 有的时候这种特性是不好的, 有副作用
第一种方法
使用影子拷贝创建一个新的对象, 永远不能改变传入的参数
- def foo(xyz=[], u='abc', z=123):
- xyz = xyz[:] # 影子拷贝
- xyz.append(1)
- print(xyz)
- foo()
- print(foo.__defaults__)
- foo()
- print(foo.__defaults__)
- foo([10])
- print(foo.__defaults__)
- foo([10,5])
- print(foo.__defaults__)
函数体内, 不改变默认值
xyz 都是传入参数或者默认参数的副本, 如果就想修改原参数, 无能为力
第二种方法
通过值的判断就可以灵活的选择创建或者修改传入对象
这种方式灵活, 应用广泛
很多函数的定义, 都可以看到使用 None 这个不可变的值作为默认参数, 可以说这是一种惯用法
- def foo(xyz=None, u='abc', z=123):
- if xyz is None:
- xyz = []
- xyz.append(1)
- print(xyz)
- foo()
- print(foo.__defaults__)
- foo()
- print(foo.__defaults__)
- foo([10])
- print(foo.__defaults__)
- foo([10,5])
- print(foo.__defaults__)
使用不可变类型默认值
如果使用缺省值 None 就创建一个列表
如果传入一个列表, 就修改这个列表
变量名解析原则 LEGB
Local, 本地作用域, 局部作用域的 local 命名空间. 函数调用时创建, 调用结束消亡
Enclosing,Python2.2 时引入了嵌套函数, 实现了闭包, 这个就是嵌套函数的外部函数的命名空间
Global, 全局作用域, 即一个模块的命名空间. 模块被 import 时创建, 解释器退出时消亡
Build-in, 内置模块的命名空间, 生命周期从 python 解释器启动时创建到解释器退出时消亡. 例如 print(open),print 和 open 都是内置的变量
所以一个名词的查找顺序就是 LEGB
函数的销毁
全局函数销毁
重新定义同名函数
del 语句删除函数对象
程序结束时
局部函数销毁
重新在上级作用域定义同名函数
del 语句删除函数名称, 函数对象的引用计数减 1
上级作用域销毁时
递归
函数直接或者间接调用自身就是递归
递归需要有边界条件, 递归前进段, 递归返回段
递归一定要有边界条件
当边界条件不满足的时候, 递归前进
当边界条件满足的时候, 递归返回
递归要求
递归一定要有推出条件, 递归调用一定要执行这个退出条件. 没有退出条件的递归调用, 就是无限调用
递归调用的深度不宜过深
Python 对递归调用的深度做了限制以保护解释器
超过递归深度限制, 抛出 RecursionError maxinum recursion depth exceeded 超出最大深度 sys.getrecursionlimit()
递归的性能
循环稍微复杂一些, 但是只要不是死循环, 可以多次迭代直至算出结果
递归有深度限制, 如果递归复杂, 函数反复压栈, 占内存很快就溢出了
间接递归
通过别的函数调用了函数自身
但是, 如果构成了循环递归调用是非常危险的, 但是往往在代码复杂的情况下, 还是可能发生这种调用. 要用代码的规范来避免这种递归调用的发生
总结
递归是一种很自然地表达, 符合逻辑思维
递归相对运行效率低, 每一次调用函数都要开辟栈帧
递归有深度限制, 如果递归层次太深, 函数反复压栈, 栈内存很快就溢出了
如果是有限次数的递归, 可以使用递归, 或者使用循环代替, 循环代码稍微复杂一些, 但是只要不是死循环, 可以多次迭代直至算出结果
绝大多数递归, 都可以使用循环实现
即使递归代码很简洁, 但是能不用则不用递归
匿名函数
使用 Lambda 表达式构建匿名函数
格式
lambda 参数列表: 表达式
使用 lambda 关键字来定义匿名函数
参数列表不需要小括号
冒号是用来分割参数列表和表达式的
不需要使用 return, 表达式的值, 就是匿名函数返回值
lambda 表达式 (匿名函数) 只能写在一行上, 被称为单行函数
用途
在高阶函数传参时, 使用 lambda 表达式, 往往能简化代码
- [x for x in (lambda *args: map(lambda x: x+1, args))(*range(5))]
- [x for x in (lambda *args: map(lambda x: (x+1,args), args))(*range(5))]
Python 第四周 学习笔记(1)
来源: http://www.bubuko.com/infodetail-2562589.html