Skip to content

函数的定义 ​

函数是组织好的、可重复使用的,用来实现特定功能的代码片段。

定义与调用语法:

python
# 定义函数
def 函数名(参数列表):
    函数体
    ......
    return 返回值

# 调用函数
函数名(参数)

定义与调用样例:

python
# 定义函数
def say_hello():
    print("大家好!")


# 调用函数
say_hello()

**提示:**函数定义时的参数列表与返回值语句是可有可无的(由需求确定),函数必须先定义后调用执行(函数定义时,内部代码并未执行,在调用时才执行)。

注意:

  • 函数必须先定义,在调用
  • 函数定义时,并不会执行,只有在调用函数时,函数体的逻辑才会运行
  • 函数中通过缩进来描述归属关系

函数的变量作用域 ​

  • 变量的作用域指的是变量的作用范围,也就是说这个变量,在哪里可以使用,在哪儿不可以使用。
  • 根据作用范围的不同,常见的变量作用域分为两类:全局变量 和 局部变量。
python
"""
全局变量:在函数、推导式之外定义的变量,称之为全局变量,在整个文件中(包括函数内)都可以使用(通常定义在文件的顶部)。
局部变量:在函数、推导式内部定义的变量,称之为局部变量,只能在该函数内部使用,外部无法访问(执行完毕后,会自动销毁其内部局部变量) 。
优先级:在使用变量时,优先在当前作用域范围内查找,如果未查询到,再到上一层作用域查询。
"""
tax_rate = 0.05 #税率
# 定义函数
def cal_tax(total_price):
    tax_rate = 0.08
    if total_price >= 10000: #金额大于等于10000
        tax = total_price * tax_rate #按税率计算税费
    elif total_price >= 5000: #金额大于等于5000
        tax = total_price * tax_rate * 0.8 #按税率打8折计算税费
    else:
        tax = 0 #金额小于5000,不缴税
    return  tax #返回税费
# 调用函数
my_tax = cal_tax(10000)
print(f"税前价格: 10000, 税: {my_tax}")
"""
当我们在执行Python代码时,对应的优先级顺序为:优先在当前作用域范围内查找,如果未查找到,再到上一层作用域查询 。
也就意味着,当我们在调用 cal_tax(1000) 这个函数的时候,用到 tax_rate 这个变量,那这个变量优先使用的是在函数内部所定义的这个局部变量 tax_rate = 0.08,而并非全局变量; 而如果在函数内部并未定义这个局部变量,此时才会使用全局变量 tax_rate 。
"""

global关键字 ​

那如果我们需要在函数内部,需要使用全局变量 并 修改全局变量的值(对全局变量重新赋值),此时,就可以使用python中提供的关键字 global。

global关键字用于明确的告诉Python解释器,在函数中要使用全局变量,使得可以在函数内部修改全局变量的值。

python
# 全局变量 : 在函数外部 或 函数的内部都是可以访问的 ;
tax_rate = 0.05

# 定义函数
def cal_tax(total_price):
    global tax_rate  # 告诉Python解释器, 函数中使用的是全局变量 tax_rate
    tax_rate = 0.08  # 修改全局变量的值
    if total_price > 10000:
        tax = total_price * tax_rate
    elif total_price >= 5000:
        tax = total_price * tax_rate * 0.8
    else:
        tax = 0
    return  tax

my_tax = cal_tax(10000)
print(f"税前价格: 10000, 税: {my_tax}")
print(tax_rate)

注意:

  • 在基于global声明全局变量时,要先声明,再使用。

  • 尽量避免在函数中使用全局变量,因为会使代码难以维护和调试

  • 考虑使用函数参数和返回值来传递数据,而不是依赖全局变量

  • global主要用在程序的状态、配置和计数器等场景中

参数与返回值 ​

在定义函数时,根据业务需要,可以指定参数与返回值,具体格式如下:

python
# 定义函数
def 函数名(形参列表):
    函数体
    ......
    return 返回值

# 调用函数
函数名(实参)
  • 形参(形式参数):函数定义时括号里的参数,只能在函数内使用(局部变量)
  • 实参(实际参数):函数在实际调用时传入的参数。

案例如下:

python
# 函数1: 计算圆的面积(circle_area)并返回 -- 半径
# 定义函数
def circle_area(r):
    area = 3.14 * r ** 2
    return area  # 返回结果
# 调用函数
result = circle_area(10)  # result表示结果的意思
print(f"圆的面积为:{result}")


# 函数2: 计算长方形的面积(rectangle_area)并返回  -- 长, 宽
# 定义函数
def rectangle_area(l, w):
    return l * w
# 调用函数
result = rectangle_area(10, 5)
print(f"长方形的面积为:{result}")


# 函数3: 计算长方形的周长和面积(rectangle_perimeter_area)并返回  -- 长, 宽
# 定义函数
def rectangle_perimeter_area(l, w):
    return (l + w) * 2, l * w
# 调用函数
result = rectangle_perimeter_area(10, 5)
print(result, type(result))  # (30, 50) <class 'tuple'>
print(f"长方形周长:{result[0]},长方形面积:{result[1]}")

# 解包
perimeter,area = rectangle_perimeter_area(10, 5)
print(f"长方形周长:{perimeter},长方形面积:{area}")

注意:

  • 函数定义时如果有多个参数,多个参数之间使用逗号(,)分隔 。
  • return语句只有返回功能,而没有输出打印的功能,如果要输出,需要结合print()函数来实现 。
  • 函数是可以定义多个返回值的,多个返回值会封装到一个元组中。

位置参数 ​

位置参数:调用函数时根据函数定义时的位置来传递参数。

python
# 定义函数
def reg_stu( name, age,gender, city):
    print(f"注册成功,姓名:{name}, 年龄:{age}, 性别:{gender}, 城市:{city}")
    return {"name": name, "age": age, "gender": gender, "city": city}

# 位置参数:调用函数时根据函数定义时的位置来传递参数。
stu1 = reg_stu("张益达",20,"男","武汉")
print(stu1)

关键字参数 ​

**关键字参数:**调用函数时以形参名称作为关键字,以 "形参名=值" 的形式来传递参数(不要求顺序)。

**特别注意:**如果位置参数与关键字参数混用,关键字参数必须在位置参数之后(关键字参数之间,没有顺序要求)

python
# 关键字参数:调用函数时以形参名称作为关键字,以 "形参名=值" 的形式来传递参数(不要求顺序)。
# 定义函数
def reg_stu(name, age, gender, city):
    print(f"注册成功,姓名:{name}, 年龄:{age}, 性别:{gender}, 城市:{city}")
    return {"name": name, "age": age, "gender": gender, "city": city}
# 关键字参数:调用函数时以形参名称作为关键字,以 "形参名=值" 的形式来传递参数(不要求顺序)。
# stu2 = reg_stu(name="张伟",age=20,gender="男",city="武汉")
stu2 = reg_stu(age=20,name="张伟",city="武汉",gender="男")
print(stu2)

# 位置参数和关键字参数混用:要求:如果位置参数与关键字参数混用,关键字参数必须在位置参数之后(关键字参数之间,没有顺序要求)
stu3 = reg_stu("张大炮",23,city="武汉",gender="男")
# stu3 = reg_stu(name="张大炮",age=23,"男","武汉")  # 报错:位置参数必须写在关键字参数前面
print(stu3)

那针对于这两种传参方式,都可以完成参数传递,那我们在项目开发中,到底应该使用哪种方式呢? 接下来,我们就通过一张表格对比一下:

位置参数关键字参数
形式s = calc(87, 68, 92, 85)s = calc(math=87, chinese=68, english=92, computer=85)
优点简洁可读性强、易维护和扩展
缺点可读性差、易出错、维护难代码繁琐
场景参数少(一般不超过3个),且类型不同参数较多,类型相同或容易混淆的场景

扩展:参数分隔符 ​

特别注意:请勿滥用!!!

1. / 仅限位置参数示例

/左侧参数只能位置传参,不能关键字传参

python
# a,b在/的左边:只能按位置传入;
# c,d在/的右边:既可以位置,也可以关键字传参。
def func(a, b, /, c, d):
    print(a, b, c, d)

# ✅正确:a,b使用位置传参
func(10, 20, 30, 40)
func(10, 20, c=30, d=40)

# ❌错误!a,b在 / 左边,不允许关键字传参
# func(a=10, b=20, c=30, d=40)

2.* 仅限关键字参数分隔符(单独的,不是*args)*

* 右侧参数只能关键字传参,不能位置传参

def func(a, b, *, c, d):
    print(a, b, c, d)

# ✅正确:c、d必须关键字传参
func(1, 2, c=3, d=4)

# ❌错误!*后面的c,d不能用位置传参
# func(1,2,3,4)

3./ 和 * 写在同一个函数里(完整组合)

python
def demo(a, b, /, mid, *, x, y):
    """
    a,b:只能位置传参
    mid:可以位置 / 关键字传参
    x,y:只能关键字传参
    """
    print(a, b, mid, x, y)

# ✅合法调用
demo(10, 20, 30, x=100, y=200)
demo(10, 20, mid=30, x=100, y=200)

# ❌错误:a,b使用关键字
# demo(a=10,b=20, mid=30, x=100,y=200)

# ❌错误:x,y使用位置传参
# demo(10,20,30,100,200)

4.区分容易混淆的写法

python
# 1./ 分隔符:限定左侧只能位置
def f(a, b, /, c): pass

# 2. * 分隔符:限定右侧只能关键字
def f(a, b, *, c): pass

# 3. *args:收集可变位置参数(收集多参数元组)
def f(*args): pass

# 4.**kwargs:收集可变关键字参数(收集字典)
def f(**kwargs): pass

默认参数 ​

默认参数也称为缺省参数,用于在定义函数时,为参数提供默认值,调用函数时,可以不传递有默认值的参数。

python
# 定义函数
def reg_stu(name, age, gender, city='北京'):
    print(f"注册成功,姓名:{name}, 年龄:{age}, 性别:{gender}, 城市:{city}")
    return {"name": name, "age": age, "gender": gender, "city": city}

# 调用函数
stu = reg_stu("张三", 18, "男")
print(stu)
stu = reg_stu("赵四", 22, "男", "深圳")
print(stu)

注意:

  • 函数调用时,如果为默认参数传递了值,则会使用传递进来的值;如果没有传递该参数,则直接使用默认值。
  • 默认参数必须放在没有默认值的参数列表的后面,一个函数在定义时是可以设置多个默认参数的。
python
"""
默认参数:也称为缺省参数,用于在定义函数时,为参数提供默认值,调用函数时,可以不传递有默认值的参数。
注意1:函数调用时,如果为默认参数传递了值,则会使用传递进来的值;如果没有传递该参数,则直接使用默认值。
注意2:默认参数必须放在没有默认值的参数列表的后面,一个函数在定义时是可以设置多个默认参数的。
"""
# 定义函数
def reg_stu( name, age,gender, city="武汉"):  # city="武汉"就是默认值
    print(f"注册成功,姓名:{name}, 年龄:{age}, 性别:{gender}, 城市:{city}")
    return {"name": name, "age": age, "gender": gender, "city": city}

# 调用函数
stu1 = reg_stu("张益达",22,"男")  # 没有给city赋值,就使用默认值"武汉"
print(stu1) # {'name': '张益达', 'age': 22, 'gender': '男', 'city': '武汉'}

# 注意1:函数调用时,如果为默认参数传递了值,则会使用传递进来的值;如果没有传递该参数,则直接使用默认值。
stu2 = reg_stu("张伟",22,"男","北京")
print(stu2)

# 注意2:默认参数必须放在没有默认值的参数列表的后面,一个函数在定义时是可以设置多个默认参数的。
def reg_stu2( name, age,gender="男", city="武汉"):  #允许: gender="男", city="武汉"就是默认值
    print(f"注册成功,姓名:{name}, 年龄:{age}, 性别:{gender}, 城市:{city}")
    return {"name": name, "age": age, "gender": gender, "city": city}

# def reg_stu3( name, age=0, gender, city="武汉"):  #报错:默认参数必须放在没有默认值的参数列表的后面
#     print(f"注册成功,姓名:{name}, 年龄:{age}, 性别:{gender}, 城市:{city}")
#     return {"name": name, "age": age, "gender": gender, "city": city}

不定长参数 ​

不定长参数也叫可变参数,用于定义及调用函数时参数个数不确定(0个或多个)的场景。对于不定长参数,根据传参方式的不同,又分为两类:位置参数 与 关键字参数。

不定长度位置参数 ​

不定长位置参数指的是,通过位置参数形式传递进来的,长度不确定的参数。具体参数列表的书写形式如下

python
# 不定长位置参数指的是,通过位置参数形式传递进来的,长度不确定的参数。具体参数列表的书写形式如下:
# 定义函数,返回给定的一批数据的最大值、最小值、平均值(保留2位小数)
def calc_data(*args):  # args是一个元组,python解释器会将多个位置参数封装到args元组中
    print(args,type(args))  # (388, 39, 105) <class 'tuple'>
    max_value = max(args)
    min_value = min(args)
    avg_value = round(sum(args) / len(args), 2)
    return max_value, min_value, avg_value


# 调用函数,传递的参数个数不固定
min_v, max_v, avg_v = calc_data(388, 39, 105)
# min_v, max_v, avg_v = calc_data(15, 18, 3, 27, 89, 35)
# min_v, max_v, avg_v = calc_data(100, 45, 78, 15, 183, 96, 27, 89, 35)
print("最小值:", min_v, "最大值:", max_v, "平均值:", avg_v)

不定长位置参数的写法 *args ,传递的所有匹配的位置参数都会被 args 变量收集,这些参数会合并封装为一个元组tuple中,args是元组类型(注意并不会封装关键字参数)。

注意:args 只是约定俗成的变量名,并不是关键字,这里可以使用任何合法的变量名(如 *data)。

不定长度关键字参数 ​

  • 不定长关键字参数指的是,通过关键字参数形式传递进来的,长度不确定的参数。具体参数列表的书写形式如下:
python
# 需求:定义函数,返回给定的一批数据的最大值、最小值、平均值(保留2位小数)
def calc_data(*args,**kwargs):
    """
    根据传入的这批数据, 计算这批数据的最小值, 最大值, 平均值
    :param args: 不定长位置参数, 需要计算的这批数据
    :param kwargs: 不定长关键字参数
         rd: 保留的小数位个数
         print: 是否打印输出,True表示打印,False表示不答应
    :return: 最小值, 最大值, 平均值
    """
    # 计算最大值、最小值、平均值并返回
    max_value = max(args)
    min_value = min(args)
    avg_value = sum(args) / len(args)
    # 判断是否有round选项,有就保留指定位置
    if "rd" in kwargs:
        avg_value = round(avg_value, kwargs.get("rd"))

    # 判断是否有print选项,如果有就打印结果
    if "print" in kwargs and kwargs['print']:
        print(f"计算出来的最小值: {max_value}, 最大值: {min_value}, 平均值:{avg_value}")
    return max_value, min_value, avg_value

# 调用函数
result = calc_data(1, 2, 3, 4, 5, 3, 6, 3,rd=3,print=False)
print(result)
  • 不定长关键字参数的写法是 **kwargs,传递的参数是以 "键=值" 形式传递的关键字参数,这些 "键=值" 参数都会被kwargs接收,并合并封装为一个字典类型 。
  • 注意:kwargs只是约定俗成的变量名,并不是关键字,这里可以使用任何合法的变量名(如 **options)。

不定长参数的总结 ​

  1. 什么是不定长参数?
  • 参数个数不确定,此时就可以使用不定长参数解决这类问题
  1. 不定长参数的分类?
  • *args:不定长位置参数,函数调用时,通过位置参数传递多个参数封装到一个元组(tuple)中
  • **kwargs:不定长关键字参数,函数调用时,通过关键字参数传递多个参数封装到一个字典(dict)
  1. *args 与 **kwargs 的应用场景?
  • *args 适用于处理数量不确定的数据
  • **kwargs 适用于处理数量不确定的选项

参数类型 ​

在定义函数时,关于函数的参数类型可以是普通参数,也可以是特殊参数。普通参数,比较常见的就是:float、int、bool、str、list、set、tuple、dict等。我们之前定义的函数,参数基本都是普通类型的参数,比如:

python
def circle_area(r): # 参数是 int、float 类型
    area = 3.14 * r ** 2
    return area

area = circle_area(10)
print(area)

def calc_score(score_list): # 参数 是 list 列表类型
    max_s = max(score_list)
    min_s = min(score_list)
    avg_s = round(sum(score_list) / len(score_list), 1)
    return max_s, min_s, avg_s

s_list = [589, 609, 605, 643, 677, 455, 477, 489, 503]
max_score, min_score, avg_score = calc_score(s_list)

函数本身作为参数的传递 ​

特殊参数,比如:函数。 也就是说,我们是可以把一个函数作为参数,在调用另一个函数时,将其传递进入。比如:

python
# 函数的参数类型
# 加
def add(x, y):
    return x + y
# 减
def subtract(x, y):
    return x - y
# 乘
def multiply(x, y):
    return x * y
# 除
def divide(x, y):
    return x / y
# 计算
def calc(x, y, oper):
    return oper(x, y)

print(calc(10, 20, multiply))

匿名函数 ​

匿名函数指没有名称的函数,需要通过lambda表达式来声明函数,可以简化简单函数的编写(单行表达式)。

python
# 命名函数和匿名函数的语法对比如下:
# 定义命名函数
def 函数名(参数列表):
    函数体...

# 定义匿名函数
lambda 参数列表 : 函数体

say_hello = lambda : print('我爱中国')
add = lambda x, y: x + y

say_hello()
print(add(100, 200))

**注意:**函数逻辑比较简单(单行表达式)且只在一个地方使用时,例如:作为其它函数的参数传递。

注意:匿名函数中必须是一个表达式,会自动将表达式的运行结果作为返回值。如果执行的是 print() 这类无返回值的操作,lambda 会返回 None。

python
# 需求: 完成如下列表的排序操作, 按照每一个元素的字符个数, 从小到大排序 ;
data_list = ["C++", "C", "python", "jack", "PHP", "Java", "Go", "JavaScript", "Rust"]
print(f"排序前:{data_list}")
# 默认排序:A->B->C->D->...->Z->a->b->c->d->...->z
# data_list.sort()
# print(f"排序后:{data_list}")

# 自定义排序规则:对列表中的字符串按照字符个数升序排序
# def sort_rule(s):  # 定义排序规则,根据字符个数排序就返回每个字符串的长度
#     return len(s)
# data_list.sort(key=sort_rule)

#---使用匿名函数优化---
# data_list.sort(key=lambda s: len(s))  # 按照字符个数升序
data_list.sort(key=lambda s: len(s),reverse=True)  # 按照字符个数降序
print(f"排序后:{data_list}")

案例中,调用的list列表的sort方法,是进行排序操作的,会根据key传入进来的函数进行排序,具体根据什么排序,是依据函数的返回值进行从小到大排序的。

命名函数与匿名函数怎么选择:

  1. 建议使用匿名函数的情况:函数逻辑简单,只在一个地方调用,例如:作为其它函数的参数传递
  2. 建议使用命名函数的情况:函数逻辑复杂,需要多步操作,需要多个地方重复使用或需要加文档说明的场景

函数的递归 ​

递归调用:指的是在函数中自己调用自己的情况,先层层递进, 再逐层回归 ----> 一定得有终结点(避免死循环)

python
"""
# 案例: 计算n的阶乘
# 递归调用(先层层递进, 再逐层回归): 指的是在函数中自己调用自己的情况 ----> 一定得有终结点
jc(10) = 10 * jc(9)
jc(9) = 9 * jc(8)
jc(8) = 8 * jc(7)
jc(7) = 7 * jc(6) = 7 * 720 = 5040
jc(6) = 6 * jc(5) = 6 * 120 = 720
jc(5) = 5 * jc(4) = 5 * 24 = 120
jc(4) = 4 * jc(3) = 4 * 6 = 24
jc(3) = 3 * jc(2) = 3 * 2 = 6
jc(2) = 2 * jc(1) = 2 * 1 = 2
jc(1) = 1
"""

def jc(n):
    if n == 1:
        return 1
    else:
        return n * jc(n - 1)

result = jc(10)
print(result)

函数的说明文档 ​

  • 函数的说明文档(Docstring)是写在函数开头,用三个引号包裹的字符串,用于解释函数的功能、参数、返回值等信息,方便调用者清楚函数的具体作用及细节。
python
# 函数2: 计算长方形的面积 -- 长, 宽
def rectangle_area(l,w):
    """
    根据长方形的长度和宽度, 计算长方形的面积
    :param l: 长度
    :param w: 宽度
    :return: 长方形的面积
    """
    area = l * w
    return area

print(rectangle_area(20, 10))
  • :param :描述函数的参数

  • :return:描述函数的返回值

  • **查看函数说明文档:**在pycharm中,鼠标悬浮在函数上,自动展示。

嵌套调用 ​

  • 嵌套调用指的是在一个函数中,又调用了另外一个函数。
  • 函数调用遵循栈结构,最后被调用的函数最先返回LIFO(Last In First Out,后进先出)
  • 样例:
python
# 函数的嵌套调用
def function_a():
    print("a ... before")
    function_b()
    print("a ... after")

def function_b():
    print("b ... before")
    function_c()
    print("b ... after")

def function_c():
    print("c ...")

function_a()

print("函数调用完毕 ~")
  • 执行流程:
    • 调用 function_a() 函数,输出 "a ... before"
    • 调用 function_b()函数,输出 "b ... before"
    • 调用 function_c()函数,输出 "c ..."
    • function_c()函数执行完毕后,再执行function_b()函数的后半部分,输出 "b ... after"
    • function_b()函数执行完毕后,再执行function_a()函数的后半部分,输出 "a ... after"
    • function_a()函数执行完毕

函数的案例 ​

python
# 1. 定义函数:计算传入的字符串中元音字母的个数(元音字母为 aeiouAEIOU)
# 定义函数
def count_vowels(s):  # s="s8ojwe7e3sau"
    """
    计算传入的字符串中元音字母的个数(元音字母为 aeiouAEIOU)
    :param s: 要统计的字符串
    :return: 元音字母个数
    """
    # 1 定义计数器变量count
    count = 0
    # 2 遍历字符串s得到每一个字符
    for ch in s:  # ch ="s",ch="8,...
        # 3 判断是否是元音字母
        if ch in "aeiouAEIOU":
            # 4 如果是,计数器加1
            count += 1
    # 5 返回计数器变量count的值
    return count


# 调用函数
count = count_vowels("s8ojwe7e3sau")
print(f"'s8ojwe7e3sau'中元音字母个数:{count}")

# 2. 定义函数:计算传入的班级学员高考成绩列表中成绩的最高分、最低分、平均分(保留1位小数),并返回。
# 定义函数
def calculate_scores(scores):  # scores=[...]
    """
    计算传入的班级学员高考成绩列表中成绩的最高分、最低分、平均分(保留1位小数),并返回。
    :param scores: 成绩列表
    :return: 最高分、最低分、平均分
    """
    avg = round(sum(scores) / len(scores), 1)  # round(数据值,保留的小数位数)
    return max(scores), min(scores), avg

# 调用函数
max_score, min_score, avg_score = calculate_scores([589, 609, 605, 643, 677, 455, 477, 489, 503])
print(f"最高分:{max_score}分,最低分:{min_score}分,平均分:{avg_score}分")

"""
    案例:开发一个教务管理系统,在该系统中可以维护和管理学员的成绩信息,具体需求如下:
        1. 添加学生信息:根据提示录入学生姓名、语文、数学、英语成绩,录入完成保存到系统中。
        2. 修改学生信息:要求输入要修改的学生姓名,然后再提示输入语文、数学、英语成绩,输入完成后修改学员信息。
        3. 删除学生信息:要求输入要删除的学生姓名,根据姓名删除学生信息。
        4. 查询学生信息:要求输入要查询的学生姓名,根据姓名查询学生信息并输出。
        5. 列出所有学生:遍历所有学生信息并输出。
        6. 退出系统。
"""

# 定义菜单内容
menu = """
# # # # # # # # # # # # # # # # # # # # # # 【菜单】 # # # # # # # # # # # # # # # # # # # # # # # 
#  1. 添加学生信息   2. 修改学生信息   3. 删除学生信息   4. 查询学生信息   5. 列出所有学生   6. 退出系统   #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
"""

print("欢迎使用教务管理系统 ~")

# 存储全部的学生的列表容器
student_dict = {
    '张三': {'chinese': 98.0, 'math': 92, 'english': 100},
    '李四': {'chinese': 76, 'math': 84, 'english': 71},
    '王五': {'chinese': 81, 'math': 64, 'english': 91},
}


def add_student():
    # 判断用户是否存在,如果已经存在了就提示不要重复添加
    name = input('请输入学生姓名:')
    if name in student_dict:
        print(f'【{name}】已经存在了,请勿重复添加')
        return
        # 如果学生不存在,则继续下一步的成绩录入的操作
    chinese = float(input('请输入语文成绩:'))
    math = float(input('请输入数学成绩:'))
    english = float(input('请输入英语成绩:'))
    student_dict[name] = {'chinese': chinese, 'math': math, 'english': english}
    print(f'【{name}】添加成功')
    # 打印存量数据的分割线
    print("-----------------------当前用户如下:-----------------------")
    # 调用之前的全量查看学生的方法
    list_all_students()
    print("-----------------------当前用户如下:-----------------------")


def update_student():
    # 判断用户是否存在,不存在不能修改
    name = input('请输入学生姓名:')
    if name not in student_dict:
        print(f'【{name}】用户不存在,不能进行修改操作')
        return
    # 用户存在的话,继续下一步修改的操作
    chinese = float(input('请输入语文成绩:'))
    math = float(input('请输入数学成绩:'))
    english = float(input('请输入英语成绩:'))
    student_dict[name] = {'chinese': chinese, 'math': math, 'english': english}
    print(f'【{name}】修改成功')
    # 打印存量数据的分割线
    print("-----------------------当前用户如下:-----------------------")
    # 调用之前的全量查看学生的方法
    list_all_students()
    print("-----------------------当前用户如下:-----------------------")


def delete_student():
    # 判断用户是否存在,不存在不能删除
    name = input('请输入学生姓名:')
    if name not in student_dict:
        print(f'【{name}】用户不存在,不能进行删除操作')
        return
    # 用户存在的话,继续下一步删除的操作
    # 删除的操作分为两种
    # 1.使用del
    # del student_dict[name]
    # 2.使用pop
    student_dict.pop(name)
    print(f'【{name}】删除成功')
    # 打印存量数据的分割线
    print("-----------------------当前用户如下:-----------------------")
    # 调用之前的全量查看学生的方法
    list_all_students()
    print("-----------------------当前用户如下:-----------------------")


def search_student():
    # 判断用户是否存在,不存在不能查询
    name = input('请输入学生姓名:')
    if name not in student_dict:
        print(f'【{name}】用户不存在,不能进行查询操作')
        return
    # 根据name拿到用户的score
    score = student_dict[name]
    print(f"{name}的语文成绩是:{score['chinese']},数据成绩是:{score['math']},英语成绩是:{score['english']}")


def list_all_students():
    # 如果字典为空,提示用户还没有学生
    if not student_dict:
        print('还没有一个学生,请添加了之后再过来')
        return
    for name, score in student_dict.items():
        print(f"{name}的语文成绩是:{score['chinese']},数学成绩是:{score['math']},英语成绩是:{score['english']}")


while True:
    print(menu)
    # 定义操作类型
    menu_type = input('请选择操作类型:')
    # 使用match-case进行匹配
    match menu_type:
        case '1':
            add_student()
        case '2':
            update_student()
        case '3':
            delete_student()
        case '4':
            search_student()
        case '5':
            list_all_students()
        case '6':
            break
        case _:
            print('操作有误,请检查后重试')

类型注解 ​

介绍 ​

类型注解是Python中的一种语法特性,用于明确标识变量、函数参数和返回值的数据类型,从而使代码更清晰、更安全、更易维护。

python
# 定义变量
a: int | float = 695  #a的类型为int或者float类型
# a = 5.2

score: float = 98.5
hobby: str = "Python"
flag: bool = True
pic: None = None

names: list[str] = ["A", "C", "E"]
names: list[str | int] = ["A", "C", "E", 100]

phones: set[str] = {"13309091111", "15209109121"}
options: dict[str, int] = {"count": 0, "total": 0}

goods: tuple[str,int] = ("手机", 5999)  #元组有几个类型就写几个值(有几个值就指定几个类型)
goods: tuple[str, int, int] = ("手机", 5999, 1)
# 需求:元组中有10个值,但是都是字符串或者int类型
goods: tuple[str|int,...] = ("手机", 5999, 1,"hello")

注意:Python是动态类型语言,添加的类型注解只是提示,并不是强制约束!!!

扩展:

  • goods:tuple[str,int,int] = ("手机", 5999, 1) #表示goods元组中只能存三个值,且类型是str,int,int

  • goods2:tuple[str|int,...] = ("手机", 5999, 1,100) # 表示goods元组中可以存任意多个值,可以是str或者int类型,其中...省略号表示长度不固定。

类型推断 ​

类型推断是指Python解释器自动推断出变量、表达式或函数返回值的数据类型的能力,而无需开发者显式声明。

函数的类型注解 ​

例子:

python
def calc_capital_letter(s:str)->int:
    """
    计算大写字母的个数并返回
    :param s: 字符串
    :return: 大写字母个数
    """
    count=0
    for ch in s:
        if ch.isupper(): #判断是大写字母
            count+=1
    # 2 返回结果
    return count

count = calc_capital_letter("AbcSs8H")
print(f"大写字母个数:{count}")

多个参数的类型指定方式也是一样,在每一个参数的名称后面加上 参数名: 类型

python
def calc_order_cost(*args:tuple[str,float,int], coupon:int|float=0, score:int=0, express:float=0.0)->float:
    """
    根据传入的一批商品信息(商品名、价格、数量)、优惠(优惠券、积分抵扣)、运费信息计算订单的总金额
    :param args: 商品信息(商品名、价格、数量) ----> 如: ("鼠标", 188, 2) ("键盘", 388, 1)
    :param coupon: 优惠券
    :param score: 积分(100积分抵扣1元)
    :param express: 运费
    :return: 订单的总金额
    """
    # 订单的总金额 = 商品总金额 - 优惠券 - 积分抵扣 + 运费
    # 1 计算商品总金额
    total_price = [goods[1]*goods[2] for goods in args]
    total_cost = sum(total_price)
    # 2 扣减优惠券
    total_cost-=coupon
    # 3 扣减积分抵扣
    total_cost-=score//100
    # 4 加运费
    total_cost+=express
    return total_cost


# 测试
total = calc_order_cost(("鼠标", 88.5, 2),("键盘", 388, 1),("手机", 6999, 1))
print(total)
total = calc_order_cost(("鼠标", 88.5, 2),("键盘", 388, 1),("手机", 6999, 1),coupon=100)
print(total)
total = calc_order_cost(("鼠标", 88.5, 2),("键盘", 388, 1),("手机", 6999, 1),coupon=100,score=1550)
print(total)

模块 ​

Python模块(module):一个**.py**文件就是一个模块,模块是Python程序的基本组织单位。在模块中可以定义变量、函数、类,以及可执行的代码。

那这个模块是我们自己定义的,那其实在Python中,还有很多的内置的模块,比如:数学计算模块 math、随机数模块 random、操作系统模块 os、系统参数模块 sys、日期时间模块 datetime、正则模块 re、csv文件操作模块 csv等等。

模块的导入 ​

如果我们要使用Python官方提供的,或者第三方提供的模块,要想使用,比如得先导入才可以。 导入模块的具体语法如下:

导入形式代码样例调用方式
import 模块名import random, os模块名.功能名
import 模块名 as 别名import random as rd别名.功能名
from 模块名 import 功能名from random import randint,choice功能名
from 模块名 import 功能名 as 别名from random import randint as rint别名
from 模块名 import *from random import *功能名

使用案例如下:

python
# 1. 导入模块 import 模块名,模块名, [ as ... ] ---> 调用方式: 模块名.功能名 / 别名.功能名
# import 模块名
import random
print(random.randint(1, 20))

# import 模块名 as 别名
import random as r
print(r.randint(10,20))
# print(random.randint(10,20))  # 报错,一旦取了别名,就必须使用别名

# 2. 导入模块中的功能 from 模块名 import 功能名,功能名, ----> 调用方式: 功能名 / 别名
# from 模块名 import 功能名,功能名,...
from random import randint, randrange
print(randint(10, 20))

# from 模块名 import 功能名 as 别名
from random import randint as rint, randrange as rrange
print(rint(10, 20))

# from 模块名 import *
from random import *  # *表示导入的"所有"功能
print(randint(10, 20))

自定义模块 ​

当我们在开发一些复杂的项目时,为了让项目结构更清晰,更便于项目的管理维护 以及 代码的复用,通常会将一个项目拆分为若干个模块。每个模块负责不同的职责,比如像如下这样:

  • config.py 主要定义一些项目的配置信息

  • session.py 主要定义项目会话操作

  • 在核心业务代码 ai_partner.py 中要想使用,就需要将其导入进来

  • **提示:**每一个python文件都可以作为一个模块,模块的名字就是文件的名字(建议使用python标识符定义,规范命名) 。

代码演示1:

  1. 在 my.py 文件中定义常量, 函数

    python
    # 常量(不会发生变化的数据 ; 常量的名称为全部大写)
    NAME = "我是名字"
    ADDRESS="我是地址"
    
    # 函数
    def say1():
        print("say1--->我❤️中国")
    
    def say2():
        print("say2--->我❤️湖北")
    
    def say3():
        print("say3--->我❤️武汉")
    
    def say4():
        print("say4--->我❤️黑马")
        
    # def show():
    #     print("展示一下!!!")
  2. 在 main.py 文件中引入进来测试

    python
    # 1. 导入模块 import 模块名,模块名, [ as ... ] ---> 调用方式: 模块名.功能名 / 别名.功能名
    import my
    
    my.say1()
    my.say2()
    my.say3()
    my.say4()
    
    print(my.NAME)
    print(my.ADDRESS)

代码演示2:

  1. 在 my.py 文件中定义常量, 函数

    python
    # 常量(不会发生变化的数据 ; 常量的名称为全部大写)
    NAME = "我是名字"
    ADDRESS="我是地址"
    
    # 函数
    def say1():
        print("say1--->我❤️中国")
    
    def say2():
        print("say2--->我❤️湖北")
    
    def say3():
        print("say3--->我❤️武汉")
    
    def say4():
        print("say4--->我❤️黑马")
        
    # 作用:定义其它模块使用from my import *导入哪些功能
    __all__=["NAME","ADDRESS","say1","say2"]

    __all__是一个模块级别的特殊变量,用于指定 from 模块名 import * 时会导入哪些功能(*通配了哪些功能)。

    1. 在 main.py 文件中引入进来测试
    python
    # 导入自定义模块中的功能
    from my import *
    
    # 使用模块中的功能
    say1()
    say2()
    say3() # 报错:在my .py的__all__中没有指定say3
    
    print(NAME)
    print(ADDRESS)

    代码演示3:

    1. 在 my.py 文件中定义常量, 函数 ,还定义了调用say1()和say2()等测试代码
    python
    # 常量(不会发生变化的数据 ; 常量的名称为全部大写)
    NAME = "我是名字"
    ADDRESS="我是地址"
    
    # 函数
    def say1():
        print("say1--->我❤️中国")
    
    def say2():
        print("say2--->我❤️湖北")
    
    def say3():
        print("say3--->我❤️武汉")
    
    def say4():
        print("say4--->我❤️黑马")
        
    # 作用:定义其它模块使用from my import *导入哪些功能
    __all__=["NAME","ADDRESS","say1","say2"]
    
    print("------------以下是测试当前模块中定义的方法---------------")
    say1()
    say2()
    say3()
    say4()
    1. 在 main.py 文件中引入进来测试
    python
    # 导入自定义模块中的功能
    from my_fun import *

    我们会发现,my.py中的say1()、say2()、say3()、say4()方法都执行了,因为引入my.py中的功能,会将整个文件加载一次。如果期望导入的时候不执行测试代码,直接运行改文件时执行,那么就需要使用__name__变量。

    __name__ 是Python中非常重要的内置变量,可用于区分当前模块(.py文件)是被导入执行的还是直接执行的。

    • 当模块直接运行时:__name__的值为 "main"
      • 因此,如果这段测试代码,我们只想在模型直接运行的时候执行,模块被导入时不执行,就可以将测试代码放在if代码块中。
      • if __name__ == "__main__"
    • 当模块被导入时:__name__等于模块的文件名(不含.py后缀)
    python
    # 常量(不会发生变化的数据 ; 常量的名称为全部大写)
    NAME = "我是名字"
    ADDRESS="我是地址"
    
    # 函数
    def say1():
        print("say1--->我❤️中国")
    
    def say2():
        print("say2--->我❤️湖北")
    
    def say3():
        print("say3--->我❤️武汉")
    
    def say4():
        print("say4--->我❤️黑马")
        
    # 作用:定义其它模块使用from my import *导入哪些功能
    __all__=["NAME","ADDRESS","say1","say2"]
    
    # 如果直接运行当前模块,__name__="__main__",如果当前模块被其它模块引入,__name__="模块名",也就是说在其他模块引用当前模块的时候,下面的测试代码并不会执行
    if __name__=="__main__":
        say1()
        say2()
        say3()
        say4()

包 ​

我们在实际项目开发中,开发一个项目,会涉及到大量的模块,包括数据处理的相关模块、web应用相关模块、通用工具模块等等,涉及到的python文件也会比较多。

为了便于项目的管理和维护,我们可以把这些python文件,放在不同的目录下,那这个目录我们也称为软件包 package。比如 数据处理的相关模块,放在一个目录中,web应用相关模块,放在一个目录中;通用工具模块,放在一个目录中。

  • **包:**本质就是一个文件夹,该文件夹中可以包含若干python模块(.py文件),文件夹下还包含了一个__init__.py。
  • **作用:**模块文件较多时,用来管理多个模块。(包的本质也是一个模块)

在 init.py 文件中就可以描述这个包的基本信息,包括版本、作者、import * 时导入的是哪些模块等。 定义好了包之后,在别的模块中要想使用,还是需要先将其导入进来。

  • 包的导入方式:
导入形式代码样例调用方式
import 包名.模块名import utils.my包名.模块名.功能名
from 包名 import 模块名from utils import my模块名.功能名
from 包名 import *from utils import *模块名.功能名
from 包名.模块名 import 功能名from utils.my import say1功能名
from 包名.模块名 import *from utils.my import *功能名
  • 演示代码:
  1. 定义一个目录utils,并在这个目录下面存放三个Python文件,分别命名为me.py和my.py,以及__init__.py

其中me.py文件的内容如下:

python
# 常量(不会发生变化的数据 ; 常量的名称为全部大写)
CLASSNAME = "测试"
NUMBER = 63

my.py 中定义了几个函数:

python
# 常量(不会发生变化的数据 ; 常量的名称为全部大写)
NAME = "我是名字"
ADDRESS="我是地址"

# 函数
def say1():
    print("say1--->我❤️中国")

def say2():
    print("say2--->我❤️湖北")

def say3():
    print("say3--->我❤️武汉")

def say4():
    print("say4--->我❤️黑马")
    
# 作用:定义其它模块使用from my import *导入哪些功能
__all__=["NAME","ADDRESS","say1","say2"]

print("------------以下是测试当前模块中定义的方法---------------")
say1()
say2()
say3()
say4()

然后在 init.py 中描述一下这个包的基本信息

python
# 描述包信息
__version__ = "1.0.0"
__author__ = "chengang"
# 定义当执行 `from 包名 import *` 的时候,哪些模块会被导出
__all__ = ["my", "me"]
  1. 引入包的模块进行测试
python
# import 包名·模块名        调用方法:包名·模块名·功能名
import utils.my
utils.my.my_say1()
utils.my.my_say2()
print("----------------")

# from 包名 import 模块名    调用方法:模块名·功能名
from utils import me
print(me.CLASSNAME)
print(me.NUMBER)
print("----------------")

# 这个相当于引进所有的模块,然后通过模块进行调用
# from 包名 import *        调用方法:模块名·功能名
from utils import *
print(me.CLASSNAME)
print(me.NUMBER)
print(my.NAME)
print(my.ADDRESS)
print("----------------")

# 这里相当于直接引入包中的模块指定的方法名称
# from 包名.模块名 import 功能名    调用方法:功能名
from utils.my import my_say1,my_say2
my_say1()
my_say2()
print("----------------")

# 相当于引入包中指定模块下的全部方法
# from 包名.模块名 import *        调用方法:功能名
from utils.my import *
my_say1()
my_say2()

**注意:**在通过 from 包名 import * 导入全部模块的时候,必须要在 __init__.py 文件中添加 __all__=[],控制允许导入的模块列表 。

导入包中的模块 或 导入包中模块中的功能,是比较灵活的,写法方法有很多,这里大家可以根据实际的需求来决定。