zoukankan      html  css  js  c++  java
  • python | 自定义函数

     

    1 函数的定义

    函数是一段具有特定功能的、可复用的语句组。python中函数用函数名来表示,并通过函数名进行功能调用。它是一种功能抽象,与黑盒类似,所以只要了解函数的输入输出方式即可,不用深究内部实现原理。函数的最突出优点是:

    • 实现代码复用:减少重复性工作
    • 保证代码一致:只需要修改该函数代码,则所有调用均能受影响

    在python中可以把函数分为:系统自带函数、第三方库函数、自定义函数。需要重点掌握的是「自定义函数」。

     
    自定义函数

    自定义函数语法:
    
    def 函数名([参数列表]):
    	函数体
        return语句
    
    # 示例
    def add1(x):
        x = x + 1
        return x
    

    函数通过「参数」和「返回值」来传递信息,并通过「参数列表」和「return语句」实现对两者的控制,详见下图:

    注意事项:

    • 函数定义时无需声明形参类型(由调用时的实参类型确定);也无需指定返回值类型(由return语句确定)
    • 自定义函数即使没有任何参数,也必须保留一队空括号()
    • 括号后面的冒号(:)必不可少
    • 函数体相对于def关键字必须有缩进关系
    • python允许嵌套定义函数
    • return语句作用是结束函数调用,并将结果返回给调用者
      • return语句是可选的,可以出现在函数体任意位置
      • 无return语句、有return语句没有执行、有return语句而没有返回值三种情况,函数都返回None

     

    2 函数的调用

    在定义好函数之后,有两种方式对其进行调用:

    • 从本文件调用:直接使用函数名 + 传入参数,如add1(9)
    • 从其他文件调用:这种方法有两种实现手段
      • 先指定文件路径 + import 文件名,再用文件名.函数名(参数列表)调用
      • 先指定文件路径 + from 文件名 import 函数名,再用文件名.函数名(参数列表)调用
    # 从本文件调用
    def add1(x):
        x = x+2
        return x
    
    add1(10)
    
    # 从其他文件调用:从名为addx的文件调用已经定义好的add1函数
    import os
    os.chdir('D:\data\python_file') 
    
    # 从其他文件调用方法1
    import addx
    addx.add1(4)
    
    # 从其他文件调用方法2
    from addx import add1
    add1(9)
    

     

    3 函数的参数

    3.1 形参与实参

    从上面可知,函数最重要的三部分就是参数、函数体、返回值,而参数分为形参和实参:

    • 形参:定义函数时,函数名后面圆括号中的变量
    • 实参:调用函数时,函数名后面圆括号中的变量

    注意事项:

    1. 形参只在函数内部有效,一个函数可以没有形参,但必须有括号()
    2. 通常修改形参不影响实参;但如果传递给函数的是「可变序列」(列表、字典、集合),修改形参会影响实参
    def printmax(a,b):       # a, b是形参
        if a>b:
            print(a)
     printmax(3,4)           # 3, 4是实参
    
    # 形参修改不影响实参
    def add2(x):
        x = x+2
        return x
    
    x = 10
    print(add2(x))  
    print(x)
    
    # 形参修改影响实参
    def add2(x):              
        x.append(2)           
        return x
    
    y = [1, 1]                # 实参y是变序列(列表、字典、集合)
    print(add2(y))            # 函数返回值
    print(y)                  # 修改形参影响实参
    

     

    3.2 参数的传递

    在定义函数时无需指定形参类型,在调用函数时,python会根据实参类型来自动推断。而定义函数和调用函数的过程,可以简化为下面图中三步,实质就是通过参数和返回值传递信息,而参数的传递发生在第一和第三步。

    函数参数多种多样,根据参数传递发生的先后顺序,可以从两个角度学习常见的一些参数:

    • 定义函数(形参):默认值参数、可变参数
    • 调用函数(实参):位置参数、关键字参数、命名关键字参数

    同时,这些参数可以组合使用(可变参数无法和关键字参数组合),且参数定义的顺序从左至右分别是:位置参数 >> 默认值参数 >> 可变参数 / 关键字参数 / 命名关键字参数。参数传递还有一种高级用法——参数传递的序列解包。

     

    3.2.1 默认值参数

    默认参数就是在调用函数的时候使用一些包含默认值的参数。

    # 默认值参数b=5, c=10
    def demo(a, b=5, c=10):
        print(a, b, c)
    
    demo(1, 2)
    

    注意事项:

    • 默认值参数必须出现在参数列表最右端
    • 调用带有默认值参数的函数时,可以对默认值参数进行赋值,也可以不赋值
    • 默认值参数只能是不可变对象,使用可变序列作为参数默认值时,程序会有逻辑错误
    • 可以使用 “函数名.defaults” 查看该函数所有默认参数的当前值

     

    3.2.2 可变参数

    可变参数就是允许在调用参数的时候传入多个(≥0个)参数,可变参数分为两种情况:

    • 可变位置参数:定义参数时,在前面加一个*,表示这个参数是可变的,可以接受任意多个参数,这些参数构成一个「元组」,只能通过位置参数传递
    • 可变关键字参数:定义参数时,在前面加**,表示这个参数可变,可以接受任意多个参数,这些参数构成一个「字典」,只能通过关键字参数传递
    # 可变位置参数
    def demo(*p):
        print(p)
        
    demo(1, 2, 3)                 # 参数在传入时被自动组装成一个元组
    
     
    # 可变关键字参数
    def demo(**p):
        print(p)
    
    demo(b='2', c='5', a='1')     # 参数在传入时被自动组装成一个字典
    

     

    3.2.3 位置参数

    位置参数特点是调用函数时,要保证实参和形参的顺序一致、数量相同。

    # 位置参数
    def demo(a, b, c):
        print(a, b, c)
    
    demo(1, 2, 3)
    

     

    3.2.4 关键字参数

    关键字参数允许在调用时以字典形式传入0个或多个参数,且在传递参数时用等号(=)连接键和值。关键字参数最大优点,就是使实参顺序可以和形参顺序不一致,但不影响传递结果。

    # 关键参数
    def demo(a, b, c):
        print(a, b, c)
    
    demo(b=2, c=5, a=1)   # 改变参数顺序对结果不影响
    

     

    3.2.5 命名关键字参数

    命名关键字参数是在关键字参数的基础上,限制传入的的关键字的变量名。和普通关键字参数不同,命名关键字参数需要一个用来区分的分隔符*,它后面的参数被认为是命名关键字参数。

    # 这里星号分割符后面的city、job是命名关键字参数
    def person_info(name, age, *, city, job):
        print(name, age, city, job)
    
    person_info("Alex", 17, city="Beijing", job="Engineer")
    

     

    3.2.6 参数传递的序列解包

    参数传递的序列解包,是通过在实参序列前加星号(*)将其解包,然后按顺序传递给多个形参。根据解包序列的不同,可以分为如下5种情况:

    序列解包 示例
    列表的序列解包 *[3,4,5]
    元组的序列解包 *(3,4,5)
    集合的序列解包 *{3,4,5}
    字典的键的序列解包 若字典为dic={'a':1,'b':2,'c':3},则解包代码为:*dic
    字典的值的序列解包 若字典为dic={'a':1,'b':2,'c':3},则解包代码为:*dic.values()

    注意事项:

    • 对实参序列进行序列解包后,得到的实参值就变成了位置参数,要和形参一一对应
    • 当序列解包和位置参数同时使用时,序列解包相当于位置参数,且会优先处理
    • 序列解包不能在关键字参数解包之后,否则报错
    """函数参数的序列解包"""
    def demo(a, b, c):
        print(a+b+c)
    
    demo(*[3, 4, 5])       # 列表的序列解包
    demo(*(3, 4, 5))       # 元组的序列解包
    demo(*{3, 4, 5})       # 集合的序列解包
    
    dic = {'a': 1, 'b': 2, 'c': 3}
    demo(*dic)			   # 字典的键的序列解包				
    demo(*dic.values())    # 字典的值的序列解包
    
    """位置参数和序列解包同时使用"""
    def demo(a, b, c):  
        print(a, b, c)
    
    demo(*(1, 2, 3))       # 元组的序列解包
    demo(1, *(2, 3))       # 位置参数和序列解包同时使用
    demo(c=1, *(2, 3))     # 序列解包相当于位置参数,优先处理,正确用法
    demo(*(3,), **{'c': 1, 'b': 2})  # 序列解包必须在关键字参数解包之前,正确用法
    

     

    4 全局变量与局部变量

    变量起作用的代码范围称为「变量的作用域」。不同作用域内变量名可以相同,但互不影响。从变量作用的范围分类,可以把变量分类为:

    • 全局变量:指函数之外定义的变量,在程序执行全过程有效
    • 局部变量:指在函数内部使用的变量,仅在函数内部有效,当函数退出时变量将不存在

    需要特别指出的是,局部变量的引用比全局变量速度快,应考虑优先使用。

     
    全局变量声明

    有两种方式可以声明全局变量:

    • 方式一:在函数外声明
    • 方式二:在函数内部用global声明,又分为两种情况:
      • 情况1:变量已在函数外定义,使用global声明。若进行了重新赋值,则赋值结果会覆盖原变量值
      • 情况2:变量未在函数外定义,使用global在函数内部声明,它将增加为新的全局变量

    特殊情况,若局部变量和全局变量同名,那么全局变量会在局部变量的作用域内被隐藏掉。

    d = 2       # 全局变量
    def func(a, b):
        c = a*b
        return c
    
    func(2, 3)
    
    def func(a, b):
        c = a*b
        d = 2   # 局部变量
        return c
    
    func(2, 3)
    
    """声明的全局变量,已在函数外定义"""
    n = 1
    def func(a, b):
        global n
        n = b
        c = a*b
        return c
    
    s = func("knock~", 2)
    print(s, n)
    
    """声明的全局变量,未在函数外定义,则新增"""
    def func(a, b):
        c = a*b
        global d  # 声明d为全局变量
        d = 2
        return c
    
    func(2, 3)
    
    """局部变量和全局变量同名,则全局变量在函数内会被隐藏"""
    d = 10                   # 全局变量d
    def func(a, b):
        d = 3                # 局部变量d
        c = a+b+d
        return c
    
    func(1, 2)
    d
    

     

    5 lambda函数

    lambda函数,又称匿名函数,即没有函数名字临时使用的小函数。其语法如下:

    lambda 函数参数:函数表达式
    
    注意:
    	- 匿名函数只能有一个表达式
    	- 该表达式的结果,就是函数的返回值
    	- 不允许包含其他复杂语句,但表达式中可以调用其他函数
    

    lambda函数的使用场景,主要在两方面:

    • 尤其适用于需要一个函数作为另一个函数参数的场合,比如排序
    • 把匿名函数赋值给一个变量,再利用变量来调用该函数
    def f(x, y, z): 
        return x+y+z         # 位置参数
    f(1, 2, 3)
    
    def f1(x, y=10, z=10): 
        return x+y+z  		 # 默认值参数
    f(1)
    
    """把匿名函数赋值给一个变量,再利用变量来调用该函数,作用等价于自定义函数"""
    f=lambda x,y,z:x+y+z
    f(y=1,x=2,z=3)			 #关键值参数
    
    L = ['ab', 'abcd', 'dfdfdg', 'a']
    L.sort(key=lambda x: len(x))               # 按长度排序
    L
    
    L=[('小明',90,80),('小花',70,90),('小张',98,99)]
    L.sort(key=lambda x:x[1],reverse=True)     # 降序排序
    L
    

     

    6 递归

    在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。但不是的函数调用自己都是递归,递归有其自身的特性:

    • 必须有一个明确的递归结束条件,称为递归出口(基例
    • 相邻两次重复之间有紧密的联系,前一次要为后一次做准备(通常前一次的输出就作为后一次的输入)
    • 每一次递归,整体问题都要比原来减小,并且递归到一定层次时,要能直接给出结果

    从上图可知,递归过程是函数调用自己,自己再调用自己,...,当某个条件得到满足(基例)的时候就不再调用,然后再一层一层地返回,直到该函数的第一次调用。递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出,在Python中,通常情况下,这个深度是1000层,超过将抛出异常。

     
    案例:用递归实现阶乘

    # 案例一:用递归实现阶乘
    def fact(n):
        if n==0:
            return 1
        else:
            return n*fact(n-1)
    fact(5)
    
    # 案例二:实现字符串反转
    # 方法1,先转成列表,调用列表的revers方法,再把列表转成字符串
    s = 'abcde'
    l = list(s)
    l.reverse()
    ''.join(l)
    
    # 方法2,切片的方法
    s = 'abcde'
    s[::-1]
    
    # 方法3,递归的方法
    s = 'abc'
    def reverse1(s):
        if s == '':
            return s
        else:
            print(s)
            return reverse1(s[1:])+s[0]
    
    reverse1(s)
    
  • 相关阅读:
    【带着canvas去流浪(5)】绘制K线图
    【带着canvas去流浪(4)】绘制散点图
    【带着canvas去流浪】 (3)绘制饼图
    Mybatis缓存(1)--------系统缓存及简单配置介绍
    this引用逃逸
    MySQL优化(1)--------常用的优化步骤
    Java的内存模型
    Java Generator
    深入理解Java重载与重写
    对象的自我救赎
  • 原文地址:https://www.cnblogs.com/1k-yang/p/12324248.html
Copyright © 2011-2022 走看看