Python4.函数进阶
本文最后更新于503 天前,其中的信息可能已经过时,如有错误请发送邮件到gedaling@foxmail.com

目标:掌握函数相关易错点&项目开发必备技能

概要:

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

1、参数的补充

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

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

1.1、参数内存地址相关【面试题】

再开始讲参数内存地址相关之前,先学习一个技能:如果想要查看某个值在内存中的地址?

v1 = '武沛齐'
addr = id(v1)
print(addr)  # 2307800736944
v1 = [11, 22, 33]
v2 = [11, 22, 33]
print(id(v1))   # 2855833700288
print(id(v2))   # 2855833513664


v1 = [11, 22, 33]
v2 = v1
print(id(v1))   # 2674044772480
print(id(v2))   # 2674044772480

记住一句话:函数执行传参时,传递的是内存地址

image-20231219115347381

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


v1 = '武沛齐'
print(id(v1))   # 2304215327920

func(v1)    # 武沛齐 2304215327920

面试题:请问Python的参数默认传递的是什么?

Python参数的这一特性有两个好处:

  • 节省内存
  • 对于可变类型且函数中次改元素的内容,所有的地方都会修改。可变类型:列表、字典、集合
# 可变类型 & 修改内部元素
def func1(data):
    data.append(666)


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


# 特殊情况:可变类型 & 重新赋值
def func2(data):
    data = ['武沛齐', 'alex']


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


# 特殊情况,不可变类型,无法修改内部元素,只能重新赋值
def func3(data):
    data = 'alex'


v1 = '武沛齐'
func3(v1)
print(v1)   # 武沛齐

其他很多变成语言执行函数时,默认传参时会将数据重新拷贝一份,会浪费内存。

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

import copy


# 可变类型 & 修改内部元素
def func(data):
    data.append(666)


v1 = [11, 22, 33]
new_v1 = copy.deepcopy(v1)  # 拷贝一份数据
func(new_v1)
print(v1)  # [11, 22, 33]
print(new_v1)   # [11, 22, 33, 666]

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接收返回值,所以v1和data都指向[11,22,33]的内存地址(两个变量指向此内存,引用计数器为2)
  • 由函数执行完毕后,函数内部的变量都会被释放(即:删除data变量,内存地址的引用计数器-1)

所以,最终v1指向的函数内部创建的那块内存地址

 

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


v1 = func()
print(v1, id(v1))  # 2693005379712  # [11, 22, 33] 2693005379712
v2 = func()
print(v2, id(v2))  # 2693005378624  # [11, 22, 33] 2693005378624

上述代码的执行过程:

  • 执行func函数
  • data = [11,22,33]创建一块内训区域,内部存储[11,22,33],data变量指向这块内存地址2693005379712
  • return data返回data指向的内存地址
  • v1接收返回值,所以v1和data都指向[11,22,33]的内存地址(两个变量指向此内存,引用计数器为2)
  • 由函数执行完毕后,函数内部的变量都会被释放(即:删除data变量,内存地址的引用计数器-1)

此时,v1指向的函数内部创建的那块内存地址(v1指向的2693005379712内存地址)

  • 执行func函数
  • data = [11,22,33]创建一块内训区域,内部存储[11,22,33],data变量指向这块内存地址2693005378624
  • return data返回data指向的内存地址
  • v2接收返回值,所以v2和data都指向[11,22,33]的内存地址(两个变量指向此内存,引用计数器为2)
  • 由函数执行完毕后,函数内部的变量都会被释放(即:删除data变量,内存地址的引用计数器-1)

此时,最终v2指向的函数内部创建的那块内存地址(v2指向的2693005378624内存地址)

注意:如果data是数字或字符串,会因为Python的缓存/驻留机制(只创建一个地址)导致v1和v2指向一样的内存地址

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

该知识点在面试题中出现概率比较高,但真正实际开发中用的比较少

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

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

  • 执行函数未传值时,则让a2指向函数维护的那个值的地址
    func('root')
    
  • 执行函数传值时,则让a2指向新传入的值的地址
    func('admin',20)
    

在特定情况【默认参数的值是可变类型list/dict/set】&【函数内部会修改这个值】下,参数的默认值 有坑。

  • # 在函数内存中会维护一块区域存储[1,2] 地址:100001001
    def func(a1, a2=[1, 2]):
        a2.append(666)
        print(a1, a2)
    
    # a1 = 100
    # a2 -> 100001001 地址的内容在本次调用中修改为[1, 2, 666]
    func(100)   # 100 [1, 2, 666]
    
    # a1 = 200
    # a2 -> 100001001 依然指向该地址,该地址的内容在本次调用中修改为[1, 2, 666, 666]
    func(200)   # 200 [1, 2, 666, 666]
    
    # a1 = 99
    # a2 = [77, 88] -> 11111101 传值,指向新地址
    func(99, [77, 88])  # 99 [77, 88, 666]
    
    # a1 = 300
    # a2 -> 100001001 依然指向该地址,该地址的内容在本次调用中修改为[1, 2, 666, 666, 666]
    func(300)   # 300 [1, 2, 666, 666, 666]
    
  • 大坑
    # 在函数内存中会维护一块区域存储[1,2] 地址:1010101010
    def func(a1, a2=[1, 2]):
        a2.append(a1)
        return a2
    
    # a1 = 10
    # a2 -> 1010101010
    # v1 -> 1010101010 该地址的内容在本次调用中修改为[1, 2, 10]
    v1 = func(10)
    print(v1)  # [1, 2, 10]
    
    # a1 = 20
    # a2 -> 1010101010 依然指向该地址
    # v2 -> 1010101010 该地址的内容在本次调用中修改为[1, 2, 10, 20]
    v2 = func(20)
    print(v2)  # [1, 2, 10, 20]
    
    # a1 = 30
    # a2 = [11, 22] -> 111111111 传值,指向新地址
    # v3 -> 111111111
    v3 = func(30, [11, 22])
    print(v3)  # [11, 22, 30]
    
    # a1 = 40
    # a2 -> 1010101010 依然指向该地址
    # v4 -> 1010101010 该地址的内容在本次调用中修改为[1, 2, 10, 20, 40]
    v4 = func(40)
    print(v4)  # [1, 2, 10, 20, 40]
    
  • 深坑
    # 在函数内存中会维护一块区域存储[1,2] 地址:100000001
    def func(a1, a2=[1, 2]):
        a2.append(a1)
        return a2
    
    # a1 = 10
    # a2 -> 100000001
    # v1 -> 100000001 该地址的内容在本次调用中修改为[1, 2, 10]
    v1 = func(10)
    
    # a1 = 20
    # a2 -> 100000001 依然指向该地址
    # v2 -> 100000001 该地址的内容在本次调用中修改为[1, 2, 10, 20]
    v2 = func(20)
    
    # a1 = 30
    # a2 = [11, 22] -> 111100001 传值,指向新地址
    # v3 -> 111100001 该地址的内容在本次调用中修改为[11, 22, 30]
    v3 = func(30, [11, 22])
    
    # a1 = 40
    # a2 -> 100000001 依然指向该地址
    # v4 -> 100000001 该地址的内容在本次调用中修改为[1, 2, 10, 20, 40]
    v4 = func(40)
    # 以上函数运行结束后
    v1 v2 v4 -> 100000001 该地址的内容最后一次修改为[1, 2, 10, 20, 40]
    v3 -> 111100001 指向传值的地址,内容修改为[11, 22, 30]
    print(v1)  # [1, 2, 10, 20, 40]
    print(v2)  # [1, 2, 10, 20, 40]
    print(v3)  # [11, 22, 30]
    print(v4)  # [1, 2, 10, 20, 40]
    

1.4、动态参数

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

def func(*args, **kwargs):
    print(args, kwargs)


func('宝强', '杰伦', n1='alex', n2='eric')  # ('宝强', '杰伦') {'n1': 'alex', 'n2': 'eric'}

在定义函数时可以用***,其实在执行函数时,也可以用

  • 形参固定,实参用***
    def func(a1, a2):
        print(a1, a2)
    
    
    func(11, 22)  # 11 22
    func(a1=1, a2=2)  # 1 2
    
    func(*[11, 22])  # 1 2
    func(**{'a1': 1, 'a2': 2})  # 1 2
    
  • 形参用***,实参也用***
    def func(*args, **kwargs):
        print(args, kwargs)
    
    
    func(11, 22)    # (11, 22) {}
    func(11, 22, name='武沛齐', age=18)    # (11, 22) {'name': '武沛齐', 'age': 18}
    
    # 小坑
    func([11, 22, 33], {'k1': 1, 'k2': 2})  # ([11, 22, 33], {'k1': 1, 'k2': 2}) {}
    
    # 值得注意:按照这个方式将数据传递给args和kwargs时,数据是会重新拷贝一份的(可理解为内部循环每个元素并设置到args和kwargs中)
    func(*[11, 22, 33], **{'k1': 1, 'k2': 2})   # (11, 22, 33) {'k1': 1, 'k2': 2}
    

所以,在使用format字符串格式化时,可以如下这般:

v1 = '我是{},年龄:{}。'.format('武沛齐', 18)
v2 = '我是{name},年龄:{age}。'.format(name='武沛齐', age=18)

v3 = '我是{},年龄:{}。'.format(*['武沛齐', 18])
v4 = '我是{name},年龄:{age}。'.format(**{'name': '武沛齐', 'age': 18})

练习题:

读取文件中的URL和标题,根据URL下载视频到本地(以标题作为文件名)

模仿,https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0300f570000bvmace0gvch7lo53oog&ratio=720P&line=0
卡特,https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f3e0000bv52fpn5t6p007e34qlg&ratio=720P&line=0
罗斯,https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f240000buuer5aa4tij4gv6ajqg&ratio=720P&line=0
# 下载视频实例
import requests
res = requests.get(
    url="https://www.bilibili.com/video/BV1qh411273b?p=127&spm_id_from=pageDriver",
    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('123.mp4', mode='wb') as file_object:
   file_object.write(res.content)
import requests


def 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('{}.mp4'.format(title), mode='wb') as f:
        f.write(res.content)


# 读取文件
with open('files/db.csv', mode='r', encoding='utf-8') as file_object:
    # 循环文件的每一行
    for line in file_object:
        # 去除空格/换行符
        line = line.strip()
        # 根据","分割
        row_list = line.split(',')
        # 下载视频
        # download(row_list[0], row_list[1])
        download(*row_list)

2、函数和函数名

函数名其实就是一个变量,这个变量只不过代指的函数而已

name = '武沛齐'
def add(n1,n2):
    return n1 + n2

注意:函数必须先定义才能被调用(解释型语言)

# 正确
def add(n1,n2):
    return n1 + n2

ret = add(1,2)
print(ret)

# 错误
ret = add(1,2)
print(ret)

def add(n1,n2):
    return n1 + n2

2.1、函数做元素

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

def func():
    return 123


data_list = ['武沛齐', 'func', func, func()]

print(data_list[0]) # 字符串‘武沛齐’
print(data_list[1]) # 字符串‘func’
print(data_list[2]) # 函数 func
print(data_list[3]) # 整数 123

res = data_list[2]()
print(res)  # 123   # 执行函数func,并获取返回值;print再输出返回值

print(data_list[2]())   # 123   # 执行函数func,并获取返回值;print再输出返回值

注意:函数同时也可被哈希,所以函数名同时也可以当做集合的元素、字典的键

 

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

  • 情景1,例如:开发一个类似于微信的功能
    def send_message():
        """发送消息"""
        pass
    
    def send_image():
        """发送图片"""
        pass
    
    def send_emoji():
        """发送表情"""
        pass
    
    def send_file():
        """发送文件"""
        pass
    
    print('欢迎使用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():
        """发送文件"""
        pass
    
    
    def XXX():
        """收藏"""
        pass
    
    
    function_dict = {
        '1': send_message,
        '2': send_image,
        '3': send_emoji,
        '4': send_file
    }
    
    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():
        """发送微信"""
        pass
    
    
    # 执行函数
    send_msg()
    send_email()
    send_wechat()
    
    def send_msg():
        """发送短信"""
        pass
    
    
    def send_email():
        """发送邮件"""
        pass
    
    
    def send_wechat():
        """发送微信"""
        pass
    
    
    func_list = [send_msg, send_email, send_wechat]
    for item in func_list:
        item()	# 执行函数
    

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

  • 情景1补充:
    def send_message(phone, content):
        """发送消息"""
        pass
    
    
    def send_image(img_path, content):
        """发送图片"""
        pass
    
    
    def send_emoji(emoji):
        """发送表情"""
        pass
    
    
    def send_file(path):
        """发送文件"""
        pass
    
    
    function_dict = {
        '1': [send_message, ['15131255089', '你好呀']],
        '2': [send_image, ['XXX/xxx/xx.png', '消息内容']],
        '3': [send_emoji, ['☺']],
        '4': [send_file, ['xx.zip']],
    }
    
    print('欢迎使用XX系统')
    print("请选择:1、发送消息;2、发送图片;3、发送表情;4、发送文件")
    choice = input('输入选择的序号')  # 1
    
    item = function_dict.get(choice)  # [send_message, ['15131255089', '你好呀']]
    if not item:
        print('输入错误')
    else:
        # 执行函数
        func = item[0]  # send_message
        param_list = item[1]  # ['15131255089', '你好呀']
        func(*param_list)  # send_message(*['15131255089', '你好呀'])
    
  • 情景2补充:
    def send_msg(mobile, content):
        """发送短信"""
        pass
    
    
    def send_email(to_email, subject, content):
        """发送邮件"""
        pass
    
    
    def send_wechat(user_id, content):
        """发送微信"""
        pass
    
    
    func_list = [
        {'name': send_msg, 'params': {'mobile': '15131255089', 'content': '你有新短消息'}},
        {'name': send_email,
         'params': {'to_email': 'wupeiqi@live.com', 'subject': '报警消息', 'content': '硬盘容量不够用了'}},
        {'name': send_wechat, 'params': {'user_id': '1', 'content': '约吗'}},
    ]
    for item in func_list:
        # 执行函数
        func = item['name']  # send_msg
        param_list = item['params']  # {'mobile': '15131255089', 'content': '你有新短消息'}
        func(*param_list)  # send_msg(**{'mobile': '15131255089', 'content': '你有新短消息'})
    

2.2、函数名赋值

  • 将函数名赋值给其他变量,函数名其实就是个变量,代指某函数;如果将函数名赋值给另外一个变量,则此变量也会代指该函数,例如:
    def func(a1, a2):
        print(a1, a2)
    
    
    xxxx = func
    # 此时,xxxx和func都代指上面的函数,所以都可以被执行
    func(1, 1)  # 1 1
    xxxx(2, 2)  # 2 2
    
    def func(a1, a2):
        print(a1, a2)
    
    
    func_list = [func, func, func]
    
    func(11, 22)    # 11 22
    func_list[0](11, 22)    # 11 22
    func_list[1](33, 44)    # 33 44
    func_list[2](55, 66)    # 55 66
    
  • 对函数名重新赋值,如果将函数名修改为其他值,函数名便不再代指函数,例如:
    def func(a1, a2):
        print(a1, a2)
    
    
    # 执行func函数
    func(11, 22)    # 11 22
    
    # func重新赋值成一个字符串
    func = '武沛齐'
    print(func) # 武沛齐
    
    def func(a1, a2):
        print(a1 + a2)
    
    func(1, 2)  # 3
    
    # 重新定义函数,覆盖掉上面定义的函数
    def func():
        print(666)
    
    func()  # 666
    

注意:由于函数名被重新定义后,就会变成被定义的值,所以在自定义函数时,不要与Python内置的函数同名,否则会覆盖掉内置函数的功能,例如:

id,bin,hex,oct,len....

# 新定义函数覆盖内置函数示例
# len内置函数用于计算值的长度
v1 = len('武沛齐')
print(v1)	# 3

# len重新定义成另外一个函数
def len(a1,a2):
    return a1 + a2

# 以后执行len函数,只能按照重新定义的来使用
v3 = len(4,2)
print(v3)	# 6

2.3、函数名做参数和返回值

函数名其实就是一个变量,代指某个函数,所以,也可以当做函数的参数和返回值。

  • 参数
    def plus(num):
        return num + 100
    
    
    def handler(func):
        res = func(10)	# 110
        msg = '执行func,并获取到的结果为:{}'.format(res)
        print(msg)
    
    # 执行handler函数,并将plus作为参数传递给handler的形式参数func
    handler(plus)   # 执行func,并获取到的结果为:110
    
  • 返回值
    def plus(num):
        return num + 100
    
    
    def handler():
        print('执行handler函数')
        return plus
    
    
    result = handler()  # 执行handler函数,并将返回值plus赋值给result,此时result也指向函数plus
    data = result(20)   # 120
    print(data) # 120
    

3、返回值和print

对于初学者,很多人对print和返回值分不清楚,例如;

def add(n1, n2):
    print(n1 + n2)


v1 = add(1, 3)
print(v1)  # 4 # None


def plus(a1, a2):
    return a1 + a2


v2 = plus(1, 2)
print(v2)  # 3

这两个函数是完全不同的

  • 在函数中使用print,只是用于在某个位置输出内容而已
  • 在函数汇总使用return,是为了将函数的执行结果返回给调用者,以便于后续其他操作

 

在调用函数并执行函数时,要学会分析函数的执行步骤

def f1():
    print(123)

def f2(arg):
    ret = arg()
    return ret

# f1作为参数传给函数f2()的参数arg,此时arg也指向函数f1(),执行函数f1()输出‘123’,再将函数f1()返回值None赋值给变量ret,最后将返回值ret值赋值给调用者v1
v1 = f2(f1)
print(v1)  # 123   # None
def f1():
    print(123)

def f2(arg):
    ret = arg()
    return f1

# f1作为参数传给函数f2()的参数arg,此时arg也指向函数f1(),执行函数f1()输出‘123’,再将函数f1()返回值None赋值给变量ret,最后将返回值f1值赋值给调用者v1,此时v1也指向函数f1()
v1 = f2(f1)		# 123	# 123
# v1指向函数f1(),将返回值None赋值给变量v2
v2 = v1()
print(v2)  # None

4、作用域

作用域,可以理解为一块空间,这块空间的数据是可以共享的。通俗点讲,作用域就类似于一个房子,房子内的东西归里面的所有人共享,其他房子的人无法获取。

4.1、函数为作用域

Python以函数为作用域,所以在函数内创建的所有数据,可以此函数中被使用,无法在其他函数中使用。

def func():
    name = '武沛齐'
    data_list = [11, 22, 33, 44]
    print(name, data_list)
    age = 20
    print(age)


def handler():
    age = 18
    print(age)


func()  # 武沛齐 [11, 22, 33, 44]  # 20
handler()   # 18

 

学会分析代码,了解变量到底属于哪个作用域且是否可以被调用:

def func():
    name = '武沛齐'
    age = 29
    print(age)
    data_list = [11, 22, 33, 44]
    print(name, data_list)

    for num in range(10):
        print(num)  # 运行到最后,num=9
    print(num)  # 9

    if 1 == 1:
        value = 'admin'  # 运行,value='admin'
        print(value)  # admin
    print(value)  # admin

    if 1 > 2:
        max_num = 10  # 不运行,不存在max_num
        print(max_num)
    print(max_num)  # 报错


def handler():
    age = 18
    print(age)


handler()
func()

4.2、全局和局部

4.3、global关键字

 

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇