轩小陌的Python笔记-day11 函数进阶

day11 函数进阶

在这里插入图片描述
目标:掌握函数相关易错点 & 项目开发必备技能。

今日概要:

  • 参数的补充
  • 函数名到底是什么?
  • 返回值和print,傻傻分不清楚。
  • 函数的作用域

1.参数的补充

在函数基础部分,我们掌握函数和参数基础知识,掌握这些其实完全就可以进行项目的开发。

今天的补充的内容属于进阶知识,包含:内存地址相关、面试题相关等,在特定情况下也可以让代码更加简洁,提升开发效率。

1.1 参数内存地址相关

查询某个参数的内存地址,可以使用Python内置函数 id():

v1 = "轩小陌"
addr = id(v1)
print(addr) 
>>输出结果:
2902636304592
v1 = [11,22,33]
v2 = [11,22,33]
print( id(v1) )
print( id(v2) )
>>输出结果:
2498192294080
2498192285568
v1 = [11,22,33]
v2 = v1
print( id(v1) )
print( id(v2) )
>>输出结果:
1571382902976
1571382902976

记住一句话:函数执行传参时,传递的是内存地址。
在这里插入图片描述

v1 = "轩小陌"
print(id(v1))  
>>输出结果:140247057684592

def func(data):
    print(data, id(data))  

func(v1)
>>输出结果:140247057684592

Python参数的这一特性有两个作用:

  • 节省内存

  • 对于参数为可变类型(列表、字典、集合),可通过函数对参数的内部元素进行修改。

    def func(data):
        data.append(999)
    
    v1 = [11,22,33]
    func(v1)
    print(v1) 
    >>输出结果:[11,22,33,666]
    
    # 如果在函数中对传入的参数进行了重新赋值,则会生成新的内存地址储存:
    def func(data):
        data = ["轩小陌","alex"]
        
    v1 = [11,22,33]
    func(v1)
    print(v1) 
    >>输出结果:[11,22,33]
    
    # 如果参数为不可变类型(字符串、元组),则无法通过函数修改内部元素,只能重新赋值:
    def func(data):
    	data = "alex"
        
    v1 = "轩小陌"
    func(v1)
    print(v1)
    >>输出结果:轩小陌
    
  • 其他很多编程语言执行函数时,传参时默认会将数据重新拷贝一份,这样会浪费内存。不过,其他语言也可以通过 ref 等关键字来实现传递内存地址。

  • 当然,如果你不想让外部的变量和函数内部参数的变量一致,也可以选择将外部值拷贝一份,再传给函数。

import copy

def func(data):
    data.append(999)

v1 = [11, 22, 33]
new_v1 = copy.deepcopy(v1) 
func(new_v1)
print(v1)  
>>输出结果:[11,22,33]

1.2 函数的返回值是内存地址

def func():
    data = [11, 22, 33]
    return data

v1 = func()
print(v1) 
>>输出结果:[11,22,33]

上述代码的执行过程:

  • 执行func函数
  • data = [11, 22, 33] 创建一块内存区域,内部存储[11,22,33],data变量指向这块内存地址。
  • return data 返回data指向的内存地址
  • v1接收返回值,所以 v1data 都指向 [11,22,33] 的内存地址(两个变量指向此内存,引用计数器为2)
  • 函数执行完毕之后,函数内部的变量都会被释放。因为data被释放后,只剩v1指向该内存地址,所以内存地址的引用计数器变为1。
def func():
    data = [11, 22, 33]
    return data

v1 = func()
print(v1) # [11,22,33]

v2 = func()
print(v2) # [11,22,33]

上述代码的执行过程:

  • 执行func函数(第一次)

  • data = [11, 22, 33] 创建一块内存区域,内部存储[11,22,33],data变量指向这块内存地址。

  • return data 返回data指向的内存地址(假设为1000001110)。

  • v1接收返回值,所以 v1data 都指向 [11,22,33] 的内存地址(两个变量指向此内存,引用计数器为2)。

  • 函数执行完毕之后,函数内部的变量都会被释放,内存地址的引用计数器变为1。因为data被释放后,只剩v1指向该内存地址,所以v1指向地址1000001110。

  • 执行func函数(第二次)

  • data = [11, 22, 33] 创建一块内存区域,内部存储[11,22,33],data变量指向这块内存地址。

  • return data 返回data指向的内存地址(假设为11111001110)。

  • v2接收返回值,所以 v2data 都指向 [11,22,33] 的内存地址(两个变量指向此内存,引用计数器为2)。

  • 函数执行完毕之后,函数内部的变量都会被释放,内存地址的引用计数器变为1。因为data被释放后,只剩v2指向该内存地址,所以v1指向地址11111001110。

由此可见,v1v2为两个不同的内存地址。

1.3 参数的默认值【面试题】

def func(a1,a2=18):
    print(a1,a2)

func("root")
func("admin",20)

Python在创建函数(未执行)时,如果发现函数的参数中有默认值,则在函数内部会创建一块内存区域并维护这个默认值。

  • 执行函数未传值时,则让a2指向函数一开始维护的默认值的地址。

  • 执行函数传值时,则让a2指向新传入的值的地址。

在特定情况下:

  • 默认参数的值是可变类型 list/dict/set
  • 函数内部对默认参数进行了修改

后续在调用该函数并传入参数时,会存在特殊情况,需要特别注意:

  • 案例1:

    # 在函数内存中会维护一块区域存储 [1,2],假设内存地址为:100010001
    def func(a1,a2=[1,2]):
        a2.append(666)
        print(a1,a2)
    
    # a1 = 100
    # a2 -> 100010001
    func(100) 
    >>输出结果:100 [1,2,666]
    
    # a1 = 200
    # a2 -> 100010001
    func(200) 
    >>输出结果:200 [1,2,666,666]
    
    # a1 = 99
    # a2 -> 新地址:1111111101
    func(99,[77,88]) 
    >>输出结果:99 [77,88,666]
    
    # a1 = 300
    # a2 -> 100010001
    func(300) 
    >>输出结果:300 [1,2,666,666,666] 
    
  • 案例2:

    # 在函数内部会维护一块区域存储[1, 2],假设内存地址为:1010101010
    def func(a1, a2=[1, 2]):
        a2.append(a1)
        return a2
    
    # a1 = 10
    # a2 -> 1010101010
    # v1 -> 1010101010
    v1 = func(10)
    print(v1) 
    >>输出结果:[1, 2, 10]
    
    # a1 = 20
    # a2 -> 1010101010
    # v2 -> 1010101010
    v2 = func(20)
    print(v2) 
    >>输出结果:[1, 2, 10, 20]
    
    # a1 = 30
    # a2 -> 新地址:11111111111       
    # v3 -> 新地址:11111111111
    v3 = func(30, [11, 22])
    print(v3) 
    >>输出结果:[11, 22,30]
    
    # a1 = 40
    # a2 -> 1010101010
    # v4 -> 1010101010
    v4 = func(40)
    print(v4) 
    >>输出结果:[1, 2, 10, 20, 40] 
    
  • 案例3:

    # 在函数内部会维护一块区域存储[1, 2],假设内存地址为:1010101010
    def func(a1, a2=[1, 2]):
        a2.append(a1)
        return a2
    
    # a1=10
    # a2 -> 1010101010
    # v1 -> 1010101010
    v1 = func(10)
    
    # a1=20
    # a2 -> 1010101010
    # v2 -> 1010101010
    v2 = func(20)
    
    # a1=30
    # a2 -> 新地址:11111111111  
    # v3 -> 新地址:11111111111
    v3 = func(30, [11, 22])
    
    # a1=40
    # a2 -> 1010101010
    # v4 -> 1010101010
    v4 = func(40)
    
    print(v1) 
    print(v2) 
    print(v3) 
    print(v4) 
    >>输出结果:
    [1, 2, 10, 20, 40]
    [1, 2, 10, 20, 40]
    [11,22,30]
    [1, 2, 10, 20, 40] 
    

小结::

  1. 对于函数中的默认参数为可变类型(列表、字典、集合),且函数中对默认参数进行了修改操作的情况,后续调入函数时,如果没有传入默认参数的值,则每调用一次函数,默认参数就会修改一次。如果重新赋值了默认参数,则会按传入的新值进行操作。

  2. 总之,不管参数怎么变,只要记住函数执行传参时,传递的是内存地址,根据这个原则去逐步分析,就不会出错。

1.4 动态参数

动态参数,定义函数时在形参位置用 *args或**kwargs 可以接收任意个参数。

def func(*args,**kwargs):
    print(args,kwargs)
    
func("宝强","杰伦",n1="alex",n2="eric")

除了在定义函数时可以用 *args和**kwargs,其实在执行函数时,也可以用:

  • 形参固定,实参用*args和**kwargs

    def func(a1,a2):
        print(a1,a2)
        
    func( 11, 22 )
    func(a1=11, a2=22)
    func(*[11,22])
    func(**{
         "a1":11,"a2":22})
    >>以上输出结果均为:
    11,22
    
  • 形参用*args和**kwargs,实参也用 *args和**kwargs

    def func(*args,**kwargs):
        print(args,kwargs)
        
    func(11, 22)	# 只通过位置传参,会默认传入*args的空元组()中
    >>输出结果:(11,22) {
         }
    
    func(11, 22, name="轩小陌", age=18)	# 通过位置和关键字传参,前半部分传入*args的空元组()中,后半部分传入**kwargs的空字典{}中
    >>输出结果:(11,22) {
         name:'轩小陌',age:18}
    
    func(*[11,22,33], **{
         "k1":1,"k2":2}) 	# 通过*和**传参,*后面的参数传入*args的空元组()中,**后面的参数传入**kwargs的空字典{}中
    >>输出结果:(11,22,33) {
         "k1":1,"k2":2}
    
    # 注意:按照这个方式将数据传递给args和kwargs时,数据是会重新拷贝一份的(可理解为内部循环每个元素并设置到args和kwargs中)。
    

    因此,在使用format字符串格式化时,可以有更多的方式:

    之前的写法:
    v1 = "我是{},年龄:{}。".format("轩小陌",18)
    v2 = "我是{name},年龄:{age}。".format(name="轩小陌",age=18)
    
    新增的写法:
    v3 = "我是{},年龄:{}。".format(*["轩小陌",18])
    v4 = "我是{name},年龄:{age}。".format(**{
         "name":"轩小陌","age":18})
    

练习题:

  1. 看代码写结果

    def func(*args,**kwargs):
        print(args,kwargs)
        
    params = {
         "k1":"v2","k2
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值