5.1 Python函数

函数简介

- 函数也是一个对象
- 函数用来保存一些可执行的代码,并且可以对这些语句进行多次调用
- 创建函数:
    def 函数名([形参1, 形参2,..., 形参n]):   ---> 形参:形式参数
        代码块
- 函数中的代码不会立即执行,需要调用才能执行
- 函数名要符合标识符规范(不能以数字开头)
例:
    def fn():
        pass
    
    与print()函数类比
- 函数的调用
    fn是函数对象,fn()调用函数
    print是函数对象,print()调用函数

函数的参数

- 形参(形式参数):定义函数时指定形参,相当于在函数内部声明了变量,但是并不赋值
    定义形参时,可以为形参指定默认值
    指定默认值以后,如果用户传递了实参则默认值没有作用
    如果用户没有给该形参传递实参,则默认值会生效
    例:
        def function(a, b, c=10)
            pass
                          ---> 形参c指定了默认值为10
- 实参(实际参数):调用函数时,传递实参(有几个形参就得传几个实参)
    1、位置参数:将对应位置的实参赋值给相应位置的形参
        第一个实参赋值给第一个形参,以此类推
        如:若定义函数 def fn(a, b, c),则调用时需要传位置参数: fn(1, 2, 3), 位置一一对应(a=1,b=2,c=3)
    2、关键字参数:可以不按照形参定义的顺序去传递
        如:若定义函数 def fn(a, b, c),则调用函数时可以全部使用关键字参数:fn(b=2,a=1,c=3),顺序不重要
    - 位置参数和关键字参数可以混合使用:此时必须将位置参数写在前面,如:
       def fn(a, b, c)  调用: fn(2,1,c=3) ---> a=2, b=1, c=3
    - 函数在调用时,解析器不会检查实参的类型,实参可以传递任意类型的对象
        甚至还可以传递函数,如定义函数def fn() , 那么可以这样调用:fn(fn),注意不是: fn(fn())
    因此,当传递一个可变对象时,如果不希望函数内部去改变外部变量指定的对象,则需要传递外部变量的副本而不是其本身,如:
        def fn(a):
            a[0] = 30 # a指向的对象被改变了
            print(a)
        c = [1,2,3,4] # c是列表,为可变对象,一旦调用函数fn(),则c和函数fn()内部变量a指向同一个对象
        fn(c) # 调用函数fn()后,c指向的对象也被改变: c = [30,2,3,4]
    如何避免外部可变对象被改变:
        传递副本: fn(c.copy())   或   fn(c[:])

不定长的参数

- 定义函数时,可以在形参前面加一个*,这样这个形参将会获得所有的实参,它会将所有实参保存到一个元组中
例:
    def fn(*a):
        print('a =', a, type(a)) ---> a是元组tuple
    
    调用: fn(1,2,3,4,5,6) ---> a = (1,2,3,4,5,6)  (这是一个装包的过程,与解包相反)
- 定义一个函数,可以求任意个数字的和:
    def sum(*nums):
        # 定义一个变量来保存结果
        result = 0
        # 遍历元组,将元组中的数进行累加
        for n in nums:
            result += n
        print(result)
    调用: sum(1,2,3,4,0,6,7) ---> 传入实参不定长
注意:带星号*的形参只能有一个,可以跟其他参数配合使用
     可变参数(带星号*)不是必须写在最后,但带*的参数后的所有参数,必须以关键字参数的形式传递:
    def fn(*a, b, c):  调用:fn(1,2,3,4,5,b=6,c=7) ---> a=(1,2,3,4,5)  b=6  c=7
    def fn(a, *b, c):  调用:fn(1,2,3,4,5,6,c=7) ---> a=1  b=(2,3,4,5,6)  c=7
    def fn(a, b, *c):  调用:fn(1,2,3,4,5,6,7) ---> a=1  b=2  c=(3,4,5,6,7)
特例:如果在形参的开头直接写一个*,则要求我们所有的参数<必须>以关键字参数的形式传递:(顺序不定)
    def fn(*, a, b, c):  调用:fn(a=1, b=2, c=3)
    
- *形参(只接收位置参数),而不能接收关键字参数
    def fn(*a):  调用:fn(a=1,b=2,c=3) ---> 报错,因为 *形参 不能接收关键字参数
    解决办法: **形参 可以接收其他的关键字参数,将它们统一保存在一个字典中,key:参数的名字,value:参数的值
        def fn(**a):  调用:fn(d=1,r=4,y=6) ---> a={'d':1, 'r':4, 'y':6}
  **形参(只接收关键字参数) 只能有一个,并且必须写在所有参数的后面
        def fn(b, c, **a):  调用:fn(b=1, d=10, c=3, j=34) ---> a={'d':10, 'j':34}, b=1, c=3

参数的解包

def fn(a, b, c):  调用: fn(t) ,其中t=(1,2,3) ---> 调用错误,参数不够
解决方案:
    1、fn(t[0], t[1], t[2]),其中t=(1,2,3) ---> 可以,但是很麻烦
    2、解包(拆包):   调用:fn(*t),其中t=(1,2,3) ---> 调用正确,此过程为解包(拆包),很简单
    注意:只要是序列就可以解包,并且要求序列中元素个数必须与形参个数一致
    *t  对元组等序列进行解包
    **t 对字典进行解包

返回值

- 函数执行后返回的结果
- 可以通过return来指定函数的返回值(可以返回任意对象,甚至可以返回一个函数)
- 可以直接使用函数的返回值,也可以通过变量来接收返回值
    例:def fn():
            def fn2():
                print('hello')
                
            return fn2
        r = fn()  ---> r 收到的返回值是函数fn2()
- 如果仅仅写retur,或者不写return,则相当于return None
- 在函数中,return后的代码都不会执行,return一旦结束函数自动结束(类似于循环中的break)
- fn 和 fn() 的区别:
    print(fn): fn是函数对象,打印fn实际上是打印函数对象,<function fn at 0x05771BB8>
    print(fn()): fn()是在调用函数,打印fn()实际上是在打印fn()函数的返回值
    (跟在return后面的值,多个值则以元组的形式返回) 

文档字符串

help()是python的内置函数,可以查询python中函数的用法
语法:
    help(函数对象)  例: help(print) ---> 获取函数print()的说明
那么,在自己写的函数中,怎么在函数内部编写文档字符串(即函数的说明)?
- 文档字符串(doc str)
    - 当编写了文档字符串(函数说明)时,就可以通过help()函数来查看该函数的说明
    - 文档字符串很简单,直接在函数的第一行写一个字符串就是文档字符串
例:
    def fn(a,b,c):
        '''
        这是一个文档字符串的示例:
        函数的作用: ...              ---> 三重单引号中间的字符串即为文档字符串(函数说明)
        函数的参数: ...
        '''
        return a,b,c
    
    help(fn)
    
- 另外,函数上有些描述(这些描述只起到提示作用,不强制要求,也不会报错):
    def fn(a:int, b:str='hello', c:list='[1,2,3]') -> int:
    解释:
        a:int ---> 表示参数a需要传入int类型实参,但也不是强制要求,输入其他类型也不报错
        b:str='hello' ---> 表示参数b需要传入str类型实参,且默认值是'hello'
        c:list='[1,2,3]' 同上
        -> int  ---> 表示函数fn()的返回值是int类型, 也是提示作用,没有强制要求
    以上设置,是为了使用help()函数查看说明时更加方便用户直观了解该函数,没有其他意义

作用域(scope)

- 作用域指的是变量生效的区域
- 在python中有两种作用域:
    1、全局作用域:
        - 在程序执行时创建,在程序执行结束时销毁
        - 所有函数以外的区域都是全局作用域
        - 在全局作用域中定义的变量都属于全局变量,可以在程序的任意位置被访问
    def fn():
        a = 1
        return   ---> a在函数内部,不是全局变量;但是fn是属于全局变量
    2、函数作用域
        - 在函数<调用>时创建,在调用结束时销毁(注意不是函数定义时)
        - 函数调用依次就会产生一个新的函数作用域
        - 在函数作用域中定义的变量,都是局部变量,只能在函数内部被访问
    3、变量的查找
        - 当我们使用变量时,会优先在当前作用域中寻找该变量,如果有则使用,
        如果没有则继续去上一级作用域中寻找,再没有,继续往上一级,以此类推。。。
        直到找到全局作用域依然没有找到,则抛出错误:NameError
        例1:
            def fn():
                a = 10
                def fn2():
                    a = 20
                    print('a =', a)
                fn2()
                
            调用fn()  ---> a = 20
            
        例2:
            a = 10
            def fn():
                 print('a =', a)
            
            调用fn()  ---> a = 10
        
        例3:
            a = 10
            def fn():
            a = 20
                 print('a =', a)
            
            调用fn()  ---> a = 20   
            
    4、global 关键字
        - 如果希望在函数内部修改全局变量,则需要使用global关键字来声明变量
            global a # 声明在函数内部使用的变量a是全局变量,此时再去修改a,则意味着在修改全局变量a
        例子:
            a = 10
            def fn():
                global a
                a = 31
                print('a =', a)
                
            调用fn()  ---> a = 31   ---> 全局变量a已被修改

命名空间(name space)

- 指的是变量存储的位置,每个变量都需要存储到指定的命名空间中
- 每一个作用域都会有一个对应的命名空间
- 全局命名空间用来保存全局变量,函数命名空间用来保存函数中的变量
- 命名空间实际上就是一个字典,是一个专门用来存储变量的字典

- locals() 用来获取当前作用域的命名空间
- 如果在全局作用域中调用locals()则获取全局命名空间;
    如果在函数作用域中调用loclas()则获取函数命名空间
- 返回的是一个字典
例:
    scope = locals() # 当前命名空间,为全局空间
    print(type(scope))  ---> 得到的是一个字典
- 向scope中添加一个key-value
    scope['c'] = 1000 # 向字典中添加key-value就相当于在全局中创建一个变量(一般不建议这么做)
- 同样,在函数内部调用locals()也可以获取到函数的命名空间
    也可以通过scope来操作函数的命名空间,但也不建议这么做
- global()函数可以用来在任意位置获取全局命名空间,但是在全局作用域是看不到函数内部的命名空间的

递归

- 问题:求 10!
    - 法一:for循环 ---> 此方法是常规方法
    - 法二:递归(简单理解就是自己去引用自己)
        递归函数:在函数中自己调用自己
        无穷递归:如果这个函数被调用,程序的内存会溢出,效果类似于死循环
        例:
            def fn():
                fn()   ---> 无穷递归
            fn()
        递归是解决问题的一种方式,跟循环很像
            整体思想是,将一个大问题分解为一个个小问题,直到问题无法分解,再去解决问题
        - 递归式函数的两个条件:
            1、基线条件
                - 问题可以被分解为最小问题,当满足基线条件时,递归结束
            2、递归条件
                - 将问题继续分解的条件
                10! = 10 * 9!
                9! = 9 * 8!
                8! = 8 * 7!
                ...
                2! = 2 * 1!
                1! = 1
        def factorial(n):
            '''
                该函数用来求任意数n的阶乘
                参数:
                    n  要求阶乘的数字
            '''
            # 基线条件  判断n是否为1,如果为1则此时不能再继续递归
            if n == 1:
                # 1的阶乘就是1,直接返回1
                return 1
                
            # 递归条件
            # return n * (n-1)! ---> 伪代码
            return n * factorial(n-1)
总结:递归和循环类似,基本上是可以相互代替的
        循环编写起来比较容易,阅读起来稍难
        递归编写起来难,但是阅读方便
练习:
    1、创建一个函数 power 来为任意数字做幂运算 n ** i
    2、创建一个函数,用来检查一个任意的字符串是否是回文字符串,是则返回True,否则返回False
        回文字符串:字符串从前往后念跟从后往前念是一样的,如'abcba'

函数式编程

- 在python中,函数是一等对象
- 一等对象一般会具有如下特点:
    1、对象是在运行时创建的
    2、能赋值给变量或作为数据结构中的元素
    3、能作为参数传递
    4、能作为返回值返回
    ---> 一般在python中几乎所有的对象都是一等对象
    
- 高阶函数
    - 高阶函数至少要符合以下两个特点之一:
        1、接收一个或多个函数作为参数
        2、将函数作为返回值返回
    例子:
    l = [1,2,3,4,5,6,7,8,9]
    def fn1(i):
        # 功能1
        if i > 2:
            return True
        return False
        
    def fn2(i):
        # 功能2
        if i % 3 == 0:
            return True
        return False
        
    注意,函数fn2的等价简单写法: (函数fn1同理)
        def fn2(i):
            return i % 3 == 0
        
    
    def fn(func, lst):
        '''
            函数功能:按照要求对列表进行修改然后返回
            函数参数:
                lst: 列表
                func: 传入的函数(每个函数均有特定功能)
        '''
        # 定义一个新的列表
        new_list = []
        
        for i in lst:
            if func(i):
                new_list.append(i)
        
        return new_list
        
    fn(fn1, l) # 调用函数fn1,实现功能1
    fn(fn2, l) # 调用函数fn2,实现功能2

匿名函数

- filter()   ---> 不是匿名函数
    - filter()可以从序列中过滤出符合条件(条件由函数来界定)的元素,保存到一个新的序列中
    语法: filter(函数, 序列)
    参数:
        1、函数,根据该函数来过滤序列(可迭代的结构)
        2、需要过滤的序列(可迭代的结构)
    返回值:
        过滤后的新序列(可迭代的结构)
    例子:
        l = [1,2,3,4,5,6]
        def fn(i):
            if i % 3 ==0:
                return True
            return False
            
        print(list(filter(fn, l)))  ---> fn作为filter的参数
- lambda (语法糖) ---> 不能实现复杂的功能
    (匿名函数一般是作为参数使用,其他时候不常用)
    语法:
        lambada 参数列表 : 函数列表 
    例:
        lambda a,b : a + b
        等价于下列函数:
        def fn(a, b):
            return a + b
        
        如何调用:
            1、(lambda a,b : a+b)(10, 20) ---> 将(10,20)赋值给(a,b) 
            2、常用的方法:
            l = [1,2,3,4,5,6]
                r = (lambda a : a > 5 , l)
                print(list(r))    
- zip() ---> zip()函数接收任意多个(包括0个和1个)序列作为参数,合并后返回一个tuple列表:
    a = [1, 2, 3]
    b = [4, 5, 6]
    ab = zip(a, b) ---> ab = <zip object at 0x0511DFC8>
    list(ab) ---> [(1,4), (2,5), (3,6)] # 需要加list()使得ab可视化
    
    - 可以解包进行迭代:
    for i,j in zip(a,b):
        print(i, j)

- map()
    - map()函数可以对可迭代对象中的所有元素做指定的操作,然后将其添加到一个新的对象中返回
    例:
        l = [1,2,3,4,5,6]
        r = map(lambda i: i ** 2, l) ---> 将列表l中的每个元素做平方运算
        print(list(r))
- sort()  ---> 方法
    - 该方法用来对[列表]中的元素进行排序,返回None,但是原来该列表的值会重新排列
    - sort()方法默认是直接比较列表中元素的大小
    - 在sort()中可以接收关键字参数,key
        key需要一个函数作为参数,当设置了函数作为参数
        每次都会以列表中的一个元素作为参数来调用函数,并使用函数的返回值来比较元素的大小
        例1:
            l = ['bb', 'aaaa', 'c', 'dddd', 'fff']
            l.sort(key=len) # 传入函数len(),进行字符串长度的比较
        例2:
            a = [2,4,3,'5','8']
            l.sort(key=int) # 传入函数int(),将所有字符转换为int类型再进行比较
- sorted() ---> 函数
    - 此函数和sort()方法的用法基本一致,但是sorted()可以对任意序列进行排序
        并且使用sorted()排序不会不会影响原来的对象,而是返回一个新的对象
    例:
        l = [2,4,3,'5','8']
        s = sorted(l, key=int) # 同样可以传入函数作为参数
        ---> 调用函数后,创建一个新的对象赋值给变量s,但是序列l没有被改变

闭包(不常用)

- 将函数作为返回值返回,也是一种高阶函数
- 这种高阶函数被称为闭包,通过闭包可以创建一些只有当前函数能访问的变量
    可以将一些私有的数据藏到闭包中

装饰器

# 创建几个函数
def add(a,b):
    # 求和
    return a+b

def mul(a,b):
    # 求积
    return a*b
# 希望函数在计算前,打印开始计算,计算结束后打印计算完毕
    可以通过修改函数中的代码来完成这个需求,但是会产生一些问题:
        1、如果要修改的函数过多,修改起来会比较麻烦
        2、不方便后期的维护
        3、这样做会违反开闭原则(OCP)
            程序的设计,要求开放对程序的扩展,要关闭对程序的修改
    希望在不修改原函数的情况下,来对函数进行扩展:
    def fn():
        print('我是fn函数...')
    
    # 只需要根据现有的函数,来创建一个新的函数
    def fn2():
        print('函数开始执行...')
        fn()
        print('函数执行结束...')
    fn2() # 调用fn2(),此时也会间接调用fn(),相当于对fn()进行了功能扩展
    # 再举个例子:
    def add(a,b):
    # 求和
    return a+b
    
    def new_add(a,b):  ---> 对add()函数进行功能扩展,但不修改add()的内容
        print('计算开始...')
        result = add(a,b)
        print('计算结束...')
        return result
    # 调用
    r = new_add(1,2)
    # 问题:如果要对mul()函数进行扩展,就需要再写一个函数new_mul(),很麻烦
    解决方法:---> 创建一个函数(能创建函数的函数),让这个函数可以自动帮助我们产生函数
    def begin_end(old):
        '''
            函数功能:
            用来对其他函数进行扩展,使其它函数可以在执行前打印开始执行,执行后打印执行结束
            
            参数:
                old 要扩展的函数对象
        '''
        # 创建一个新函数
        def new_function(a, b):
            print('函数开始执行...')
            # 调用被扩展的函数
            old()
            print('函数执行结束...')
        
            # 返回新函数
            return new_function
    
    # 调用函数
    f = begin_end(add)
    
    f()
    ........................
    太难了,而且不常用,这个部分不学了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值