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))
>>输出结果:140247057684592def 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 copydef 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 datav1 = 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 datav1 = 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":"v2"}
    func(params)
    >>输出结果:({"k1":"v2","k2":"v2"},) {}
    func(**params)
    >>输出结果:() {"k1":"v2","k2":"v2"}
    
  2. 读取文件中的 URL 和 标题,根据URL下载视频到本地(以标题作为文件名)。

    模仿,https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0300f570000bvbmace0gvch7lo53oog&ratio=720p&line=0
    卡特,https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f3e0000bv52fpn5t6p007e34q1g&ratio=720p&line=0
    罗斯,https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f240000buuer5aa4tij4gv6ajqg&ratio=720p&line=0
    
    import requestsdef download(title, url):res = requests.get(url=url,headers={"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) ""Chrome/87.0.4280.88 Safari/537.36 FS "})with open(f'{title}.mp4', mode='wb') as f:f.write(res.content)with open('db.csv', mode='r', encoding='utf-8') as file:for line in file:line_list = line.strip().split(',')download(*line_list)    # 通过*列表传入参数,自动给title和url赋值
    

2. 函数和函数名

函数名其实就是一个变量,通过变量代指函数。

注意:函数必须先定义才能被调用执行(因为Python是解释型语言,代码会由上而下解释、执行)。

# 正确
def add(n1,n2):return n1 + n2ret = add(1,2)
print(ret)
# 错误
ret = add(1,2)
print(ret) def add(n1,n2):return n1 + n2

2.1 函数作为元素

既然函数就相当于是一个变量,那么在列表等中就可以把函数当做元素:

def func():return 123data_list = ["轩小陌", "func", func , func() ]print( data_list[0] )
>>输出结果:轩小陌
print( data_list[1] )
>>输出结果:func
print( data_list[2] )
>>输出结果:<function func at 0x000001F73BCDF280>
print( data_list[3] )
>>输出结果:123
res = data_list[2]()
print( res )
>>输出结果:123

注意:函数同时也可被哈希,所以函数名通常也可以作为集合的元素、字典的键。

掌握这个知识之后,对后续的项目开发有很大的帮助,例如,在项目中遇到需要根据选择做不同操作的情况时:

  • 情景1:开发一个类似于微信的功能:

    • 常规处理方式:
    def send_message():"""发送消息"""pass
    def send_image():"""发送图片"""pass
    def send_emoji():"""发送表情"""pass
    def send_file():"""发送文件"""passprint("欢迎使用xx系统")
    print("请选择:1.发送消息;2.发送图片;3.发送表情;4.发送文件")
    choice = input("输入选择的序号")
    if choice == "1":send_message()
    elif choice == "2":send_image()
    elif choice == "3":send_emoji()
    elif choice == "4":send_file()
    else:print("输入错误")
    
    • 将函数名作为字典的值保存起来,再通过字典的键获取值的方式执行函数:
    def send_message():"""发送消息"""pass
    def send_image():"""发送图片"""pass
    def send_emoji():"""发送表情"""pass
    def send_file():"""发送文件"""passfunction_dict = {"1": send_message,"2": send_image,"3": send_emoji,"4": send_file,"5": xxx
    }
    print("欢迎使用xx系统")
    print("请选择:1.发送消息;2.发送图片;3.发送表情;4.发送文件")
    choice = input("输入选择的序号")
    func = function_dict.get(choice)
    if not func:print("输入错误")
    else:func()
    
  • 情景2:某个特定情况,要同时实现发送短信、微信、邮件的功能:

    • 常规处理方式:
    def send_msg():"""发送短信"""pass
    def send_email():"""发送图片"""pass
    def send_wechat():"""发送微信"""passsend_msg()
    send_email()
    send_wechat()
    
    • 将函数名作为列表元素保存起来,通过遍历列表的方式执行函数:
    def send_msg():"""发送短信"""pass
    def send_email():"""发送图片"""pass
    def send_wechat():"""发送微信"""passfunc_list = [send_msg, send_email, send_wechat]
    for item in func_list:item()
    

注意:上述两种情景,在参数相同时才可用,如果参数不一致,会出错。所以,在项目设计时就要让程序满足这一点,如果无法满足,也可以通过其他手段实现:

情景3:将 [函数名, 参数] 作为字典的值保存起来,再通过字典的键获取值的方式获取函数名和参数,再执行函数:

def send_message(phone,content):"""发送消息"""pass
def send_image(img_path, content):"""发送图片"""pass
def send_emoji(emoji):"""发送表情"""pass
def send_file(path):"""发送文件"""passfunction_dict = {"1": [send_message, ['15131255089', '你好呀']],"2": [send_image, ['xxx/xxx/xx.png', '消息内容']],"3": [send_emoji, ["												

轩小陌的Python笔记-day11 函数进阶相关推荐

  1. 轩小陌的Python笔记-day06 数据类型

    day06 数据类型(中) 常见的数据类型: int,整数类型(整形) bool,布尔类型 str,字符串类型 list,列表类型 tuple,元组类型 dict,字典类型 set,集合类型 floa ...

  2. 轩小陌的Python笔记-day13 匿名函数、生成器、内置函数、推导式

    day13 内置函数和推导式 今日概要: 匿名函数 生成器 内置函数 附加:推导式,属于数据类型的知识,内部的高级的用法会涉及到[生成器]和[函数]的内容. 1. 匿名函数 传统的函数的定义包括了:函 ...

  3. 轩小陌的Python笔记-day28 索引、函数及存储过程

    day28 索引和函数及存储过程 课程目标:了解 MySQL 中索引.函数.存储过程.函数.触发器.视图等知识点. 课程概要: 索引 函数 存储过程 视图 触发器 1. 索引 在数据库中索引最核心的作 ...

  4. 轩小陌的Python笔记-day16 模块二总结

    day16 阶段总结 课程目标:对第二模块 "函数和模块" 阶段的知识点进行总结. 课程概要: 知识补充 阶段总结(思维导图) 1. 知识补充 1.1 nolocal关键字 在之前 ...

  5. 轩小陌的Python笔记-day25 MySQL入门

    第四模块 MySQL数据库 从今天开始将进入第四模块的学习,这个模块就是给大家讲解MySQL数据库. 以前,在开发程序时,我们会把很多的数据和信息存储到某个文件夹中的文件中,例如:user.txt . ...

  6. 轩小陌的Python笔记-day14 自定义模块、第三方模块、内置模块(部分)

    day14 模块 课程目标:掌握Python中常用模块的使用方法. 今日概要: 自定义模块(包) 第三方模块 内置模块[1/2] 1. 自定义模块 1.1 模块和包 import hashlibdef ...

  7. 轩小陌的Python笔记-day05 数据类型

    day05 数据类型(上) 接下来的3篇的内容都是讲解数据类型的知识点,常见的数据类型: int,整数类型(整型) bool,布尔类型 str,字符串类型 list,列表类型 tuple,元组类型 d ...

  8. 轩小陌的Python笔记-day02 快速上手

    day02 快速上手 课程目标:学习Python最基础的语法知识,可以用代码快速实现一些简单的功能. 课程概要: 初识编码(密码本) 编程初体验 输出 初识数据类型 变量 注释 输入 条件语句 1.初 ...

  9. 轩小陌的Python笔记-day17 初识面向对象

    第三模块 面向对象&网络编程&并发编程 第三模块包含的知识内容: 面向对象,Python中支持两种编程方式来编写代码,分别是:函数式编程.面向对象式编程. 函数式 # 定义函数,在函数 ...

最新文章

  1. 浅谈网络协议(四) IP的由来--DHCP与PXE
  2. 工信部通告:任何组织和机构不得继续实施“计算机信息系统集成企业资质认定”...
  3. 需求评审五个维度框架分析及其带来的启示-3-典型需求评审
  4. 成功抓取豆瓣读书的所有书籍
  5. 支付宝备用金七天不还会不会上征信?
  6. 三条中线分的六个三角形_八年级数学上册:三角形已知两条边如何求第三边
  7. MongoDB教程-使用Node.js从头开始CRUD应用
  8. pt-show-grants的用法
  9. markdown生成html不出效果,mdeditor: 简单markdown编辑器,同步预览html效果。不依赖任何插件,使用简单,原创,造轮子中。。。更新中。。。...
  10. 深入理解C++浮点数(float、double)类型数据比较、相等判断
  11. git commit后,如何撤销commit
  12. RAPIDXML 中文手册,根据官方文档完整翻译!
  13. Eclipse的使用教程
  14. 苹果执行请求时出错_错误报告 - Apple Developer
  15. 如何安装Win10/Ubuntu双系统
  16. qcap 教程_给winpe添加explorer教程(续):文件列表
  17. 中国互联网的发展历程
  18. Office 2016 系列 VOL版本下载
  19. 电力系统系统潮流分析【IEEE 57 节点】(Matlab代码实现)
  20. 不要迷恋我,虽然我利用Python来耍植物大战僵尸

热门文章

  1. Spring 控制反转和依赖注入
  2. JavaScript ——〖猜数字游戏〗10次机会
  3. 浏览器小知识之苹果浏览器(Safari)
  4. PVN3D 配置过程(适用ubuntu20,适用30系显卡)
  5. windows服务器双网卡链路聚合_Win10怎样绑定双网卡或多网卡做Nic Teaming链路聚合...
  6. h5 html分页样式,原创H5分页器
  7. 虚拟机 云盘服务器,虚拟机 云盘服务器
  8. Dubbo的底层实现原理和机制
  9. python词云需要导入什么包_[python] 词云:wordcloud包的安装、使用、原理(源码分析)、中文词云生成、代码重写...
  10. 世界再大,过年回家 | 当代青年人春运抢票图鉴