Loading

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

![image-20190816162459219](/Users/yull/Library/Application Support/typora-user-images/image-20190816162459219.png)

  • 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))
posted @ 2021-07-14 11:08  勤俭持家亮公子  阅读(97)  评论(0)    收藏  举报