python函数
函数
函数定义
 由若干语句组成的语句块,函数名称、参数列表构成
 完成一定的功能
函数的作用
- 结构化变成对代码的基本封装,按照功能组织成一段代码
 - 封装的目的为了复用,减少冗余代码
 - 代码更加简洁,可读性高
 
函数的分类
- 内建函数,如max()
 - 库函数,如math.ceil()
 
函数定义
- def语句定义
 
def 函数名(参数列表):
    函数体(代码块)
    [return 返回值]
- 函数名就是标识符,命名要求和python一样
 - Python的函数如果没有return语句,隐式会返回一个None值
 - 定义中的参数列表成为形式参数,只是一种符号表达,简称形参
 
函数调用
- 函数定义,只是声明了一个函数,它不会被执行,需要调用
 - 调用的方式,就是函数名加上小括号,括号内写上参数
 - 调用时写的参数式实际参数,是实实在在传入的值,简称实参
 
函数举例
def add(x, y):
    result = x + y
    return result
    
out = add(4, 5)
print(out)
- 定义了一个函数叫add,接收两个参数
 - 计算的结果,通过返回值返回
 - 调用通过函数名ad加两个参数,返回值可使用变量接收
 - 定义需要在调用前,也就是调用时,已经被定义过了,否者会抛Error
 - 函数是可调用对象,callable()
 
函数参数
- 参数调用时传入的参数要和定义的个数匹配(可遍参数例外)
 
位置参数
- def(x , y, z)调用时使用f(1, 3, 5)
 - 按照参数定义顺序传入实参
 
关键值参数
- def f(x, y, z)调用时使用f(x=1, y=3, z=5)
 - 使用形参的名字来传入实参的方式,如果使用了形参名字,那么传参的顺序可以和定义顺序不同
 
参数默认值
- 定义时,在形参后跟上一个默认值
 
def add(x=4, y=5)
    return x+y
- 参数的默认值在未传入足够的实参时,对没有给定的参数传入默认值
 
可变参数
- 一个形参可以匹配任意个参数
 - 位置参数的可变参数
 
def add(*nums):
    sum = 0
    for x in nums:
        sum += x
    print(sum)
add(3, 6, 9) #调用
- 在形参前使用*表示该形参时可变参数,
 - 关键字参数的可变参数
 
def showconfig(**kwargs):
    for k,v in kwagrs.items():
        print('{}={}'.format(k,v))
        
showconfig(host='127.0.0.1', port='8080')
- 形参前使用**符号,表示可以接受多个关键字参数
 - 收集的实参名称和值组成一个字典
 - 可变参数的混合使用
 
def showconfig(username, *args, **kwargs):
    print(username)
    print(args)
    for k,v in kwargs.items():
        print('{}={}'.format(k,v))
        
# 调用,username按照位置参数取值,后面的先传递args的元组,然后传递给kwargs的字典
In [14]: showconfig('zhangsan', host='127.0.0.1', port='8080')
zhangsan
()
host=127.0.0.1
port=8080
- 
keyword-only参数
- 如果在一个星号参数后,或者一个位置可变参数(不可以是可变关键字参数)后出现的普通参数,实际上已经不是一个普通的参数了,而是keyword-only参数
 - 示例
 
In [1]: def fn(*args, x): # 注意*args顺序 ...: print(x) ...: print(args) ...: In [2]: fn(3, 5) # 用普通位置参数传参报错 --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-2-26e4b6779170> in <module>() ----> 1 fn(3, 5) TypeError: fn() missing 1 required keyword-only argument: 'x' In [3]: fn(3,5,x=7) # 需要用关键字参数x=7传入才可以 7 (3, 5)- 强制使用keyword-only参数
 
In [4]: def fn1(*, x, y): # 注意* ...: print(x, y) ...: In [5]: fn1(5, 6) # 使用普通位置参数传参报错 --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-5-3ff6e7039ab2> in <module>() ----> 1 fn1(5, 6) TypeError: fn1() takes 0 positional arguments but 2 were given In [6]: fn1(x=5, y=6) # 必须使用关键值参数才可以 5 6 - 
总结:
- 有位置可变参数和关键字可变参数
 - 位置可变参数在形参前使用一个*号定义
 - 关键字可变参数在形参前使用两个**号
 - 位置可变参数和关键字可变参数都可以收集若干个实参,位置可变参数收集形成一个tuple,关键字可变参数收集形成一个dict
 - 混合使用参数时,可变参数要放到参数列表的后面,普通参数需要放到参数列表的前面,位置可变参数需要放到关键字可变参数之前
 
 - 
函数参数顺序
- 普通位置参数---缺省参数---可变位置参数---keyword-only参数---可变关键值参数
 - 示例
 
In [7]: def fn(x, y, z=3, *args, m=4, n, **kwargs): # 注意形参的定义 ...: print(x, y, z, m, n) ...: print(args) ...: print(kwargs) ...: In [8]: fn(1,2,4) # 在*args后面的已经不是普通位置参数,而是keyword-only参数,所以必须指定k=v --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-8-1452997b9625> in <module>() ----> 1 fn(1,2,4) TypeError: fn() missing 1 required keyword-only argument: 'n' In [9]: fn(1,2,n=4) # n的值传入keyword-only参数,则可以 1 2 3 4 4 () {} In [10]: fn(1,2,10,11,n=4,t=11,q=12) # 1和2按照位置参数赋值给x和y # 10按照位置参数替换为z # 11为可变位置参数被*args接收为tuple类型 # m使用默认值,n=4为keyword-only参数赋值给n # t=11,q=12为可变关键值参数被**kwargs接收为dict类型 1 2 10 4 4 (11,) {'t': 11, 'q': 12} - 
参数解构
- 将复杂的数据结构解构成函数需要的参数
 - 示例
 
In [11]: def fn(x, y): ...: print(x+y) ...: In [12]: t1 = (1,6,8) In [13]: l1 = [8,3] In [14]: fn(t1[1], l1[0]) 14- 给函数提供实参的时候,可以在集合类型前使用*或者**,把集合类型的结构解开,提取出所有元素作为函数的实参
 - 非字典类型使用*结构成位置参数
 - 字典类型使用**结构成关键字参数
 - 提取出来的元素数目要和参数的要求匹配,也要和参数的类型匹配
 
In [45]: def add(x, y): ...: print(x+y) ...: In [46]: dict1 = {'x':5, 'y':8} In [47]: add(**dict1) 13 In [48]: add(*dict1.keys()) xy In [49]: add(*dict1.values()) 13 
函数嵌套
- 
作用
- 封装-数据隐藏,外部无法访问嵌套函数
 - 贯彻diy原则,减少重复代码
 - 闭包
 
In [2]: def outer(): ...: def inner(): ...: print('inner') ...: print('outer') ...: inner() ...: In [3]: outer() outer inner In [4]: inner() --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-4-bc10f1654870> in <module>() ----> 1 inner() NameError: name 'inner' is not defined - 
总结
- 函数有可见范围,也叫作用域
 - 内部函数不能直接被外部调用,会报NameError错误
 
 
函数作用域
- 一个标识符的可见范围,就是标识符的作用域
 
全局作用域
- 在整个程序运行环境中都可见
 
局部作用域
- 在函数、类等内部课件
 - 局部变量使用范围不能超过其所在的局部作用域
 
In [9]: def outer2():
   ...:     o = 65
   ...:     def inner():
   ...:         o = 97
   ...:         print("inner {}".format(o))
   ...:         print(chr(o))
   ...:     inner()
   ...:     print("outer {}".format(o))
   ...:
In [10]: outer2()
inner 97
a
outer 65
- 从上面的例子可以看出
- 外层变量作用域在内层作用域可见
 - 内层作用域inner中,如果定义了o=97,相当于当前作用域重新定义了一个新的变量o,这个o并没有覆盖外层作用域outer中的o
 
 
n [12]: x =5  # 定义全局作用域x
In [13]: def foo():
    ...:     x = x + 1  # 当本地作用域x同时出现在等号两边,意味着会查找本地作用域x,但此时x只是标识还没有定义,故会报错。
    ...:     print(x)
    ...:
In [14]: foo()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-14-c19b6d9633cf> in <module>()
----> 1 foo()
<ipython-input-13-b321e3951b12> in foo()
      1 def foo():
----> 2     x = x + 1
      3     print(x)
      4
UnboundLocalError: local variable 'x' referenced before assignment
问题:如何解决上面的问题?
- 定义全局作用域 global
 
In [21]:  x = 5
In [22]: def foo():
    ...:     global x
    ...:     x += 2
    ...:     print(x)
    ...:
In [23]: foo()
7
- 使用global关键字的变量,将foo内的x声明为外部的全局作用域
 - 全局作用域中一定要有x的定义,如没有会报not found
 
闭包
- 
自由变量:没有在本地作用域中定义的变量,例如定义在内层函数外的外层函数的作用域中的变量。
 - 
闭包:就是一个概念,出现在嵌套函数中,指的是内层函数引用到了外层函数的自由变量,就形成了闭包。
 - 
python2实现闭包示例:
 
In [4]: def counter():
   ...:     c = [0]
   ...:     def inc(): # 内部函数中使用到了c,这个c就是自由变量,此处就形成了闭包,这个c在调用结束后依旧存在
   ...:         c[0] += 1 # c已经在counter函数中定义了,此处引用的是c元素的值,不是重新定义变量,因此不会报错
   ...:         return c[0]
   ...:     return inc
   ...: foo = counter() # 返回callable对象
   ...: print(foo())
   ...: print(foo())
   ...: c = 100
   ...: print(foo())
   ...:
1
2
3
注意:下面这段代码
In [1]: def counter():
   ...:     count = 0
   ...:     def inc():
   ...:         count += 1
   ...:         return count
   ...:     return inc
   ...:
   ...:
In [2]: foo = counter()
In [3]: foo()
- 上面的代码会报错,使用global可以解决,但这使用的是全局变量,而不是闭包
 
In [5]: count = 0
   ...: def counter():
   ...:     count = 0
   ...:     def inc():
   ...:         global count
   ...:         count += 1
   ...:         return count
   ...:     return inc
   ...:
   ...:
In [6]: foo = counter()
In [7]: foo()
- 如果要对普通变量的闭包,python3中可以使用nonlocal
 
nonlocal关键字
- 使用了nonlocal关键字,将变量标记为在上一级作用域的局部变量,但不能是全局作用域的变量
 
In [9]: def counter():
   ...:     count = 0
   ...:     def inc():
   ...:         nonlocal count
   ...:         count += 1
   ...:         return count
   ...:     return inc
   ...:
   ...:
In [10]: foo = counter()
In [11]: foo()
Out[11]: 1
In [12]: foo()
Out[12]: 2
- count是外层函数的局部变量,被内部函数引用
 - 内部函数使用nonlocal关键字声明count变量在上一级作用域中
 - 可正常执行,且形成闭包
 
默认值作用域
In [14]: def foo(xyz=[]):
    ...:     xyz.append(1)
    ...:     print(xyz)
    ...:
In [15]: foo()
[1]
In [16]: foo()
[1, 1]
In [17]: xyz  # 当前作用域没有xyz变量
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-17-8714e0ef31ed> in <module>()
----> 1 xyz
NameError: name 'xyz' is not defined
- 为什么第二次调用foo函数打印的是[1,1]?
- 因为函数也是对象,python把函数的默认值放到了属性中,这个属性就伴随着这个函数对象的整个生命周期
 - 可以通过foo.__defaults__查看
 - 函数在内存中的地址没有变
 - 属性__defaults__中使用元组保存所有默认值,它不会因为在函数体内使用它而发声改变。
 
 - 常用示例
 
In [26]: def foo(xyz=None, u ='abc'):
    ...:     if xyz is None:
    ...:         xyz = []
    ...:     xyz.append(1)
    ...:     print(xyz)
    ...:
In [27]: foo()
[1]
In [28]: foo([10])
[10, 1]
使用None来创建一个列表
如果传入一个列表,就修改这个列表。
通过值的判断就可以灵活的选择创建或者修改传入的对象。
变量名解析原则LEGB

- L — Local(function);本地作用域,局部作用域的local命名空间,函数调用时创建,调用结束消亡
 - E— Enclosing;Python2.2时引入了嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间
 - G— Global;全局作用域,即一个模块的命名空间,模块被import时创建,解释器退出时消亡
 - B— Build-in;内置模块的命名空间,生命周期从Python解释器启动时创建到解释器退出时消亡,例如print
 
依据就近原则,从下往上,从里向外,依次寻找
(1)内部函数可以直接在函数外部调用么?   不行!
(2)调用外部函数后,内部函数可以在函数外部调用吗  不行!
(3)内部函数可以在函数内部调用吗   可以
(4)内部函数在函数内部调用时,是否有先后顺序 有先后顺序
函数的返回值
- python函数使用return语句返回“返回值”
 - 所有函数抖友返回值,如果没有return语句,隐式调用return None
 - return语句并不一定是函数的语句块的最后一条语句。
 - 一个函数可以存在多个return语句,但只有一条可以被执行
 - 如果函数执行了return语句,函数就会返回,当被执行的return语句之后的其他语句就不会被执行了。
 
递归 Recursion
- 函数直接或者间接调用自身就是递归
 - 递归需要有边界条件、递归前进段、递归返回段。否者就会进入无线调用
 - 当边界条件不满足的时候,递归前进
 - 当边界条件满足的时候,递归返回
 - 递归调用的深度不宜过深
- pythondUI递归做了深度限制,以保护解释器
 - 超过递归深度限制,抛出RecurisonError
 - 可以通过sys.getrecursionlimit()
 
 
斐波那契数递归实现
i = 0
pre = 0
nxt = 1
print(pre)
print(nxt)
def fib(n, pre=0, nxt=1):
    pre, nxt = nxt, pre + nxt
    print(nxt)
    if n == 2:
        return
    fib(n - 1, pre, nxt)
fib(10)
- 递归总结
- 递归是一种符合数学逻辑的表达
 - 递归相对运行效率低,每一次调用函数都要开辟栈帧
 - 递归有深度限制,如果递归层次太深,函数反复压栈,栈内存很快就溢出
 - 如果有限次数的递归,可以使用递归调用,或者使用循环代替。
 - 即使递归代码很简洁,但能不用则不用。
 
 
匿名函数
- 
python借助lambda表达式构建匿名函数
 - 
格式
- 
lambda 参数列表:表达式
lambda x:x**2
(lambda x:x**2)(4) # 调用,4是参数
foo = lambda x,y:(x+y) ** 2 # 不推荐这么用,这样的需求可以使用普通函数
 
 - 
 - 
总结
- 使用lambda关键字来定义匿名函数
 - 参数列表不需要小括号
 - 冒号是用来分割参数列表和表达式的
 - 不需要return,表达式的值就是匿名函数的表达式
 - lambda表达式只能写在一行上,右边必须是个表达式,多以被称为单行函数。
 
 
In [101]: (lambda x:x**2)(4)Out[101]: 16In [106]: print((lambda x, y=3: x + y)(5))8In [111]: print((lambda *args: [x for x in args])(*range(5)))[0, 1, 2, 3, 4]In [110]: print((lambda *args: (x for x in args))(*range(5)))<generator object <lambda>.<locals>.<genexpr> at 0x7ff77f729f50>
函数示例
- 字典扁平化处理
- 原字典:{'a': {'b': 1, 'c': 2}, 'd': {'e': 3, 'f': {'g': 4}}}
 - 目标字典:
 
 
# 递归实现src = {'a': {'b': 1, 'c': 2}, 'd': {'e': 3, 'f': {'g': 4}}}def bianping(src, targetkey=''):    for k, v in src.items():        if isinstance(v, dict):            bianping(v, targetkey=targetkey + k + '.')  # 递归调用        else:            target[targetkey + k] = vtarget = {}bianping(src)print(target)# 递归增加缺省值src = {'a': {'b': 1, 'c': 2}, 'd': {'e': 3, 'f': {'g': 4}}}# 增加dest的默认值def bianping(src, dest=None, targetkey=''):    if dest == None:        dest = {}    for k, v in src.items():        if isinstance(v, dict):            bianping(v, dest, targetkey=targetkey + k + '.')  # 递归调用        else:            dest[targetkey + k] = v    return destprint(bianping(src))

                
            
        
浙公网安备 33010602011771号