第六章 容器数据类型

1. 列表 List

1.1 列表的特点

  • 有序列表:列表中的元素保持插入顺序
  • 可变类型:创建后可以修改(增删改元素)
  • 可包含任意类型:同一个列表可以包含数字、字符串、其他列表等不同类型
  • 允许重复元素:同一个值可以出现多次

1.2 列表的创建

  • 直接创建列表
  • 通过 list() 函数创建列表

1.3 列表的基本操作

1. 访问列表
2. 列表运算
  1. 向列表中添加元素

  2. 修改列表中元素

  3. 删除列表中元素

  4. 列表相加

  5. 列表乘法

  6. 获取列表长度

  7. 列表的最大值和最小值

  8. 排序和反转

3. 遍历列表
  • 使用for循环遍历集合
  • 使用下标遍历集合
  • 使用enumerate函数遍历集合

1.4 其它函数

函数说明
list.insert(index,x)在指定位置插入x
list.append(x)在列表末尾追加x
list1.extend(list2)在列表1的末尾追加列表2的数据
del list[index]删除指定位置的数据或切片
list.remove(x)删除第一次出现的x
list.pop([index])删除指定位置的数据,默认为末尾数据
list.clear()清空列表中元素
list[index] = x修改指定位置的数据
list1[start:end] = list2修改列表切片的数据
sorted(list[,reverse=True])返回排序后的新列表,可选降序
list.sort([reverse=True])对列表就地排序,可选降序
list.reverse()反转列表中的元素
list.index(x[,start,[,end]])返回x在列表中首次出现的位置,可指定起始和结束范围
list.count(x)返回x的数量
len(list)返回列表元素个数
max(list)返回列表中最大值
min(list)返回列表中最小值
sum(list)返回列表中所有元素和
list.copy()拷贝列表
list(x)将序列转换为列表

2. 元组Tuple

2.1 元组的特点

  • 使用语法:元组使用 () 定义,元素之间使用 , 分隔,单元素元组需要逗号

    (只要元素之间有逗号,()可以省略)。

  • 有序集合:元组中的元素保存插入顺序。

  • 不可变类型:不能对元组中的不可变对象进行修改操作(增删该元素)

  • 可包含任意类型:元组中元素可以是不同的类型。

  • 允许重复元素:同一个值可以出现多次。

2.2 元组的创建

  • 直接创建元组
  • 通过 tuple() 函数创建元组

2.3 元组的基本操作

1. 访问元组
  • 索引访问
  • 切片访问
2. 元组运算
  • 元组相加
  • 元组相乘
  • 元组的成员检测
  • 元组的长度
  • 元组的最大、最小值、求和
  • 元组的比较
  • 元组的排序
3. 遍历元组
  • 直接通过遍历元组
  • 通过下标遍历元组
  • 将元组转换为列表
  • 通过enumerate()同时获取元组的下标和元素

3.集合Set

3.1 集合的特点

  • 使用语法:集合Set使用{}定义,元素之间使用,分割
  • 无序集合:集合中的元素不以任何特定顺序存储,所以集合没有索引,也不能通过索引或切片方式访问集合元素
  • 可变类型:可以对集合中的元素进行修改操作(增删该元素)
  • 可以包含任意类型:集合中元素可以是不同类型
  • 不允许重复元素:同一个值只能出现一次
  • 支持集合运算:集合可以进行数学上的集合操作,如并集、交集和差集。

3.2 集合的创建

  • 直接创建集合
  • 通过 set() 函数创建集合

3.3 集合的基本操作

1. 访问集合
  • 索引访问,没有。
  • 切片访问,没有。
2. 集合运算
  • 集合的交集

  • 集合的并集

  • 集合的差集

  • 向集合中添加元素

  • 从集合中删除元素

  • 集合成员检测

  • 获取集合长度

  • 集合中元素的最大值、最小值、求和

  • 集合的排序

  • 集合的比较(== > < <= >=)

3. 遍历集合
  • 使用for循环遍历集合
  • 使用enumerate函数遍历集合
  • 转换为列表后遍历集合

3.4 集合的常用函数

函数说明
set.add(x)添加元素
set.update(x)添加元素,x可以为列表、元组、字符串、字典等可迭代对象
set.union(x)添加元素后返回一个新的集合,x可以为列表、元组、字符串、字典等可迭代对象
set.remove(x)从集合中移除x,x不存在则报错
set.discard(x)从集合中移除x,x不存在也不报错
set.pop()随机取出集合中的一个元素,如果集合为空则报错
set.clear()清空集合
set.difference(x1,…)求set1和x1的差集,返回一个新的集合
set.difference_update(x1,…)求set1和x1的差集
set.intersection(x1,…)求set1和x1的交集,返回一个新的集合
set.intersection_update(x1,…)求set1和x1的交集
set1 & set2两集合求交集
set1 | set2两集合求并集
set1 - set2两集合求差集
set1.isdisjoint(set2)判断两集合是否没有交集
set1.issubset(set2)判断set1是否为set2的子集
set1.issuperset(set2)判断set2是否为set1的子集
set1.symmetric_difference(set2)求两集合中不重复的元素,返回一个新的集合
set1.symmetric_difference_update(set2)求两集合中不重复的元素
set.copy()拷贝集合
len(set)返回集合元素个数
max(set)求集合中元素的最大值
min(set)求集合中元素的最小值
sum(set)求集合中元素的加和

4.字典Dictionary

4.1 字典的特点

  • 使用语法:字典使用 {} 定义,键(key)和值(value)使用 : 连接,每个键值对之间使用 , 分隔。如{key1 : value1, key2 : value2}
  • 有序性:python3.7+版本,保证字典会记住键值对的插入顺序,但是字典没有索引,不能使用索引或切片访问,可以通过键访问对应的值。
  • 可变类型:可以对字典中的元素(键值对)进行修改操作(增删改元素)
  • 键值类型:值可以取任何数据类型,但键必须是不可变的,如字符串、数字、元组等。不能使用列表、集合或字典作为可变类型做为键。
  • 键值重复性:字典中的键必须是唯一的,如果重复赋值,后面的值会覆盖前面的,value可以重复。

4.2 字典的创建

  • 直接创建字典
  • 通过 dict() 函数创建字典

4.3 字典的基本操作

1. 访问字典
  • 使用key访问
  • 使用get()访问
2. 字典运算
  • 向字典中添加元素

  • 修改字典中元素

  • 删除字典中元素

  • 字典key成员检测

  • 获取字典长度

  • 字典中最大、最小值查找

  • 字典的排序

  • 字典的默认值处理

  • 字典的合并

3. 遍历字典
  • 使用for循环遍历字典(key、value、key,value)

  • 使用enumerate函数遍历字典

  • 转换为列表后遍历字典

4.4 字典的常用函数

函数说明
del dict[key]根据key删除键值对
dict.pop(key[,default])获取key所对应的value,同时删除该键值对,可设置默认值
dict.popitem()取出字典中的最后插入的键值对,字典为空则报错
dict.clear()清空字典
dict1.update(dict2)将dict2中的键值对更新到dict1中
dict.get(key[,default])获取字典中key对应value,可设置默认值
dict.setdefault(key[,default])获取字典中key对应value,可设置默认值。若key不存在于字典中,将会添加key并将value设为默认值
dict.keys()获取字典所有的key,返回一个视图对象。字典改变,视图也会跟着变化
dict.values()获取字典所有的value,返回一个视图对象
dict.items()获取字典所有的(key,value),返回一个视图对象
dict.copy()拷贝字典
dict.fromkeys(seq[,default])以序列seq中元素做字典的key创建一个新字典,可设置value的默认值

5. 四种具体容器区别

数据结构是否可变是否重复是否有序定义符号
列表(List)可变允许有序[]或list()
元组(Tuple)不可变允许有序()或tuple()
字典(Dictionary)可变键不允许,值允许键无序(Python 3.7+版本中保持插入顺序){}或dict()
集合(Set)可变不允许无序{}或set()

第七章

命名规范总结

类型命名规范示例
类名PascalCase(帕斯卡命名法)class UserService:
异常类PascalCase + “Error”(帕斯卡命名法+Error)class ValidationError:
函数/方法snake_case(蛇形命名法)def calculate_total():
变量snake_case(蛇形命名法)user_count = 10
常量UPPER_SNAKE_CASE(蛇形命名法大写)MAX_USERS = 100
模块名snake_case(蛇形命名法)database_utils.py

第八章 函数

1. 函数的概念

“函数” 是带名字的代码块,用于完成具体的任务,可重复使用。当需要在程序中多次执行同一项任务时,无须反复编写完成该任务的代码,只需要调用改名字的代码块即可,这就叫函数。

使用注意

  • Python中的函数必须先定义后使用
  • Python提供了许多内建函数,比如print()。
  • Python允许自己创建函数。

2. 函数的定义

2.1 定义函数语法

def 函数名 (参数列表) :
        函数体
        [return]

2.2 定义函数规则

  • 函数代码块以def关键词开头,后接函数标识符名称和圆括号 ()。
  • 任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
  • 函数()后面以冒号结束。
  • 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
  • 函数体开始缩进
  • return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None

2.3 函数名

  • 函数名一般是一个动词,第一个单词小写,其他每个单词的首字母大写。

2.4 参数

  • 函数在完成某个功能时,可能需要一些数据,在定义函数时指定函数参数来接收这些数据。如在屏幕上打印信息,需要把要打印的信息传递给print()函数。
  • 如果有多个参数,参数之间使用逗号分隔。函数也可以没有参数,但是小括弧不能省略。

2.5 函数体

  • 调用函数时执行的代码,也即要完成功能的逻辑,也可包含函数说明。

2.6 返回值

  • 有些函数在执行完之后,需要把执行后的某些结果,返回值给调用者

  • 通过return关键字进行返回,返回到函数调用的位置。

3.函数的入门

3.1 案例打印欢迎信息

  • 不使用函数代码。

    # 打印欢迎信息1
    print("********************************")
    print("*                              *")
    print("*     欢迎来到Python世界       *")
    print("*                              *")
    print("********************************")
    
    # 打印欢迎信息2
    print("********************************")
    print("*                              *")
    print("*     欢迎来到Python世界       *")
    print("*                              *")
    print("********************************")
    
    # 打印欢迎信息3
    print("********************************")
    print("*                              *")
    print("*     欢迎来到Python世界       *")
    print("*                              *")
    print("********************************")
    
  • 使用函数改进后的代码

    def print_welcome():
        """打印欢迎信息"""
        print("********************************")
        print("*                              *")
        print("*     欢迎来到Python世界       *")
        print("*                              *")
        print("********************************")
    
    # 多次调用函数打印欢迎信息
    print_welcome()
    print_welcome()
    print_welcome()
    

3.2 函数的好处

  1. 避免代码重复:相同的打印逻辑只需写一次
  2. 提高可读性print_welcome()比多行print更直观
  3. 便于修改:如需修改边框样式,只需改函数一处
  4. 减少错误:消除了复制粘贴可能带来的格式错误
  5. 代码组织更清晰:主程序逻辑更简洁明了

3.3 使用函数注意事项

  • 函数必须先定义再调用
  • 函数只有在被调用函数后,才会真正执行。

4. 函数的参数

需求:根据购物车的商品价格,计算购物车所有商品总价。

4.1 案例计算购物车总价

  • 函数无参数实现

    # 无参数版本 - 只能计算固定的购物车
    
    def calculate_total_no_params():
        """计算固定购物车总价"""
        prices = [100, 50, 30]  # 商品价格固定写死在函数内
        total = 0
        for price in prices:
            total += price
        return total
    
    # 只能计算一个固定的购物车
    print(f"购物车总价:{calculate_total_no_params()}")
    
  • 有参数函数的实现

    # 有参数版本 - 可以计算任意购物车
    def calculate_total(prices):
        """计算任意购物车总价"""
        total = 0
        for price in prices:
            total += price
        return total
    
    # 可以计算任意购物车
    cart1 = [100, 50, 30]
    cart2 = [200, 80, 45, 60]
    cart3 = [75, 90, 120]
    
    print(f"第一个购物车总价:{calculate_total(cart1)}:")
    print(f"第二个购物车总价:{calculate_total(cart2)}")
    print(f"第三个购物车总价:{calculate_total(cart3)}")
    

4.2 函数参数的好处

1. 代码复用:同一函数可以处理不同数据

2. 灵活性高:不修改函数就能适应新需求

3.职责清晰: 函数只负责计算,数据由外部提供

4. 代码复用:同一函数可以处理不同数据,数据与逻辑分离

4.3 形参和实参

  • 定义函数时,指定的参数称为形式参数,简称为形参
  • 调用函数时,给函数传递的参数称为实际参数,简称为实参
  • 在定义函数时,形参没有分配存储空间,也没有值,仅相当于一个占位符;
  • 在调用函数时, 在栈区中给函数分配存储空间, 然后给形参分配存储空间,接收调用函数式传入的实际数据。
  • 函数执行结束,函数所占的栈空间会被释放,函数的形参也最终被释放。

4.4 参数的传递

1. 参数类型

在Python函数的参数传递,主要分参数类型:

  • 不可变类型
  • 可变类型

不可变类型

也即值传递,如整数、浮点数、字符串、元组等。比如如def fun(a):,传递的只是a的值,没有传递a对象的地址。比如在fun(a)内部修改a的值,只是修改另一个复制的对象的值,不会影响 a 本身对象的值。

可变类型

也即引用(地址)传递,如列表,集合、字典等。如def fun(la):,则是将 la对象地址传过去,修改后fun外部的la也会受影响。

其实Python 中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象。

2. 传入不可变对象类型参数案例
'''
    该案例演示了Python函数传递不可变对象
'''
def change_int(a) :
    print("函数体中未改变前a的内存地址",id(a))
    a = 10
    print("函数体中改变后a的内存地址",id(a))

a = 2
changeInt(a)
print(a)
print("函数外a的内存地址",id(a))


# 输出结果
# 函数体中未改变前a的内存地址 140729722661336
# 函数体中改变后a的内存地址 140729722661592
# 2
# 函数外a的内存地址 140729722661336

说明:实例中有 int 对象 2,指向它的变量是a,在传递给 change_int 函数时,按传值的方式复制了变量 a,a 和 a 都指向了同一个 int 对象,函数外a的内存地址和未改变前a的地址是相同的。在内部执行 a=10 时,新生成一个 int 值对象 10,并让 a 指向它。这个时候内部a的内存地址也即发生了改变。

3. 传入可变对象类型参数案例
'''
    该案例演示了Python函数传递可变对象
'''
def change_list(myList) :
    myList[1] = 50
    print("函数内的值",myList)
    print("函数内列表的内存",id(myList))

mlist = [1,2,3]
change_list(mlist)
print("函数外的值",mlist)
print("函数外列表的内存",id(mlist))

# 输出结果
# 函数内的值 [1, 50, 3]
# 函数内列表的内存 1380193079680
# 函数外的值 [1, 50, 3]
# 函数外列表的内存 1380193079680

说明:可变对象在函数里修改了参数,那么在函数外面,这个原始的参数也被改变了。

通过内存地址的输出,我们可以看出来,是在原有的列表对象上进行的修改。

4.5 函数可使用的参数形式

1. 必须参数

调用函数时传入参数的的数量必须和定义函数时参数一致。

'''
    该案例演示了函数位置实参
'''
def func(a, b, c):
    print(a, b, c)
func(1, 2, 3)  # 1 2 3
2. 关键字参数

函数调用使用关键字参数和定义方法形参名进行匹配,并且不要求函数调用时关键字参数的顺序与定义函数的参数名一致。

'''
    该案例演示了函数调用时的关键字参数
'''
def print_info(name,age) :
    print("姓名:",name)
    print("年龄:",age)

# Python解释器可以通过age和name这样的关键字去和形参进行匹配
print_info(name = "zhangsan",age = 18)
print_info(age = 18,name = "zhangsan")
3. 默认值参数

定义函数时,可以给每个形参指定默认值,接着在调用函数时,可以选择是否给提供了实参值,如果提供了,则使用提供的实参值,否则使用形参的默认值,给形参指定默认值后,可在函数调用中省略相应的实参。使用默认值可简化函数调用。

'''
    该案例演示了函数调用时的默认参数
'''

def print_info(name,age = 20) :
    print("姓名:",name)
    print("年龄:",age)

print_info("zhangsan")

print_info("lisi",30)
4. 不定长参数

函数定义的时候,参数的个数是不确定的。

一种是参数带一个星号*的可变长参数

语法:

def 函数名([普通参数,] *var_args_tuple ):

  函数体

说明:

  • 加了星号 * 的参数会以元组(tuple)的形式导入,存放所有未命名的变量参数。
  • 如果形参中出现了不定长参数,那么在调用函数的时候,先通过位置进行必须参数的匹配,然后匹配不定长参数。
  • 如果不定长的参数后面还有参数,必须通过关键字参数传参。
'''
   该案例演示了函数调用时的不定长参数
'''

def print_info(num,*vartuple):
    print(num)
    print(vartuple)

printInfo(70,60,50)

print("-" * 20)
# 如果不定长的参数后面还有参数,必须通过关键字参数传参
def print_info(num1,*vartuple,num) :
    print(num)
    print(num1)
    print(vartuple)

print_info(10,20,num = 40)

print("-" * 20)
# 如果没有给不定长的参数传参,那么得到的是空元组
print_info(70,num = 60)

还有一种就是参数带两个星号的可变长参数,基本语法如下:

语法:

def 函数名([普通参数,] **var_args_dict ):
   函数体

说明:

  • 加了两个星号 ** 的参数会以字典的形式导入
  • 后面就不能再有其他参数了
'''
    该案例演示了函数调用时的不定长参数
'''
def print_info(num,**vardict):
    print(num)
    print(vardict)
    # return

print_info(10,key1 = 20,key2 = 30)

在程序开发中,有时候希望一个函数执行结束后,告诉调用者一个结果,以便调用者针对具体的结果做后续的处理。返回值就是函数完成工作后,给调用者的一个结果。

说明

  • 在函数中使用 return 关键字可以返回结果 ,并结束正在执行的函数
  • 如果return后面跟[表达式],在结束函数的时,返回计算完表达式结果。
  • 如果仅仅是return关键字,后面没有加内容,函数执行返回调用方None。
  • 调用函数一方,可以使用变量来接收函数的返回结果
  • return 语句可以返回多个值,多个值会放在一个元组中。

案例

# 1.返回布尔表达式
def is_even(num):
    """检查数字是否为偶数"""
    return num % 2 == 0  # 返回布尔表达式的结果

print(is_even(4))  # 输出: True
print(is_even(5))  # 输出: False

# 2.不带表达式的 return 语句,返回 None。
def f(a, b, c):
    pass
    return 

print(f(1, 2, 3))  # None

# 3.函数中如果没有 return 语句,在函数运行结束后也会返回 None。
def f(a, b, c):
    pass


print(f(1, 2, 3))  # None

# 4.用变量接收返回结果
def add(num1,num2) :
    '''求两个数的和'''
    sum1 = num1 + num2
    return sum1

res = add(10,20)
print("两个数的和为:" ,res)

# 5.return 语句可以返回多个值,多个值会放在一个元组中。
def f(a, b, c):
    return a, b, c, [a, b, c]
print(f(1, 2, 3))  # (1, 2, 3, [1, 2, 3])

6. 函数嵌套调用

在一个函数中调用另一个函数,当内层函数执行完之后才会继续执行外层函数。

def function_A():
    print("\t函数 A 开始执行")
    print("\t函数 A 执行中...")
    print("\t函数 A 结束执行")

def function_B():
    print("函数 B 开始执行")
    print("函数 B 执行中...")
    function_A()
    print("函数 B 执行中...")
    print("函数 B 结束执行")

function_B()


#输出结果:
#函数 B 开始执行
#函数 B 执行中...
#函数 A 开始执行
#函数 A 执行中...
#函数 A 结束执行
#函数 B 执行中...
#函数 B 结束执行

7. 变量的作用域

Python中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。变量的作用域决定了哪一部分程序可以访问哪个变量,Python的作用域一共有4种,分别是:

L (Local) 局部作用域

E (Enclosing)嵌套作用域

G (Global) 全局作用域

B (Built-in) 内建作用域

以 L –> E –> G –>B 的规则查找,即:在局部找不到,便会去局部外的局部,再找不到就会去全局找,再者去内建中找。以下案例演示各种作用域类型。

7.1 局部作用域

def local_scope():
    x = 10  # 局部变量
    print("局部作用域内:", x)

local_scope()
print(x)  # 报错 NameError: name 'x' is not defined

说明:

  • x只能在local_scope()函数内部访问
  • 函数执行完毕后局部变量会被销毁

7.2 嵌套作用域(Enclosing)

def outer():
    y = 20  # 嵌套作用域变量
    
    def inner():
        print("内部函数访问嵌套作用域变量:", y)  # 可以访问外部函数的变量
    
    inner()
    print("外部函数访问自己的变量:", y)

outer()
# print(y)  # 报错 NameError

说明:

  • inner()可以访问outer()的变量y
  • 外部无法直接访问嵌套作用域的变量

7.3 全局作用域(Global)

z = 30  # 全局变量

def access_global():
    print("函数内访问全局变量:", z)

access_global()
print("直接访问全局变量:", z)

8. 全局变量和局部变量

定义在函数内部的变量拥有一个局部作用域,定义在函数外的变量拥有全局作用域。局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。

'''
    该案例演示了全局变量和局部变量
'''
number_sum = 0  # 这是一个全局变量

def add(num1,num2) :
    sum = num1 + num2  # 这是一个局部变量
    print("函数内局部变量的值:",sum)
    return sum

add(10,20)
print(num1) # num1访问不到
print("函数外全局变量:",number_sum)

8.1 global关键字

问题:定义了一个全局变量,如何在函数内对其进行修改?

# 1.全局变量是不可变类型
var1 = 100
def immutable_a():
    var1 = 200
    print("var1:", var1)
print(var1)  # 100
immutable_a()  # var1: 200
print(var1)  # 100

# 2.全局变量是不可变类型
list1 = [1, 2, 3]
def mutable_b():
    list1[0] = -1000
    print("list1:", list1)

print(list1)  # [1, 2, 3]
mutable_b()  # list1: [-1000, 2, 3]
print(list1)  # [-1000, 2, 3]

通过上述,你发现对于全局变量是不可变类型,似乎在函数内无法修改,而全局变量是可变类型,则可以直接修改。

这是因为当你尝试修改不可变对象时,Python 实际上会创建一个新对象并重新赋值。而针对去修改可变对象时,Python会选择性的创建新对象,也就是说可能会创建,也可以不创建,直接修改原来对象。

针对immutable_a()函数的 var1=200,只是修改新对象的值。并没有修改原来对象的值。所以immutable_a()函数调用完,输出的var1仍然是原来的100,不是修改后的200.

针对mutable_b()中通过 list1[0] 这种方式修改时,Python 会先查找 list1 这个变量名。按照 LEGB 规则,它会找到全局的 list1,然后直接操作该对象的内容。所以函数调用完,在外部输出的list1,就是函数内部修改后的最新值。

那么在回到不可变类型上,如果现在就想在方法内部修改不可变类型,该怎么办呢。

global是Python中用于声明全局变量的关键字,它允许在函数内部修改和访问在全局作用域(模块级别)定义的变量。

我们来改进上述代码片段的immutable_a()方法

# 1.全局变量是不可变类型
var1 = 100
def immutable_a():
    global var1
    var1 = 200
    print("var1:", var1)
print(var1)  # 100
immutable_a()  # var1: 200
print(var1)  # 200

当我们在immutable_a()方法中对var1变量添加了global关键字之后,发现immutable_a()调用完,全局变量的var1值也变为修改后的200,这就是global的作用。

那么在 immutable_a()中对var1添加了global关键字之后,发生了什么呢,其实底层就是明确告诉 Python 要修改的是全局变量(修改原来对象),而不是创建局部变量(创建新对象)。

在回到尝试修改不可变对象,我们刚刚说当修改不可变对象时Python会试选择性的创建新对象。为什么说可能,因为如果我们只是修改可变类型的部分内容, Python 不会将其视为局部变量,也即修改的还是全局变量。但是如果修改可变类型的全部内容,比如重新赋值,那么Python依然会创建一个新的局部变量。比如针对mutable_b()函数做一次修改。

改进上述代码片段的mutable_b()函数

list1 = [1, 2, 3]
def mutable_b():
    list1 = [-1000, 2, 3]
    print("list1:", list1)

print(list1)  # [1, 2, 3]
mutable_b()  # list1: [-1000, 2, 3]
print(list1)  # [1, 2, 3]

输入结果和修改不可变类型一样,依然没有修改全局变量,局么我们对局部变量list1添加global,在看结果。

list1 = [1, 2, 3]
def mutable_b():
    global list1
    list1 = [-1000, 2, 3]
    print("list1:", list1) 

print(list1) # [1, 2, 3]
mutable_b() # list1: [-1000, 2, 3]
print(list1) # [-1000, 2, 3]

成功修改了全局变量。

Global小总结:

行为不可变类型 (int, str, tuple 等)可变类型 (list, dict, set 等)
修改变量值必须用 global(因为创建新对象)不需要 global(直接修改内容)
重新赋值var1 = 200(需要 globallist1 = [-1000,2,3](需要 global
修改内部内容不适用(不可变对象无法修改内容)list1[0] = -1000(不需要 global

8.2 nonlocal关键字

nonlocal 也用作内部作用域修改外部作用域的变量的场景,不过此时外部作用域不是全局作用域而是嵌套作用域。

def function_outer():
    var1 = 1
    print(var1)
    def function_inner():
        nonlocal var1
        var1 = 200
    function_inner()
    print(var1)
function_outer()  # var1: 1 -> 200

Global和nonlocal的区别

  • global:用于访问和修改全局(模块级)变量
  • nonlocal:用于访问和修改嵌套作用域中的变量

9. 匿名函数

Python使用 lambda 来定义匿名函数,所谓匿名,指其不用 def 的标准形式定义函数。

语法:

lambda 参数列表: 表达式

说明

  • lambda 只是一个表达式,函数体比def简单很多。
  • lambda的主体是一个表达式,而不是一个代码块,所以仅仅能在lambda表达式中封装有限的逻辑进去。
  • lambda有自己的参数列表作为局部变量
  • 可以访问全局变量
  • 访问外围函数的局部变量
  • 不能访问未定义的变量

案例:

9.1 使用普通函数传参

def operator(a, b):
    return a + b
def function(a, b, abc):
    return abc(a, b)
print(function(1, 2, operator))

9.2 使用匿名函数传参

def function(a, b, operator):
    return operator(a, b)
print(function(1, 2, lambda x, y: x + y))

9.3 匿名函数作为内置函数的参数

可以将匿名函数与常用的内置参数搭配使用。

1)sorted()

有三名学生的姓名和年龄,按年龄排序。

student_list = [{"name": "zhang3", "age": 36}, {"name": "li4", "age": 14}, {"name": "wang5", "age": 27}]
print(sorted(student_list, key=lambda x: x["age"]))

2) map()

map() 的主要作用是将给定的函数批量应用到可迭代对象的每个元素上,实现数据转换

map_result = map(lambda x: x * x, [0, 1, 3, 7, 9])
print(list(map_result))  # [0, 1, 9, 49, 81]

3)filter()

filter 的主要作用是将给定的函数批量应用到可迭代对象的每个元素上,实现数据过滤

filter_result = filter(lambda x: x >= 0, [0, -1, -3, 7, 9])
print(list(filter_result))  # [0, 7, 9]

第九章 文件操作

1、文件的基本概念

在计算机中,文件是存储在磁盘上的数据集合。文件可以包含各种类型的数据,如文本、图像、音频、视频或程序代码。

文件系统通过文件名和文件路径来定位和管理文件。文件名通常包含文件的名称和扩展名,扩展名用于表示文件的类型(例如 .txt 表示文本文件,.jpg 表示图像文件)。文件路径可以是绝对路径(从文件系统的根目录开始)或相对路径(相对于当前工作目录)。

在编写程序的时候,数据是以二进制的形式存储在内存的,将数据写到磁盘文件的过程称之为持久化。

2、文件的分类

1)纯文本文件

有统一的编码,可以被看做存储在磁盘上的长字符串。

纯文本文件编码格式常见的有ASCII、ISO-8859-1、GB2312、GBK、UTF-8、UTF-16等。

2)二进制文件

没有统一的字符编码,直接由0与1组成。

如图片文件(jpg、png),视频文件(avi)等。

3、文件的路径

1)相对路径

从当前位置到指定位置的路径。

如:./hello_world.py

./代表当前路径。…/代表上一级路径。

2)绝对路径

从根目录到指定位置的路径。

如:E:/Hello/hello.py

4 文件的打开与关闭

1)打开文件

使用 open() 打开或创建文件,该方法执行完毕之后返回的是一个file对象。

1.1)常用形式

open(文件名, 模式)
f = open("test.txt", "w")

1.2 常用文件操作模式

模式说明
r读写方式:只读,文件若不存在会报错。默认此模式
w读写方式:写入,写入前清空原有数据。文件不存在会创建文件
a读写方式:追加写入,在原有数据后追加,文件不存在会创建文件
x读写方式:创建新文件并写入,文件若已存在会报错
b编码方式:以二进制打开。一般用于非文本文件如图片等
t编码方式:以文本模式打开,默认此模式

2)关闭文件

f.close()

5. 文件读写

5.1 写数据

# 打开文件
f = open("test.txt", "w")
# 写入数据
f.write("hello world\n")
f.write("nihao python\n")
# 关闭文件
f.close()

5.2 读数据

1)read

read([size]) 可以从文件中读取数据,size 表示要从文件中读取的数据的长度(单位是字节),如果没有传入 size 则读取文件中所有的数据。

# 打开文件
f = open("test.txt", "rt")
# 读取文件所有数据 
print(f.read())
# 关闭文件
f.close()
print("-"*20)
f = open("test.txt", "rt")
# 读取文件5个字节数据
print(f.read(5))
print(f.read(8))
f.close()
  1. readline

readline([size]) 可以从文件中读取整行数据,也可以通过 size 设置读取数据的长度。

# 打开文件
f = open("test.txt", "rt")
print(f.readline()) # 读取一行(直到遇到换行符\n或文件结尾)
print(f.readline(1)) # 读取最多1个字节/字符(不是一行)
print(f.readline(1))   # 继续读取下一个字符
# 关闭文件
f.close() 
  1. readlines

readlines([size]) 读取所有行并返回列表,若给定 size>0,它会尝试读取大约 size 字节的行(可能比 size 大,因为必须读取完整的行)。

假设 english.txt 内容:

Line 1
Second line is longer
Third lie
Last line

代码

f = open('english.txt', 'r') 
try:
     # 尝试读取约10字节
    chunk = f.readlines(10)
finally:
    f.close()  # 确保文件一定会被关闭

输出结果:(尽管超过了10字节,但必须读取完整行)

读取约10字节,实际读取: ['Line 1\n', 'Second line is longer\n'] 

6. 文件的常用函数

函数说明
file.seek(offset[,from])移动偏移量并返回新的绝对位置。 offset:移动的字节数,如果是负数表示从倒数第几位开始。 from:从哪个位置开始移动;0为开头,1为当前位置,2为末尾。from不为0时需使用’b’二进制模式打开文件。
file.tell()返回当前偏移量
file.truncate([size])从开头开始截断文件为size个字符,无size表示从当前位置截断。windows系统下的换行大小2个字符
file.writelines(seq)将序列中字符串写入文件,需要自己加入换行符
file.readable()如果可以读取文件则返回True
file.writeable()如果可以写入文件则返回True
file.seekable()如果文件支持随机访问则返回True
os.rename(old,new)重命名文件
os.remove(file)删除文件
os.mkdir(dir)创建目录,不支持递归创建
os.makedirs(dir)递归创建目录
os.getcwd()获取当前路径
os.chdir(dir)进入指定目录
os.listdir(dir)获取目录下文件和目录列表
os.rmdir(dir)删除空目录
os.removedirs(dir)递归删除空目录
os.path.abspath(path)将相对路径转换为绝对路径
os.path.basename(path)获取路径中的文件名部分
os.path.dirname(path)获取路径中的目录部分
os.path.join(*paths)拼接多个路径,自动处理路径分隔符
os.path.split(path)将路径分割为目录和文件名的元组
os.path.splitext(path)将路径分割为文件名和扩展名的元组
os.path.exists(path)判断路径是否存在
os.path.isfile(path)判断路径是否为文件
os.path.isdir(path)判断路径是否为目
os.path.getsize(path)获取文件的大小,以字节为单位
os.path.getatime(path)获取文件的最后访问时间
os.path.getmtime(path)获取文件的最后修改时间
os.walk()递归遍历目录递归遍历目录

(1)案例:递归遍历当前路径下所有目录和文件

使用os.walk()递归遍历目录树

import os
for root, dirs, files in os.walk(os.getcwd()):
    print("当前工作目录的路径:", root)
    print("当前路径下的子目录:", dirs)
    print("当前路径下的所有文件:", files)
    print()

(2)案例:文件拷贝

def copy_file(source_file_path,dest_file__path):
    # 打开源文件
    source_file = open(source_file_path, 'rb')
    # 读取源文件中的内容
    content = source_file.read()

    # 打开目的地文件
    dest_file = open(dest_file__path, 'wb')
    # 将读取到的数据写入到目的地
    dest_file.write(content)

    # 关闭源文件
    source_file.close()
    # 关闭目的地文件
    dest_file.close()

copy_file("D:/tmp/1.jpg","D:/1.jpg")

优化:

def copy_file(source_file, dest_file):
    #     打开文件
    source = open(source_file, "rb")
    context = source.read(1024)
    target = open(dest_file, "wb")
    while context:
        target.write(context)
        context = source.read(1024)
    else:
        print("复制完成")
        source.close()
    #     关闭资源
    # source.close()
    target.close()

copy_file("D:/tmp/1.jpg", "D:/1.jpg")

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐