Python函数——万字详解

------ 小 峰 编 程

导 语:

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

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

1、内置函数

python 复制代码
len、bin、oct、hex 等

2、自定义函数

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

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

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

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

  • 自定义模块

一. 文件操作

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

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

    python 复制代码
    name = "武沛齐"
  • 字节类型(bytes)

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

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

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

python 复制代码
#	- 路径:
#		相对路径:'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. 读文件

  • 读文本文件

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

    python 复制代码
    file_object = open('my.png', 'rb')
    data = file_object.read()
    file_object.close()
    print(data)

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

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

python 复制代码
#注意路径相关的问题
错误写法: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模块结合起来使用,判断一个路径是否存在。

    python 复制代码
    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.写文件

  • 写文本文件

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

    python 复制代码
    f1 = open('a1.png',mode='rb')
    content = f1.read()
    f1.close()
    
    f2 = open('a2.png',mode='wb')
    f2.write(content)
    f2.close()

基础案例:

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

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

python 复制代码
with open('info.txt', mode='rb') as f:
    data = f.read()
    print(data)

二. 初识函数

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

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

例如:

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

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

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

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

    python 复制代码
    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发邮件,以下是我为大家提供的发邮件的一个函数。

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(["小峰", "[email protected]"])
    # 邮件上显示的主题
    msg['Subject'] = "邮件主题"
	
    # ### 2.发送邮件 ### 
    server = smtplib.SMTP_SSL("smtp.126.com")
    server.login("[email protected]", "WIYSAILOVUKPQGHY")
    server.sendmail("[email protected]", email, msg.as_string())
    server.quit()
    
v3 = "[email protected]"
send_email(v3)

1.1 参数

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

python 复制代码
#定义有三个参数的函数(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 默认参数

python 复制代码
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 动态参数

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

  • *
python 复制代码
def func(*args):
    print(args) # 元组类型 (22,)   (22,33,99,) ()

# 只能按照位置传参
func(22)
func(22,33)
func(22,33,99)
func()
  • **

    python 复制代码
    def func(**kwargs):
        print(kwargs) # 字典类型 {"n1":"小峰"}    {"n1":"小峰","age":"18","email":"xxxx"}  {}
        
    # 只能按关键字传参
    func(n1="小峰")
    func(n1="小峰",age=18)
    func(n1="小峰",age=18,email="[email protected]")
  • *和**

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

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

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

    python 复制代码
    # 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)
  • 形参固定,实参用*和**

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

    python 复制代码
    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字符串格式化时,可以可以这样:

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

2. 函数返回值

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

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

    python 复制代码
    def func():
        value = 1 + 1
        
    ret = func()
    print(ret) # None

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

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

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

    python 复制代码
    def func():
        print("开始")
        for i in range(1,10):
            if i%2==0:
                print(i)
            else:
                return
        print("结束")
    func()
    
    # 输出
    开始

扩展:密码都不是明文。

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

3.参数的补充

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

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

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

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

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

复制代码
def func(data):
    print(data,id(data))
v='小峰'
print(id(v))
func(v)

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

  • 节省内存

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

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 等关键字来实现传递内存地址。当然,如果你不想让外部的变量和函数内部参数的变量一致,也可以选择将外部值拷贝一份,再传给函数。

python 复制代码
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 函数的返回值是内存地址

python 复制代码
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指向的函数内部创建的那块内存地址。

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

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

python 复制代码
def func(a1,a2=18):
    print(a1,a2)

> 原理:

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

  • python 复制代码
    # 在函数内存中会维护一块区域存储
    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
  • 大坑

    python 复制代码
    # 在内部会维护一块区域存储 [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 ]
  • 深坑

    python 复制代码
    # 内存中创建空间存储 [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. 函数和函数名

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

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

python 复制代码
# 正确
def add(n1,n2):
    return n1 + n2

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

4.1 函数做元素

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

python 复制代码
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 ) # 执行函数 func,并获取返回值;print再输出返回值。

print( data_list[2]() ) # 123

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

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

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

    python 复制代码
    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,
        "5": xxx
    }
    
    print("欢迎使用xx系统")
    print("请选择:1.发送消息;2.发送图片;3.发送表情;4.发送文件")
    choice = input("输入选择的序号") # "1"
    
    func = function_dict.get(choice)
    if not func:
        print("输入错误")
    else:
        # 执行函数
        func()
  • 情景2,例如:某个特定情况,要实现发送短信、微信、邮件。

    python 复制代码
    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()

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

情景:

python 复制代码
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', '你好呀'])

4.2 函数名赋值

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

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

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

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

4.3 函数名做参数和返回值

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

  • 参数

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

    python 复制代码
    def plus(num):
        return num + 100
    
    def handler():
    	print("执行handler函数")
        return plus
        
    result = handler()
    data = result(20) # 120
    print(data)

5. 作用域

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

5.1 函数为作用域

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

python 复制代码
def func():
    age = 20#局部变量,局部变量优先原则
    print(age)

def handler():
    print(age)
    
age=5#全局变量
func()
handler()
print(age)

5.2 全局和局部

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

python 复制代码
# 全局变量(变量名大写)
COUNTRY = "中国"
CITY_LIST = ["北京","上海","深圳"]

def download():
    # 局部变量
    url = "http://www.xxx.com"

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

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

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

python 复制代码
COUNTRY = "中国"
CITY_LIST = ["北京", "上海", "深圳"]


def download():
    url = "http://www.xxx.com"
    print(url)
    print(COUNTRY)
    print(CITY_LIST)

print(COUNTRY)
download()

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

python 复制代码
CITY_LIST = ["北京", "上海", "深圳"]

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

print(CITY_LIST)
download()

5.3 global关键字

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

  • 读取

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

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

    python 复制代码
    a=2
    def fun():
        a=9
        print(a)
    fun() #9
    print(a) #2

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

python 复制代码
a=2
def fun():
    global a
    a=9
    print(a)
print(a)
fun()
print(a)

三、函数的进阶

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

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

1. 函数嵌套

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

1.1 函数在作用域中

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

python 复制代码
# 1. 在全局作用域定义了函数func
def func():
    print("你好")
    
# 2. 在全局作用域找到func函数并执行。
func()

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

# 4.在全局作用域执行execute函数
execute()

1.2 函数定义的位置

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

python 复制代码
def func(a):
    print("开始")
    def sum(a):
        return a+2
    def sub(a):
        return a-2
        
    if a>3:
        return sum(a)
    else:
        return sub(a)

print(func(2))
print(func(4))

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

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

python 复制代码
def f1():
    pass

def f2():
    pass

def func():
	f1()
    f2()

1.3 嵌套引发的作用域问题

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

python 复制代码
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:封装数据防止污染全局。

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

    python 复制代码
    def task(src):
        def inner():
            print(src)
        return inner
    
    task('hello')()

3.装饰器

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

python 复制代码
def func():
    print("我是func函数")
    value = (11,22,33,44) 
    return value
    
result = func()
print(result)

3.1 装饰器的引入

  • 一般刚开始学编程的实现思路:
python 复制代码
def func():
    print("before")
    print("我是func函数")
    value = (11,22,33,44)     
    print("after")    
    return value
    
result = func()
  • 更高级的的实现思路:
python 复制代码
def func():
    print("我是func函数")
    value = (11, 22, 33, 44)
    return value
def outer(origin):
    def inner():
        print('inner')
        origin()
        print("after")
    return inner

func = outer(func)
result = func()
  • 再对其进行优化后的思路,即就是处理返回值:
python 复制代码
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 inner

result = outer(func)()

3.2 装饰器的基本语法结构

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

python 复制代码
def outer(origin):
    def inner():
        print('before')
        res = origin()
        print("after")
        return res
    return inner

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

func()

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

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

python 复制代码
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   
  • 学完装饰器的实现思路:
python 复制代码
def outer(org):
    def inner():
        print("before")
        res = org()
        print("after")
        return res
    return 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 value

func1()
func2()
func3()

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

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

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

python 复制代码
def outer(origin):
    def inner(*args, **kwargs):
        print("before 110")
        res = origin(*args, **kwargs)  # 调用原来的func函数
        print("after")
        return res
    return 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 value

func1(1)
func2(11, a2=22)
  • 实现原理基于@语法和函数闭包,将原函数封装在闭包中,然后将函数赋值为一个新的函数(内层函数),执行函数时再在内层函数中执行闭包中的原函数

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

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

3.5 重要补充

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

  • 获取函数名
python 复制代码
def test():
    pass
print(test.__name__) # test

def auth(func):
    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    return inner

@auth
def test():
    pass
test()
print(test.__name__) # inner
python 复制代码
import functools

def auth(func):
    @functools.wraps(func)
    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    return inner

@auth
def handler():
    pass

handler()
print(handler.__name__)  # handler
python 复制代码
def auth(func):
    @functools.wraps(func)
    def inner(*args, **kwargs):
        """巴巴里吧"""
        res = func(*args, **kwargs)  # 执行原函数
        print(args)
        print(kwargs)
        return res

    return inner
@auth
def func(*args, **kwargs):
    pass

print(func(1,2,name="xiaofeng"))
print(func.__name__)

4. 匿名函数

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

例如:

python 复制代码
data_list = [ lambda x:x+100,  lambda x:x+110, lambda x:x+120 ]

print( data_list[0] )
  • 参数,支持任意参数。

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

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

    python 复制代码
    func = lambda x: x + 100
    
    v1 = func(10)
    print(v1) # 110

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

补充:三元运算

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

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

例如:

python 复制代码
num=int(input("Enter a number:"))
print("hello") if num==1 else print("goodbye")

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

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

5. 生成器

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

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

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

    python 复制代码
    def func():
        print(111)
        yield 1
        print(222)
        yield 2
        print(333)
        yield 3
        print(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()

python 复制代码
def func():
    print(111)
    v1 = yield 1
    print(v1)
    print(222)
    v2 = yield 2
    print(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组
python 复制代码
#内置函数
#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

    python 复制代码
    #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,排序

    python 复制代码
    #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)
    python 复制代码
    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 整数元素。

  • 列表

    python 复制代码
    对列表的初始化
    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]
  • 集合

    python 复制代码
    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 }
  • 字典

    python 复制代码
    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}
  • 元组,不同于其他类型。

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

python 复制代码
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=9
    
    v1 = data_list[0](100)
    v2 = data_list[3](100)
    print(v1, v2)  # 109 109

补充:

  1. 推导式支持嵌套

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

    python 复制代码
    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)
    python 复制代码
    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)

本 篇 完 结 ... ...


持 续 更 新 中 ... ...

相关推荐
鸡鸭扣5 分钟前
leetcode hot100:解题思路大全
数据结构·python·算法·leetcode·力扣
无奇不有 不置可否5 分钟前
Java中的设计模式
java·开发语言·设计模式
金融数据出海14 分钟前
使用PHP对接东南亚、日本、印度和印度尼西亚股票数据源
android·开发语言·php
多啦爱梦的梦想14 分钟前
项目中把webpack 打包改为vite 打包
前端·webpack
Hello.Reader22 分钟前
使用 NGINX 的 `ngx_http_secure_link_module` 模块保护资源链接
运维·nginx·http
小刘不知道叫啥24 分钟前
简单说一下 Webpack分包
前端·javascript·webpack·node.js
Dontla28 分钟前
《黑马前端ajax+node.js+webpack+git教程》(笔记)——node.js教程+webpack教程(nodejs教程)
前端·ajax·node.js
顾子茵30 分钟前
游戏开发实战(一):Python复刻「崩坏星穹铁道」嗷呜嗷呜事务所---源码级解析该小游戏背后的算法与设计模式【纯原创】
python·算法·游戏
@Turbo@33 分钟前
【QT】一个界面中嵌入其它界面(二)
开发语言·数据库·qt
ALex_zry36 分钟前
C++ Kafka客户端(cppkafka)安装与问题解决指南
开发语言·c++·kafka