当前位置: 首页 > news >正文

Python函数——万字详解

 

  

                                       ——    

导 语:    

         从今天开始,我们将进入第二模块的学习——函数。第一模块主要是学习python基础知识,从第二模块开始就可以通过程序去解决工作中实际的问题。从今天开始,我们将进入第二模块的学习,此模块主要是:

  • 函数,一个用于专门实现某个功能的代码块(可重用)

1、内置函数

len、bin、oct、hex 等

2、自定义函数

def send_email():# 实现了发送邮件的代码pass
send_email()

3、模块,集成了很多功能的函数集合

  • 内置模块,Python内部帮助我们提供好的。

    import random
    num = random.randint(0,19)
    
    import decimalv1 = decimal.Decimal("0.1")
    v2 = decimal.Decimal("0.2")
    v3 = v1 + v2
    print(v3) # 0.3
    
  • 第三方模块,网上下载别人写好的模块(功能集合)。

  • 自定义模块

一. 文件操作

在学习文件操作之前,先来回顾一下编码的相关以及先关数据类型的知识。

  • 字符串类型(str),在程序中用于表示文字信息,本质上是unicode编码中的二进制。

    name = "武沛齐"
    
  • 字节类型(bytes)

可表示文字信息,本质上是utf-8/gbk等编码的二进制(对unicode进行压缩,方便文件存储和网络传输。)

name="小峰"
print(name.encode('utf-8'))
data=b'\xe5\xb0\x8f\xe5\xb3\xb0'
print(data.decode('utf-8'))#小峰

可表示原始二进制(图片、文件等信息)

#	- 路径:
#		相对路径:'info.txt'
#		绝对路径:'/Users/PycharmProjects/luffyCourse/day09/info.txt'
# 	- 模式
# 		rb,表示读取文件原始的二进制(r, 读 read;b, 二进制 binary;)
#

在这里对于文件的文件打开的模式我们就不再一一介绍了, 主要参考:

https://docs.python.org/3/library/functions.html#filemodes

模式 介绍
‘r’ 读取
‘w’   清空文件内容,然后写入
‘x’   新建文件,然后写入(如果文件存在,那直接失败)
‘a’ 在文件末尾写入,文件不存在就新建
‘b’  二进制模式
‘t’   文本模式
‘+’  更新(读和写)

Python里面默认的模式是:'rt'。只要有'+',就变成了可读可写的;可以与w,r,a等进行组合。

1. 读文件

  • 读文本文件

    file_object = open('info.txt', 'rt',encoding='utf-8')
    data=file_object.read()
    print(data)
    file_object.close()
  • 读图片等非文本内容文件。

    file_object = open('my.png', 'rb')
    data = file_object.read()
    file_object.close()
    print(data)

        注意事项:路径相关问题。注意区分相对路径与绝对路径。

        windows系统中写绝对路径容易出问题:

#注意路径相关的问题
错误写法:file_object = open("D:\python\try1\info.txt","rt")  
正确写法:file_object = open("D:\\python\\try1\\info.txt","rt")
正确写法:file_object = open(r"D:\python\try1\info.txt","rt")
  • 读文件时,文件不存在程序会报错。所以一般在项目中我们会将其和os模块结合起来使用,判断一个路径是否存在。

    file_path=r"D:\python\try1\info.txt"
    exists=os.path.exists(file_path)
    if not exists:print("文件不存在")
    else:file_object = open(file_path, "rt",encoding="utf-8")data = file_object.read()print(data)file_object.close()
    

2.写文件

  • 写文本文件

    # 路径:t1.txt
    # 模式:wb(要求写入的内容需要是字节类型)
    ​1、打开文件
    file_object = open(“info.txt”, mode=‘wb’)
    # 2.写入内容
    file_object.write( “小峰”.encode(“utf-8”) )
    # 3.文件关闭
    file_object.close()
  • 写图片非文本文件等文件

    f1 = open('a1.png',mode='rb')
    content = f1.read()
    f1.close()f2 = open('a2.png',mode='wb')
    f2.write(content)
    f2.close()
    

基础案例:

#用户注册
# w写入文件,先清空文件;再在文件中写入内容。
file_object = open("files/info.txt", mode='wt', encoding='utf-8')
while True:user = input("请输入用户名:")if user.upper() == "Q":breakpwd = input("请输入密码:")data = "{}-{}\n".format(user, pwd)file_object.write(data)
file_object.close()

补充:上下文书写格式,避免忘记关闭文件

with open('info.txt', mode='rb') as f:data = f.read()print(data)

二. 初识函数

函数,可以当做是一大堆功能代码的集合。

def 函数名():函数内编写代码...函数名()

例如:

# 定义名字叫info的函数
def info():print("第一行")print("第二行")print("第n行...")info()

一般在项目开发中有会有两种应用场景:

  • 有重复代码,用函数增加代码的重用性。

    def send_email():# 10行代码print("欢迎使用计算机监控系统")
    if CPU > 90%:send_email()
    if 硬盘使用率 > 99%:send_email()
    if 内存使用率 > 98%:send_email()
    ...
    
  • 代码太长,用函数增强代码的可读性。

    def mycard():card_color_list=["红桃","方片","梅花","方片"]card_num=["1","2","3","4","5","6","7","8","9","J","Q","K","A"]card=[f"{color}{num}"for color in card_color_list for num in card_num]card.append("大王")card.append("小王")return card
    cout=0
    for i in mycard():print(i)cout+=1
    print(cout)

        以前我们变成是按照业务逻辑从上到下逐步完成,称为:面向过程编程;现在学了函数之后,利用函数编程称为:函数式编程。

1. 函数的参数

之下面就来教大家用python发邮件,以下是我为大家提供的发邮件的一个函数。

import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr
​
def send_email(email):# ### 1.邮件内容配置 #### 邮件文本msg = MIMEText("在吗", 'html', 'utf-8') # 邮件上显示的发件人msg['From'] = formataddr(["小峰", "wptawy@126.com"])# 邮件上显示的主题msg['Subject'] = "邮件主题"# ### 2.发送邮件 ### server = smtplib.SMTP_SSL("smtp.126.com")server.login("wptawy@126.com", "WIYSAILOVUKPQGHY")server.sendmail("wptawy@126.com", email, msg.as_string())server.quit()v3 = "xiaofeng@live.com"
send_email(v3)

1.1 参数

在定义函数时,如果在括号中添加变量,我们称它为函数的形式参数

#定义有三个参数的函数(a1/a2/a3一般称为形式参数-形参)
def func(a1,a2,a3):print(a1+a2+a3)# 执行函数并传入参数(执行函数传值时一般称为实际参数-实参)
func(11,22,33)# 执行函数并传入参数
func(9,2,103)
  • 位置传参

    def add(n1,n2):print(n1+n2)add(1,22)
    
  • 关键字传参:只能从右往左

    def add(n1,n2):print(n1+n2)add(n1=1,n2=22)
    

1.2 默认参数

def func(a1, a2, a3=10):print(a1 + a2 + a3)# 位置传参
func(8, 19)
func(1, 2, 99)
# 关键字传参(位置和关键混合时,关键字传参要在后面)
func(12, 9, a3=90)
func(12, a2=9, a3=90)
func(a1=12, a2=9, a3=90)

1.3 动态参数

 动态参数,定义函数时在形参位置用 *或** 可以接任意个参数。在定义函数时可以用 *和**,其实在执行函数时,也可以用。

  •  *
def func(*args):print(args) # 元组类型 (22,)   (22,33,99,) ()# 只能按照位置传参
func(22)
func(22,33)
func(22,33,99)
func()
  • **

    def func(**kwargs):print(kwargs) # 字典类型 {"n1":"小峰"}    {"n1":"小峰","age":"18","email":"xxxx"}  {}# 只能按关键字传参
    func(n1="小峰")
    func(n1="小峰",age=18)
    func(n1="小峰",age=18,email="xx@live.com")
    
  • *和**

    def fun(*args,**kwargs):print(args)print(kwargs)fun(1,2,3,4,5)
    fun(13456,456,name="xiaofeng",age=19,sex="boy")

    提示:是否还记得字符串格式化时的format功能。

    v1 = "我叫{},今年{},性别{}".format("小峰",18,"男")
    v2 = "我叫{name},今年{age},性别{gender}".format(name="小峰",age=18,gender="男")
    print(v1)
    print(v2)
    
  • 注意事项:

    # 1. ** 必须放在 * 的后面
    def func1(*args, **kwargs):print(args, kwargs)
    func1(1,2,name='John')
    # 2. 参数和动态参数混合时,动态参数只能放在最后。
    def func2(a1, a2, a3, *args, **kwargs):print(a1, a2, a3, args, kwargs)
    func2(1,2,4,6,name='John',a=2,b=3,c=4)
    # 3. 默认值参数和动态参数同时存在
    def func3(a1, a2, a3, a4=10, *args, a5=20, **kwargs):print(a1, a2, a3, a4, a5, args, kwargs)
    func3(11, 22, 33, 44, 55, 66, 77, a5=10, a10=123)
    
  • 形参固定,实参用*和**

    def func(a1,a2):print(a1,a2)
    func( 11, 22 )
    func( a1=1, a2=2 )
    func( *[11,22] )
    func( **{"a1":11,"a2":22} )
    
  • 形参用*和**,实参也用 *和**

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

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

v3 = "我是{},年龄:{}。".format(*["小峰",18])
v4 = "我是{name},年龄:{age}。".format(**{"name":"小峰","age":18})
print(v3)
print(v4)

2. 函数返回值

在开发过程中,我们希望函数可以帮助我们实现某个功能,但让函数实现某功能之后有时也需要有一些结果需要反馈给我们,例如:

def func():return 6666
print(func())
def func(a):return a+10
print(func(2))
  • 返回值可以是任意类型,如果函数中没写return,则默认返回None

    def func():value = 1 + 1ret = func()
    print(ret) # None
    

    当在函数中未写返回值 或 return 或 return None ,执行函数获取的返回值都是None。

    def func():value = 1 + 1return  # 或 return Noneret = func()
    print(ret) # None
    
  • return后面的值如果有逗号,则默认会将返回值转换成元组再返回。

    def func():return 1,3,5,7
    print(func(),type(func()))
  • 函数一旦遇到return就会立即退出函数(终止函数中的所有代码)

    def func():print("开始")for i in range(1,10):if i%2==0:print(i)else:returnprint("结束")
    func()# 输出
    开始
    

扩展:密码都不是明文。

  • 注册京东,京东存储:用户名和密码(密文)
  • 登录京东:用户名- 密码。

3.参数的补充

在函数基础部分,我们掌握函数和参数基础知识,掌握这些其实完全就可以进行项目的开发。补充内容包含:内存地址相关、面试题相关等,在特定情况下也可以让代码更加简洁,提升开发效率。

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

在开始开始讲参数内存地址相关之前,我们得先了解:使用 id查看下某个值的在内存中的地址

#查看某个值在内存中的地址
v='小峰'
print(id(v))

注:函数执行传参时,传递的是内存地址。

def func(data):print(data,id(data))
v='小峰'
print(id(v))
func(v)

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

  • 节省内存

  • 对于可变类型且函数中修改元素的内容,所有的地方都会修改。可变类型:列表、字典、集合。

def fun1(data):data.insert(2,0)print(id(data))
v1=[1,2,3,4] #列表
fun1(v1)
print(v1,id(v1))
############################
def fun2(data):data.add(8)print(id(data))
v2={1,2,3,4}#集合
fun2(v2)
print(v2,id(v2))
###########################
def fun3(data):data.update({"k4": 8})print(id(data))
v3={"k1":1,"k2":2,"k3":3} #字典类型
fun3(v3)
print(v3,id(v3))

提示注意:其他很多编程语言执行函数时,默认传参时会将数据重新拷贝一份,会浪费内存。.其他语言也可以通过 ref 等关键字来实现传递内存地址。当然,如果你不想让外部的变量和函数内部参数的变量一致,也可以选择将外部值拷贝一份,再传给函数。

def fun(data):print(data,id(data))
v=[1,2,3,4,5]
print(v,id(v))
new_v=copy.deepcopy(v)
fun(new_v)
print(new_v,id(new_v))
#结果:
[1, 2, 3, 4, 5] 2213990555712
[1, 2, 3, 4, 5] 2213990545664
[1, 2, 3, 4, 5] 2213990545664

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

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

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

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

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

> 原理:

        Python在创建函数(未执行)时,如果发现函数的参数中有默认值,则在函数内部会创建一块区域并维护这个默认值。 执行函数未传值时,则让a2指向 函数维护的那个值的地址。执行函数传值时,则让a2指向新传入的值的地址。在特定情况【默认参数的值是可变类型 list/dict/set】 & 【函数内部会修改这个值】下,参数的默认值 有坑 。

  • # 在函数内存中会维护一块区域存储
    def fun(a,b=[1,4,8]):b.append(100)print(a,b)print(id(a),id(b))
    fun(1)
    fun(2)
    fun(3,[1,2,3,4,5])输出结果
    1 [1, 4, 8, 100]
    1555535456560 1555542237248
    2 [1, 4, 8, 100, 100]
    1555535456592 1555542237248
    3 [1, 2, 3, 4, 5, 100]
    1555535456624 1555542227200
  • 大坑

    # 在内部会维护一块区域存储 [1, 2, 10, 20,40 ] ,内存地址 1010101010
    def func(a1, a2=[1, 2]):a2.append(a1)print(id(a2))return a2
    # a1=10
    # a2 -> 1010101010
    # v1 -> 1010101010
    v1 = func(10)
    print(v1,id(v1))  # [1, 2, 10]
    # a1=20
    # a2 -> 1010101010
    # v2 -> 1010101010
    v2 = func(20)
    print(v2,id(v2))  # [1, 2, 10, 20 ]
    # a1=30
    # a2 -> 11111111111        [11, 22,30]
    # v3 -> 11111111111
    v3 = func(30, [11, 22])
    print(v3,id(v3))  # [11, 22,30]
    # a1=40
    # a2 -> 1010101010
    # v4 -> 1010101010
    v4 = func(40)
    print(v4,id(v4))   # [1, 2, 10, 20,40 ]
    
  • 深坑

    # 内存中创建空间存储 [1, 2, 10, 20, 40] 地址: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   [11,22,30]
    # v3 -> 11111111111
    v3 = func(30, [11, 22])
    # a1=40
    # a2 -> 1010101010
    # v4 -> 1010101010
    v4 = func(40)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]
    

4. 函数和函数名

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

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

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

4.1 函数做元素

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

def func():return 123data_list = ["小峰", "func", func , func() ]print( data_list[0] ) # 字符串"小峰"
print( data_list[1] ) # 字符串 "func"
print( data_list[2] ) # 函数 func
print( data_list[3] ) # 整数 123res = data_list[2]()
print( res ) # 执行函数 func,并获取返回值;print再输出返回值。print( data_list[2]() ) # 123

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

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

  • 情景1,例如:要开发一个类似于微信的功能。

    def send_message():"""发送消息"""passdef send_image():"""发送图片"""passdef send_emoji():"""发送表情"""passdef send_file():"""发送文件"""passdef xxx():"""收藏"""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("输入选择的序号") # "1"func = function_dict.get(choice)
    if not func:print("输入错误")
    else:# 执行函数func()
  • 情景2,例如:某个特定情况,要实现发送短信、微信、邮件。

    def send_msg():"""发送短信"""passdef send_email():"""发送图片"""passdef send_wechat():"""发送微信"""passfunc_list = [ send_msg, send_email, send_wechat ]
    for item in func_list:item()
    

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

情景:

def send_message(phone,content):"""发送消息"""passdef send_image(img_path, content):"""发送图片"""passdef send_emoji(emoji):"""发送表情"""passdef send_file(path):"""发送文件"""passfunction_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("输入选择的序号:") # 1item = function_dict.get(choice) # [ send_message,  ['15131255089', '你好呀']],
if not item:print("输入错误")
else:# 执行函数func = item[0] # send_messageparam_list = item[1] #  ['15131255089', '你好呀']func(*param_list) # send_message(*['15131255089', '你好呀'])

4.2 函数名赋值

  • 将函数名赋值给其他变量,函数名其实就个变量,代指某函数;如果将函数名赋值给另外一个变量,则此变量也会代指该函数,例如:

    def func(a1,a2):print(a1,a2)new_fuc = func# 此时,new_fuc和func都代指上面的那个函数,所以都可以被执行。
    func(1,1)
    new_fuc(2,2)
    
  • 对函数名重新赋值,如果将函数名修改为其他值,函数名便不再代指函数,例如:

    def func(a1,a2):print(a1,a2)# 执行func函数
    func(11,22)# func重新赋值成一个字符串
    func = "小峰"print(func)
    

    注意:由于函数名被重新定义之后,就会变量新被定义的值,所以大家在自定义函数时,不要与python内置的函数同名,否则会覆盖内置函数的功能。

4.3 函数名做参数和返回值

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

  • 参数

    def plus(num):return num + 100def handler(func):res = func(10) # 110msg = "执行func,并获取到的结果为:{}".format(res)print(msg) # 执行func,并获取到的结果为:110# 执行handler函数,将plus作为参数传递给handler的形式参数func
    handler(plus)
    
  • 返回值

    def plus(num):return num + 100def handler():print("执行handler函数")return plusresult = handler()
    data = result(20) # 120
    print(data)
    

5. 作用域

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

5.1 函数为作用域

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

def func():age = 20#局部变量,局部变量优先原则print(age)def handler():print(age)age=5#全局变量
func()
handler()
print(age)

5.2 全局和局部

Python中以函数为作用域,函数的作用域其实是一个局部作用域。

# 全局变量(变量名大写)
COUNTRY = "中国"
CITY_LIST = ["北京","上海","深圳"]def download():# 局部变量url = "http://www.xxx.com"

COUNTRYCITY_LIST是在全局作用域中,全局作用域中创建的变量称之为【全局变量】,可以在全局作用域中被使用,也可以在其局部作用域中被使用。download函数内部维护的就是一个局部作用域,在各自函数内部创建变量称之为【局部变量】,且局部变量只能在此作用域中被使用。局部作用域中想使用某个变量时,寻找的顺序为:优先在局部作用域中寻找,如果没有则去上级作用域中寻找。(局部变量优先的原则)

注意:全局变量一般都是大写。

示例1:在局部作用域中读取全局作用域的变量。

COUNTRY = "中国"
CITY_LIST = ["北京", "上海", "深圳"]def download():url = "http://www.xxx.com"print(url)print(COUNTRY)print(CITY_LIST)print(COUNTRY)
download()

示例2:局部作用域和全局作用域变量同名,局部变量优先

CITY_LIST = ["北京", "上海", "深圳"]def download():url = "http://www.xxx.com"CITY_LIST = ["河北", "河南", "山西"]print(url)print(CITY_LIST)print(CITY_LIST)
download()

5.3 global关键字

默认情况下,在局部作用域对全局变量只能进行:读取和修改内部元素(可变类型),无法对全局变量进行重新赋值。

  • 读取

    a=2
    def fun():print(a)  #对全局变量的读取fun()
  • 修改内部元素(可变类型)

    a=[2,"xiaofeng",(1,5)]
    print(a)
    def fun():a.append("liu")  #对内部元素的修改a[2]=3
    fun()
    print(a)
  • 无法对全局变量重新赋值

    a=2
    def fun():a=9print(a)
    fun() #9
    print(a) #2

如果想要在局部作用域中对全局变量重新赋值,则可以基于 global关键字实现,例如:

a=2
def fun():global aa=9print(a)
print(a)
fun()
print(a)

三、函数的进阶

  • 函数的嵌套
  • 闭包
  • 装饰器

上述内容均属于函数部分必备知识,以后开发时直接和间接都会使用,请务必理解(重在理解,不要去死记硬背)。

1. 函数嵌套

Python中以函数为作用域在作用域中定义的相关数据只能被当前作用域或子作用域使用

1.1 函数在作用域中

其实,函数也是定义在作用域中的数据,在执行函数时候,也同样遵循:优先在自己作用域中寻找,没有则向上一接作用域寻找,例如:

# 1. 在全局作用域定义了函数func
def func():print("你好")# 2. 在全局作用域找到func函数并执行。
func()# 3.在全局作用域定义了execute函数
def execute():print("开始")# 优先在当前函数作用域找func函数,没有则向上级作用域中寻找。func()print("结束")# 4.在全局作用域执行execute函数
execute()

1.2 函数定义的位置

函数不仅可以定义在全局作用域中,其实函数也可以定义在局部作用域,这样函数被局部作用域和其子作用于中调用(函数的嵌套)。

def func(a):print("开始")def sum(a):return a+2def sub(a):return a-2if a>3:return sum(a)else:return sub(a)print(func(2))
print(func(4))

到现在你会发现,只要理解数据定义时所存在的作用域,并根据从上到下代码执行过程进行分析,再怎么嵌套都可以搞定。有没有想过为什么要这么嵌套定义?把函数都定义在全局不好吗?

其实,大多数情况下我们都会将函数定义在全局,不会嵌套着定义函数。不过,当我们定义一个函数去实现某功能,想要将内部功能拆分成N个函数,又担心这个N个函数放在全局会与其他函数名冲突时(尤其多人协同开发)可以选择使用函数的嵌套

def f1():passdef f2():passdef func():f1()f2()

1.3 嵌套引发的作用域问题

基于内存和执行过程分析作用域。

name = "xiaofeng"
def run():name = "alex"def inner():print(name)def test():print("hello")return [inner, test]
func_list = run()
func_list[0]()
func_list[1]()

三句话搞定作用域:

  • 优先在自己的作用域找,自己没有就去上级作用域。
  • 在作用域中寻找值时,要确保此次此刻值是什么。
  • 分析函数的执行,并确定函数作用域链。(函数嵌套)

2.闭包

闭包,简而言之就是将数据封装在一个包(区域)中,使用时再去里面取。(本质上闭包是基于函数嵌套搞出来一个中特殊嵌套

  • 闭包应用场景1:封装数据防止污染全局。

    def func(age):name = "xiaofeng"def f1():print(name, age)def f2():print(name, age)f1()f2()
    func(123)
    
  • 闭包应用场景2:封装数据封到一个包里,使用时在取。 

    def task(src):def inner():print(src)return innertask('hello')()
    

3.装饰器

现在给你一个函数,在不修改函数源码的前提下,实现在函数执行前和执行后分别输入 “before” 和 “after”。

def func():print("我是func函数")value = (11,22,33,44) return valueresult = func()
print(result)

3.1 装饰器的引入

  • 一般刚开始学编程的实现思路:
def func():print("before")print("我是func函数")value = (11,22,33,44)     print("after")    return valueresult = func()
  • 更高级的的实现思路:
def func():print("我是func函数")value = (11, 22, 33, 44)return value
def outer(origin):def inner():print('inner')origin()print("after")return innerfunc = outer(func)
result = func()
  • 再对其进行优化后的思路,即就是处理返回值:
def func():print("我是func函数")value = (11, 22, 33, 44)return value
def outer(origin):def inner():print('inner')res = origin()print("after")return res  #保持和原函数一样的效果return innerresult = outer(func)()

3.2 装饰器的基本语法结构

在Python中有个一个特殊的语法糖,即就是本讲所说的装饰器:

def outer(origin):def inner():print('before')res = origin()print("after")return resreturn inner@outer
def func():print("我是func函数")value = (11, 22, 33, 44)return valuefunc()

3.3 装饰器在多函数中的应用

请在这3个函数执行前和执行后分别输入 “before” 和 “after”

def func1():print("我是func1函数")value = (11, 22, 33, 44)return value        
def func2():print("我是func2函数")value = (11, 22, 33, 44)return value 
def func3():print("我是func3函数")value = (11, 22, 33, 44)return value   
  • 学完装饰器的实现思路:
def outer(org):def inner():print("before")res = org()print("after")return resreturn inner@outer
def func1():print("我是func1函数")value = (11, 22, 33, 44)return value@outer
def func2():print("我是func2函数")value = (11, 22, 33, 44)return value@outer
def func3():print("我是func3函数")value = (11, 22, 33, 44)return valuefunc1()
func2()
func3()

装饰器在不修改原函数内容的前提下通过@函数可以实现在函数前后自定义执行一些功能(批量操作会更有意义)

3.4 装饰器的再优化,语法结构的改良

优化以支持多个参数的情况。

def outer(origin):def inner(*args, **kwargs):print("before 110")res = origin(*args, **kwargs)  # 调用原来的func函数print("after")return resreturn inner@outer  # func1 = outer(func1)
def func1(a1):print("我是func1函数")value = (11, 22, 33, 44)return value@outer  # func2 = outer(func2)
def func2(a1, a2):print("我是func2函数")value = (11, 22, 33, 44)return valuefunc1(1)
func2(11, a2=22)
  • 实现原理基于@语法和函数闭包,将原函数封装在闭包中,然后将函数赋值为一个新的函数(内层函数),执行函数时再在内层函数中执行闭包中的原函数

  • 实现效果:可以在不改变原函数内部代码 和 调用方式的前提下,实现在函数执行和执行扩展功能。

  • 适用场景:多个函数系统统一在执行前后自定义一些功能。

3.5 重要补充

       装饰器实际上就是将原函数更改为其他的函数,然后再此函数中再去调用原函数。其实,一般情况下大家不用 functools 也可以实现装饰器的基本功能,但后期在项目开发时,不加 functools 会出错( 内部会读取__name__,且__name__重名的话就报错 ,所以在此大家就要规范起来自己的写法。 functools是 Python 标准库中的一个模块,提供了对高阶函数(即操作或返回其他函数的函数)的支持。它包含了一些常用的函数装饰器和工具,用于简化函数操作和优化代码。

  • 获取函数名
def test():pass
print(test.__name__) # testdef auth(func):def inner(*args, **kwargs):return func(*args, **kwargs)return inner@auth
def test():pass
test()
print(test.__name__) # inner
import functoolsdef auth(func):@functools.wraps(func)def inner(*args, **kwargs):return func(*args, **kwargs)return inner@auth
def handler():passhandler()
print(handler.__name__)  # handler
def auth(func):@functools.wraps(func)def inner(*args, **kwargs):"""巴巴里吧"""res = func(*args, **kwargs)  # 执行原函数print(args)print(kwargs)return resreturn inner
@auth
def func(*args, **kwargs):passprint(func(1,2,name="xiaofeng"))
print(func.__name__)

4. 匿名函数

传统的函数的定义包括了:函数名 + 函数体匿名函数则是基于lambda表达式实现定义一个可以没有名字的函数,在编写匿名函数时,由于受限 函数体只能写一行,所以匿名函数只能处理非常简单的功能。基于Lambda定义的函数格式为:lambda 参数:函数体

例如:

data_list = [ lambda x:x+100,  lambda x:x+110, lambda x:x+120 ]print( data_list[0] )
  • 参数,支持任意参数。

    lambda x: 函数体
    lambda x1,x2: 函数体
    lambda *args, **kwargs: 函数体
    
  • 函数体,只能支持单行的代码。

  • 返回值,默认将函数体单行代码执行的结果返回给函数的执行处。

    func = lambda x: x + 100v1 = func(10)
    print(v1) # 110
    

注:匿名函数适用于简单的业务处理,可以快速并简单的创建函数。

补充:三元运算

简单的函数,可以基于lambda表达式实现。简单的条件语句,可以基于三元运算实现

# 结果 =  条件成立时    if   条件   else   不成立
例如:

num=int(input("Enter a number:"))
print("hello") if num==1 else print("goodbye")

lambda表达式和三元运算没有任何关系,属于两个独立的知识点。但是掌握三元运算之后,以后再编写匿名函数时,就可以处理再稍微复杂点的情况了,例如:

#匿名函数,lambda结合三元运算就可以实现比较复杂的功能
func = lambda x: "大了" if x > 66 else "小了"
print(func(1)) # "小了"
print(func(100)) # "大了"

5. 生成器

        生成器是由 函数+yield关键字 创造出来的写法,在特定情况下,用他可以帮助我们节省内存。

  • 生成器函数,函数中有yield存在时,这个函数就是生产生成器函数。

    def func():print(111)yield 1
    
  • 生成器对象,执行生成器函数时,会返回一个生成器对象。

    def func():print(111)yield 1print(222)yield 2print(333)yield 3print(444)data = func()
    v1 = next(data)
    print(v1)
    v2 = next(data)
    print(v2)
    v3 = next(data)
    print(v3)
    v4 = next(data)
    print(v4) # 执行生成器函数func,返回的生成器对象。
    # 注意:执行生成器函数时,函数内部代码不会执行。 结束或中途遇到return,程序提示StopIteration错误
    

        生成器的特点是,记录在函数中的执行位置,下次执行next时,会从上一次的位置基础上再继续向下执行。

应用场景:

        当以后需要我们在内存中创建很多数据时,可以想着用基于生成器来实现一点一点生成(用一点生产一点),以节省内存的开销。

扩展:send()

def func():print(111)v1 = yield 1print(v1)print(222)v2 = yield 2print(v2)print(333)data = func()
n1 = data.send(None)
print(n1)
n2 = data.send(666)
print(n2)
n3 = data.send(777)

send方法的工作原理

  1. 生成器函数在遇到yield语句时会暂停执行,并返回yield后面的值。
  2. 调用send方法时,生成器会从上次暂停的地方继续执行,并将send方法的参数作为yield表达式的结果。
  3. 生成器继续执行,直到遇到下一个yield语句或函数结束。

注意事项

  • 在第一次调用生成器时,不能直接使用send方法,因为生成器还没有执行到第一个yield语句。通常需要先调用next方法或send(None)来启动生成器。
  • 如果生成器已经结束(即没有更多的yield语句),再调用send方法会抛出StopIteration异常。

6.内置函数

Python内部为我们提供了很多方便的内置函数,在此整理出来36个给大家来讲解。

  • 第1组

#内置函数
#abs 求绝对值
print(abs(-1))
#pow指数
print(pow(2,3))
#sum 求和
print(sum([1,4,9]))
#divmod 求商和余数
print(divmod(7,4))
#min 求最小值
print(min([1,8,-2]))
#max 求最大值
print(max([1,8,-2]))
#any 是否存在为真
print(any([False,0,'']))
#all 是否全为真
print(all([1,2,3]))#十进制转化为二进制
print(bin(10))
#十进制转化为八进制
print(oct(10))
#十进制转化为十六进制
print(hex(10))
# ord,获取字符对应的unicode码点(十进制)
print(ord('A'))
# chr,根据码点(十进制)获取对应字符
print(chr(ord('A')+1))

  • 第2组,数据类型中有讲到

int、foat、str,unicode编码、bytes,utf-8、gbk编码、bool、list、dict、tuple、set

  • 第3组(13个)

    len、print、input、open、type,获取数据类型、range、id
  • 补充:
  • enumerate

    #enumerate  枚举
    #语法:enumerate(sequence, [start=0])
    v1 = ["小峰", "alex", 'root']
    for num, value in enumerate(v1, 1):print(num, value)# hash 它用于获取一个对象的哈希值。哈希值是一个固定长度的整数,用于表示一个对象的状态或标识,通常用于构建字典、集合等数据结构,以及进行数据加密等方面。
    print(hash("xiaofeng"))#callable,是否可执行,后面是否可以加括号。
    #通常用来判断是否为函数
    def func():print("Hello World")
    v=1
    print(callable(func))
    print(callable(v))#zip
    # Python 3:zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的对象,这样做的好处是节约了不少的内存。我们可以使用 list() 转换来输出列表
    v1=(1,5,8,3)
    v2=(5,71,1,0)
    v3=(20,4,6,9,39)
    print(zip(v1,v2,v3))#返回的是一个对象,最后一列不全,所以剔除掉
    for i in zip(v1,v2,v3):print(i)
  • sorted,排序

    #eg1:
    print(sorted([1,7,0,-2,7]))  #由小到大的排序
    print(sorted([1,7,0,-2,7],reverse=True))  #由大到小的排序#eg2:
    info = {"wupeiqi": {'id': 10,'age': 119},"root": {'id': 20,'age': 29},"seven": {'id': 9,'age': 9},"admin": {'id': 11,'age': 139},
    }
    result = sorted(info.items(), key=lambda x: x[1]['age'])
    print(result)
    
    data_list = ['1-5 编译器和解释器.mp4','1-17 今日作业.mp4','1-9 Python解释器种类.mp4','1-16 今日总结.mp4','1-2 课堂笔记的创建.mp4','1-15 Pycharm使用和破解(win系统).mp4','1-12 python解释器的安装(mac系统).mp4','1-13 python解释器的安装(win系统).mp4','1-8 Python介绍.mp4', '1-7 编程语言的分类.mp4','1-3 常见计算机基本概念.mp4','1-14 Pycharm使用和破解(mac系统).mp4','1-10 CPython解释器版本.mp4','1-1 今日概要.mp4','1-6 学习编程本质上的三件事.mp4','1-18 作业答案和讲解.mp4','1-4 编程语言.mp4','1-11 环境搭建说明.mp4'
    ]
    result = sorted(data_list, key=lambda x: int(x.split(' ')[0].split("-")[-1]) )
    print(result)
    

7.推导式

        推导式是Python中提供了一个非常方便的功能,可以让我们通过一行代码实现创建list、dict、tuple、set 的同时初始化一些值。请创建一个列表,并在列表中初始化:0、1、2、3、4、5、6、7、8、9…299 整数元素。

  • 列表

    对列表的初始化
    num_list=[i for i in range(10)]num_list=(i,i,i) for i in range(10)结合三元运算对列表进行初始化
    num_list=[i for i in range(10) if i%2==0]
  • 集合

    num_set1 = { i for i in range(10)}
    num_set2 = { (i,i,i) for i in range(10)}
    num_set3 = { (i,i,i) for i in range(10) if i%2==0 }
  • 字典

    num_dict1 = { i:i for i in range(10)}
    num_dict2 = { i:(i,11) for i in range(10)}
    num_dict3 = { i:(i,11) for i in range(10) if i>7}
  • 元组,不同于其他类型。

        不会立即执行内部循环去生成数据,而是得到一个生成器对象。

data = (i for i in range(10))
print(data)
for item in data:print(item)
  • 看代码写结果(新浪微博面试题)

    data_list = [lambda x: x + i for i in range(10)]  # [函数,函数,函数]   i=9v1 = data_list[0](100)
    v2 = data_list[3](100)
    print(v1, v2)  # 109 109
    

补充:

  1. 推导式支持嵌套

    # 一副扑克牌poker_list = [ (color,num) for num in range(1,14) for color in ["红桃", "黑桃", "方片", "梅花"]]
    print(poker_list)	
  2. 烧脑面试题

    def num():return [lambda x: i * x for i in range(4)]
    # 1. num()并获取返回值  [函数,函数,函数,函数] i=3 ,即就是相同的函数和相同的i
    # 2. for循环返回值
    # 3. 给每个函数传参都是2
    result = [m(2) for m in num()]  # [6,6,6,6]
    print(result)
    def num():return (lambda x: i * x for i in range(4))# 1. num()并获取返回值  生成器对象
    # 2. for循环返回值
    # 3. 返回值的每个元素(2)
    result = [m(2) for m in num()]  # [0,2,4,6 ]
    print(result)
    

 本   篇   完   结   …  …


持     续     更     新     中   …    … 

 

相关文章:

  • 算法题(150):拼数
  • 用Python将 PDF 中的表格提取为 Excel/CSV
  • OpenCV计算机视觉实战(6)——经典计算机视觉算法
  • Python60日基础学习打卡D30
  • LangChain多模态智能体:文生图、识图、RAG问答与小说生成全攻略
  • Apidog MCP服务器,连接API规范和AI编码助手的桥梁
  • Linux环境搭载
  • 使用MCP驱动IDA pro分析样本
  • MongoDB的管道聚合
  • 物联网之使用Vertx实现HTTP/WebSocket最佳实践
  • WordPress搜索引擎优化的最佳重定向插件:入门指南
  • 146. LRU 缓存
  • C++字符串处理:`std::string`和`std::string_view`的区别与使用
  • R 语言科研绘图第 49 期 --- 热力图-相关性
  • Geotools中关于坐标转换纬度超限问题
  • vue2、vue3项目打包生成txt文件-自动记录打包日期:git版本、当前分支、提交人姓名、提交日期、提交描述等信息 和 前端项目的版本号json文件
  • 物联网数据湖架构
  • 【C++】异常解析
  • YouTube视频字幕转成文章算重复内容吗?
  • 五分钟完成PolarDB替换postgresql
  • 确诊前列腺癌后,拜登首次发声
  • 8000余万元黄金投入研发后“不知去向”,咋回事?
  • 苏丹港持续遭无人机袭击,外交部:呼吁各方保护民用设施和平民安全
  • 上海迪士尼蜘蛛侠主题园区正式动工,毗邻“疯狂动物城”
  • 从《缶翁的世界》开始,看吴昌硕等湖州籍书画家对海派的影响
  • 湖南4个县市区被确定为野生蘑菇中毒高风险区:中毒尚无特效解毒药