云计算百科
云计算领域专业知识百科平台

Python基础-第 5 章 函数

第 5 章 函数

5.1 语法

  • 函数必须先定义再调用

def 函数名 (参数列表) :
函数体
[return]

[return]代表 return可以不加,默认返回None

5.2 函数的参数

5.2.1 形参和实参

  • 在定义函数时,形参没有分配存储空间,也没有值,相当于一个占位符;

在调用函数时, 会在栈区中给函数分配存储空间, 然后给形参/局部变量分配存储空间,传递的是实际的数据

  • 当函数执行结束,函数所占的栈空间会被释放,函数的形参/局部变量也会被释放

程序的执行逻辑:

在这里插入图片描述

  • 在堆内存首先创建 模版对象 –就是py文件

  • 创建函数对象

  • 函数对象 指向 模版对象 (存储地址)

  • 程序调用, 在 栈内存 传递 实参 的 地址,进行执行

  • 执行完毕后,栈内存空间释放

  • 5.2.2 函数的参数传递

    1)指针

    a=10

    a=“helloworld”

    以上代码中,10是数字类型," helloworld " 是 String 类型,而变量 a 是没有类型,她仅仅是一个对象的引用(一个指针),可以是指向数字类型对象,也可以是指向 String 类型对象。

    2)引用

    在 Python 中,变量和数据是分开存储的,数据保存在内存中的一个位置,变量中保存着数据在内存中的地址,变量中记录数据的地址,就叫做引用。

    3)可变(mutable)与不可变(immutable)类型对象

    在Python常见的类型中,数字类型、string、tuple是不可更改的对象,而list、set、dict等则是可以修改的对象。

    • 不可变类型

    变量赋值 a=500 后再赋值 a=1000,这里实际是新生成一个 int 值对象 1000,再让 a 指向它,而500被丢弃,不是改变a的值,相当于新生成了a。类似c++的值传递,

    def change_int(a):
    print(f"2在函数体中修改前值:{a},地址{id(a)}")
    a = 100
    print(f"3在函数体中修改后值:{a},地址{id(a)}")

    b = 10
    print(f"1在函数外调用函数前值:{b},地址{id(b)}")
    change_int(b)
    print(f"4在函数体外调用函数后值:{b},地址{id(b)}")

    值改变,地址改变

    1在函数外调用函数前值:10,地址140707877808856
    2在函数体中修改前值:10,地址140707877808856
    3在函数体中修改后值:100,地址140707877811736
    4在函数体外调用函数后值:10,地址140707877808856

    • 可变类型

    变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了。类似c++的引用传递,如列表,字典。如 fun(la),则是将 la 真正的传过去,修改后fun外部的la也会受影响

    def change_int(list1):
    print(f"2在函数体中修改前值:{list1},地址{id(list1)}")
    list1[2] = 666
    print(f"3在函数体中修改后值:{list1},地址{id(list1)}")

    list3 = [10,20,30]
    print(f"1在函数外调用函数前值:{list3},地址{id(list3)}")
    change_int(list3)
    print(f"4在函数体外调用函数后值:{list3},地址{id(list3)}")

    集体值发生变化,地址不变

    1在函数外调用函数前值:[10, 20, 30],地址1873351414144
    2在函数体中修改前值:[10, 20, 30],地址1873351414144
    3在函数体中修改后值:[10, 20, 666],地址1873351414144
    4在函数体外调用函数后值:[10, 20, 666],地址1873351414144

    在这里插入图片描述

    执行逻辑:

  • 生成模版对象,.py, 生成模块命名空间 __dict__, 里边存储各种对象的地址

  • 生成函数对象 0x1234 和 0x2345, 模版对象中存储/指向 函数对象的地址

  • 程序执行的时候,在左侧栈内存中运行,调用函数,先执行赋值操作,list1 = list3 = 0x2345,让list1指向了0x2345地址

  • list1[2] = 666,会创建一个666对象(小整数池中大小为 [-5~256]),然后直接在0x2345中的 第三个值0x3030处修改它的地址为666

  • 5.2.3 var1*=2和var1=var1*2的区别

    • var1 *= 2使用原地址。

    • var1 = var1 * 2开辟了新的空间。

    • var1 = var1 * 2:

    def change_int(list1):
    print(f"2在函数体中修改前值:{list1},地址{id(list1)}")
    list1 = list1 *2
    print(f"3在函数体中修改后值:{list1},地址{id(list1)}")

    list3 = [10,20,30]
    print(f"1在函数外调用函数前值:{list3},地址{id(list3)}")
    change_int(list3)
    print(f"4在函数体外调用函数后值:{list3},地址{id(list3)}")

    地址发生改变:

    1在函数外调用函数前值:[10, 20, 30],地址2302819176832
    2在函数体中修改前值:[10, 20, 30],地址2302819176832
    3在函数体中修改后值:[10, 20, 30, 10, 20, 30],地址2302821366784
    4在函数体外调用函数后值:[10, 20, 30],地址2302819176832

    • var1*=2:

    def change_int(list1):
    print(f"2在函数体中修改前值:{list1},地址{id(list1)}")
    list1 *= 2
    print(f"3在函数体中修改后值:{list1},地址{id(list1)}")

    list3 = [10,20,30]
    print(f"1在函数外调用函数前值:{list3},地址{id(list3)}")
    change_int(list3)
    print(f"4在函数体外调用函数后值:{list3},地址{id(list3)}")

    地址不变:

    1在函数外调用函数前值:[10, 20, 30],地址2274329563520
    2在函数体中修改前值:[10, 20, 30],地址2274329563520
    3在函数体中修改后值:[10, 20, 30, 10, 20, 30],地址2274329563520
    4在函数体外调用函数后值:[10, 20, 30, 10, 20, 30],地址2274329563520

    5.2.4 函数的参数形式

    1)必须参数

    基于位置把每个相应位置的实参和形参相关联,调用时的数量必须和声明时的一样。

    def func(a, b, c):
    print(a, b, c)
    func(1, 2, 3) # 1 2 3

    2)关键字参数

    函数调用使用关键字参数来确定每个变量传入的参数值,使用关键字参数允许函数调用时参数的顺序与声明时不一致。

    def printInfo(name,age) :
    print("姓名:",name)
    print("年龄:",age)

    # Python解释器可以通过age和name这样的关键字去和形参进行匹配
    printInfo(age = 18,name = "zhangsan")

    3)默认值参数

    非默认参数必须放在默认参数之前

    def printInfo(name,age = 20) :
    print("姓名:",name)
    print("年龄:",age)

    printInfo("zhangsan")

    printInfo("lisi",30)

    printInfo(age = 40,name = "wangwu")

    4)不定长参数

    • 形式一: *参数名, 这个可以接受多个参数,将多个参数放到元组中处理

    def print_info(num,*var,age):
    print(num)
    print(var)

    print_info(10,20,30,age=40)

    20和30都传给var,如果要给age传值,此时必须用关键词传参

    • 形式二: **参数名,以字典的形式处理,后面不能再处理普通函数了

    def print_info(num,**var):
    print(num)
    print(var)

    print_info(10,a=20 ,b=30)

    10
    {'a': 20, 'b': 30}

    4)解包传参

    形式一: 解包非字典,用 *

    def print_info(a,b,c):
    print(a,b,c)

    tup1 = (10,20,30)
    print_info(*tup1)

    形式二: 解包字典

    def print_info(a,b,c):
    print(a,b,c)

    dic = {"a":1,"b":2,"c":3}
    print_info(**dic)
    #print_info(a=1,b=2,c=3)
    print_info(*dic)

    1个*代表解包key,2个代表解包value,此外,解包相当于注释操作,因此形参和实参必须一致,如果为aa会报错

    5.2.5 强制使用位置参数或关键字参数

    / 前的参数必须使用位置传参,* 后的参数必须用关键字传参。

    def print_info(a,b,c,/,c,d,e,*,f,g):
    print(a,b,c,d,e,f,g)

    print_info(10,20,30,40,50,f = 60,g=70)

    5.2.6 防止函数修改列表/深拷贝

    5.2.6.1浅拷贝:

    正常情况下:

    def change_list(list1):
    print(f"2在函数体中修改前列表的值{list1},地址{id(list1)}")
    list1[2] = 666
    print(f"3在函数体中修改前列表的值{list1},地址{id(list1)}")

    list2 = [10,20,30]
    print(f"1在函数体外修改前列表的值{list2},地址{id(list2)}")
    change_list(list2)
    print(f"4在函数体外修改前列表的值{list2},地址{id(list2)}")

    1在函数体外修改前列表的值[10, 20, 30],地址2306436239744
    2在函数体中修改前列表的值[10, 20, 30],地址2306436239744
    3在函数体中修改前列表的值[10, 20, 666],地址2306436239744
    4在函数体外修改前列表的值[10, 20, 666],地址2306436239744

    修改了外部的列表的值,为了防止修改,我们采用了copy方法:

    def change_list(list1):
    print(f"2在函数体中修改前列表的值{list1},地址{id(list1)}")
    list1[2] = 666
    print(f"3在函数体中修改前列表的值{list1},地址{id(list1)}")

    list2 = [10,20,30]
    print(f"1在函数体外修改前列表的值{list2},地址{id(list2)}")
    change_list(list2.copy())
    print(f"4在函数体外修改前列表的值{list2},地址{id(list2)}")

    1在函数体外修改前列表的值[10, 20, 30],地址2747885228416
    2在函数体中修改前列表的值[10, 20, 30],地址2747887418368
    3在函数体中修改前列表的值[10, 20, 666],地址2747887418368
    4在函数体外修改前列表的值[10, 20, 30],地址2747885228416

    但如果是这样:

    def change_list(list1):
    print(f"2在函数体中修改前列表的值{list1},地址{id(list1)}")
    list1[3].append(400)
    print(f"3在函数体中修改前列表的值{list1},地址{id(list1)}")

    list2 = [10,20,30,[1,2,3]]
    print(f"1在函数体外修改前列表的值{list2},地址{id(list2)}")
    change_list(list2.copy())
    print(f"4在函数体外修改前列表的值{list2},地址{id(list2)}")

    1在函数体外修改前列表的值[10, 20, 30, [1, 2, 3]],地址2417730721984
    2在函数体中修改前列表的值[10, 20, 30, [1, 2, 3]],地址2417732909120
    3在函数体中修改前列表的值[10, 20, 30, [1, 2, 3, 400]],地址2417732909120
    4在函数体外修改前列表的值[10, 20, 30, [1, 2, 3, 400]],地址2417730721984

    值依旧放生了变化,因为它的执行逻辑是:

    在这里插入图片描述

    • 调用的时候,copy了一个新的对象,叫0x5678

    • 在执行的时候,追加了一个新的参数,修改的是0x3456,而原本list2指向的0x2345也指向0x3456,就会导致list2也发生变化。

    5.2.6.2 深拷贝

    这样就不会影响函数体外的数据

    import copy

    def change_list(list1):
    print(f"2在函数体中修改前列表的值{list1},地址{id(list1)}")
    list1[3].append(400)
    print(f"3在函数体中修改前列表的值{list1},地址{id(list1)}")

    list2 = [10,20,30,[1,2,3]]
    print(f"1在函数体外修改前列表的值{list2},地址{id(list2)}")
    change_list(copy.deepcopy(list2))
    print(f"4在函数体外修改前列表的值{list2},地址{id(list2)}")

    在这里插入图片描述

    5.3 函数的说明文档

    将注释写在函数体内第一行代表函数的说明文档, 将鼠标放在函数身上可以查看该函数的作用,也可以通过help函数来查看(显示的内容就是第一行注释)

    def adult(age):
    *"""该函数判断是否成年"""*
    * *if age >= 18:
    print("成年")
    else:
    print("未成年")
    help(adult)

    5.4 return

    • 不带表达式的 return 语句,返回 None。

    • 函数中如果没有 **return **语句,在函数运行结束后也会返回 None。

    • return 语句可以返回多个值,多个值会放在一个元组中。

    def f(a, b, c):
    return a, b, c, [a, b, c]
    print(f(1, 2, 3)) # (1, 2, 3, [1, 2, 3])

    5.5 变量的作用域

    在认识变量作用域前,需要先知道闭包

    5.5.1 闭包

    def outer():
    num = 10

    def inner():
    print(num)

    return inner

    *# outer()()*
    *#===== 相当于下边 ======*
    *#获取内函数对象*
    inn = outer()
    *#调用内函数*
    inn()

    会打印出来结果10,但正常来讲,会报错,为什么没有报错,这里用到了闭包

    在这里插入图片描述

    正常的执行逻辑如图:

    • 创建模块对象 .py

    • 执行到inn = outer()创建函数对象 -outer,模块对象的outer执行函数对象的地址0x1234,

    • outer()内部会返回inner,inn = outer()调用结束,会创建一个inner函数对象,在栈内存中执行,堆内存中的inn指向 inner对象

    • inn() 调用, 然后打印print(num),此时应该会报错,因为num实际只在outer函数对象中,但为什么没有报错?

    因为闭包延长了外层函数局部变量的生命周期,和内函数绑定,当内函数执行完毕之后,对应的局部变量才会被释放

    5.5.2 作用域

    分为

    • 内建作用域

    • *全局作用域 *

    • 嵌套作用域(闭包)

    • 局部作用域

    aa = int(1.1) *#内建作用域 Builtin*

    a = 10 *#全局作用域 Global*
    def outer():
    a = 20 *#嵌套作用域(闭包) Enclosing*
    * *def inner():
    a = 30 *#局部作用域 Local*
    * *print(a)
    return inner

    outer()()

    查找范围:局部作用域>嵌套作用域(闭包)>全局作用域>内建作用域

    在python中,只有类、模块、函数才会引入新的作用域, if/while/for/try不会引入新的作用域

    aa = 10
    if aa ==10:
    num1 = 20
    print(num1)

    print(num1)

    • 在if中定义的是全局作用域

    5.5.3 global 关键字

    如果在局部作用域中,要对全局变量进行赋值操作,python会默认创建新的局部变量

    var1 = 100
    def func1():
    *# 如果在局部作用域中,要对全局变量进行赋值操作,python会默认创建新的局部变量*
    * #var1 +=10 #会报错*

    * #如果在局部变量中 要修改全局变量*
    * *global var1
    var1 = 200
    print(f"局部{var1}")

    func1()
    print(f"全局{var1}")

    5.5.4 nonlocal 关键字

    同理,如果要在局部中修改嵌套变量,需要使用nonlocal:

    def outer():
    num1 = 10

    def inner():
    nonlocal num1
    num1 = 20
    print(f"局部{num1}")
    inner()
    print(f"嵌套{num1}")

    outer()

    5.6 递归

    求一个整数n的阶乘!

    def get_factorial(n):
    if n == 1:
    return 1
    else:
    return n * get_factorial(n – 1)

    print(get_factorial(5))

    在这里插入图片描述

    如果不添加if,每次调用,因为没有出栈,每次都会形成新的占据内存,一直到报错

    右侧每返回一个给它的上一个,出栈一个

    5.7 匿名函数

    就是bambda表达式

    def my_map(func,list1):
    list2 = []
    for item in list1:
    list2.append(func(item))
    return list2

    list33 =[10,20,30]

    print(my_map(lambda item:item *2,list33))

    匿名函数作为内置函数的参数

    • sorted()

    有三名学生的姓名和年龄,按年龄排序。

    student_list = [{"name": "zhang3", "age": 36}, {"name": "li4", "age": 14}, {"name": "wang5", "age": 27}]
    print(sorted(student_list, key=lambda x: x["age"]))

    • map()

    map() 函数对序列中元素逐一处理。

    map_result = map(lambda x: x * x, [0, 1, 3, 7, 9])
    print(list(map_result)) # [0, 1, 9, 49, 81]

    • filter()

    filter() 函数对序列中元素过滤。

    filter_result = filter(lambda x: x >= 0, [-0, -1, -3, 7, 9])
    print(list(filter_result)) # [0, 7, 9]

    • reduce()

    reduce() 函数对序列中元素进行累积。

    from functools import reduce

    reduce_result = reduce(lambda x, y: x * y, [1, 2, 3, 4, 5])
    print(reduce_result) # 120

    5.8 函数的注释

    写上注释,name.的时候能快速找到其中的方法

    # 普通的自定义函数:
    def dog(name, age, species):
    return (name, age, species)
    # 添加了注释的自定义函数:
    def dog(name:str, age:(1, 99), species:'狗狗的品种') -> tuple:
    return (name, age, species)
    print(dog.__annotations__)

    tuple是返回的类型

    查看这些注释可以通过自定义函数的特殊属性__annotations__获取,结果会以字典的形式返回

    {'name': <class 'str'>, 'age': (1, 99), 'species': '狗狗的品种', 'return': <class 'tuple'>}

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Python基础-第 5 章 函数
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!