40. 时间模块
40.1 time
导入模块 import time #1. 获取当前时间 c_time = time.localtime() print(c_time) #2.获得指定字端的时间的方式 #2.1根据索引来获取 不太实用 year = c_time[0] print(year) #2.2 根据时间的字段获取对应的数据 year = c_time.tm_year #这个方式是只读的 print(year) #3. 获取当前时间对应的时间戳: 从指定时间到1970年1月1日凌晨 + 时间差经历的描述 seconds = time.time() print(seconds) #4.格式化时间 ===> 字符串类型的数据 ''' 格式化时间的站位符: %Y --- 年 %m --- 月 %d --- 日 %H --- 24小时制 %I --- 12小时制 %p --- 标记上下午 %M ---- 分 %S ---- 秒 %w --- 星期 %j --- 一年中的第几天 %z --- 时区 ''' time_str = time.strftime("%Y/%m/%d %H:%M:%S zone:%z week:%w %j", c_time) print(time_str) #5. 将格式化时间转化为真实的时间元组 ''' 1. 格式化之后的字符串时间 2. 转化这个时间的时候对应的时间格式 ''' time1 = time.strptime(time_str, "%Y/%m/%d %H:%M:%S zone:%z week:%w %j") print(time1) #6.将自定义的字符串时间转化为真实时间 time1 = time.strptime("2019-01-20 12:30:24", "%Y-%m-%d %H:%M:%S") print(time1) #7.获得自定义时间对应的时间戳 传入的是自定义字符串时间对应的真实时间 seconds = time.mktime(time1) print(seconds) #8.将时间戳转化为时间元组 time2 = time.localtime(1) print(time2) print("开始睡") #9.时间休眠 设置的休眠几秒 time.sleep(2) print("睡醒了") ''' 已知 一个时间字符串: 2019-12-25 00:30:05 求三天后的时间 并且使用字符串格式展示出来 2019-12-28 00:30:05 ''' 思路: 1.已知的字符串时间 --- 真实的时间 -- 时间元组 2.获得该时间对应的时间戳 + 3天的秒数 3.将时间戳 转化为时间元组 -- 格式化成对应的字符串时间 ''' true_time = time.strptime("2019-12-25 00:30:05", "%Y-%m-%d %H:%M:%S") seconds = time.mktime(true_time) delay_seconds = seconds + 3 * 24 * 60 * 60 delay_time = time.localtime(delay_seconds) time_str = time.strftime("%Y-%m-%d %H:%M:%S", delay_time) print(time_str)
40.2 datetime
import datetime #1. 获取当前时间 -- datetime类型 #第一个datetime-- 模块名 第二个datetime是模块下的数据类型 now -- datetime类型下的方法 c_time = datetime.datetime.now() print(c_time) print(type(c_time)) #2. 获得对应字段的数据 #年的 year = c_time.year month = c_time.month day = c_time.day hour = c_time.hour minute = c_time.minute second = c_time.second print("年{}月{}日{}时{}分{}秒{}".format(year,month,day,hour,minute,second)) #3.获得今天的日期 today_day = datetime.date.today() print(today_day) #4.自定义时间 ''' 年月日必须给值 其他的默认是0 ''' cus_time = datetime.datetime(2019,1, 19) print(cus_time) cus_time = datetime.datetime(2019,1, 19, 10, 28, 35) print(cus_time) #5.获得指定时间 n天前/后的时间 #根据给days赋予正负 控制时间的前后 #days赋予的正数 根据运算符 + - 控制时间前后 delay_time = cus_time + datetime.timedelta(days=3) print(delay_time) #6.格式化时间 --- 字符串类型时间 time_str = delay_time.strftime("%Y/%m/%d") print(time_str) #7.反格式化 time1 = datetime.datetime.strptime(time_str, "%Y/%m/%d") print(time1, type(time1)) #8.求两个时间的时间差 first_time = datetime.datetime(2019,1,17,10,30,00) second_time = datetime.datetime(2019,2, 9, 10, 28, 00) diff = second_time - first_time print(diff) #获取差的天数 days = diff.days print(days) #差异的秒数 除去天数之后 剩余时间转化为秒数 seconds = diff.seconds print((seconds - 23*3600)//60) #获取的是整体差异的总秒数 把天数也包含在内了 常用的 total_seconds = diff.total_seconds() print(total_seconds) ''' 活动上的倒计时: 由秒来控制倒计时 对总秒数 减 1 将秒数转化为天 时 分 秒 '''
datetime ----> 计算时间差 time --- 时间戳 -- 时间 --- 时间格式化
40.3 calendar
import calendar #1. h获取指定年对应的日历 year_calendar = calendar.calendar(2019) print(year_calendar) #2. 获得指定年 指定月的日历 month_calendar = calendar.month(2019, 1) print(month_calendar) #3. 判断指定年是否是闰年 res = calendar.isleap(2019) print(res) #4. 指定年月日对应的星期 week = calendar.weekday(2019, 1, 18) print(week) #0-6 星期一为开始0 #5.获得指定月日历中每个星期对应的日期 #二维列表: 内层列表是一个一个的小列表 小列表中的元素 每个星期对应的日期数 没有日期的位置使用0填补 month_list = calendar.monthcalendar(2019, 1) print(month_list) #以星期一位开始的日历展示 #以星期日为开始进行日历展示 ''' 把整个列表中的数据向后移一位 将最后一位 放到开始的位置 移除原本的最后一位 其他的整体向后移 ''' total_list = [] for week_list in month_list: for d in week_list: total_list.append(d) print(total_list) last_value = total_list[len(total_list)-1] #把这一位的数据放入到索引为0的位置 total_list.insert(0, last_value) #原本的最后一位删除 total_list.pop() print(total_list) two_list = [] inner_list = [] for i in range(len(total_list)): inner_list.append(total_list[i]) if len(inner_list) == 7: two_list.append(inner_list) #清除inner_list inner_list = [] print(two_list) print("SU Mo Tu We Th Fr Sa") for week_list in two_list: for d in week_list: if d == 0: print(" ", end="") else: print(d, end=" ") print()
听写
1.统计字符串中每个字符出现的次数 s = "hellonicegoodhighlikebook" 获取出现次数最多的字符 - 以键值对形式展示 dict0 = {} #遍历字符串 for ch in s: if ch not in dict0: dict0[ch] = 1 else: dict0[ch] += 1 #数据存放在字典中 max_count = max(dict0.values()) #筛选 #普通方法: max_dict = {} #遍历字典 for k, v in dict0.items(): if v == max_count: max_dict[k] = v print(max_dict) #字典生成式 new_dict = {k:v for k, v in dict0.items() if v == max_count} #三种filter 需要接受一个函数 函数有一个参数 --- 用于接受的数据默认是序列中的每一个元素 函数需要有一个返回结果 bool True 表示保留 new_dict = dict(filter(lamnda item: item[1] == max_count ,dict0.items())) 2.在英文字母(大小写都有)和数字组合的序列中随机取出可重复的8个字符, 再为最后的字符串排重[每个字符只能存在1个] a. 允许排重之后是无序的 b. 排重之后字符的顺序不允许发生变化 import string import random chs = string.ascii_letters + string.digits new_str = "" for _ in range(8): new_str += random.choice(chs) #new_str += "".join(random.sample(chs,1)) print(new_str) a. res = "".join(set(new_str)) b. str0 = "" for ch in new_str: if ch not in str0: str0 += ch print(str0) 3.利用高阶函数 将字典中的键值颠倒 dict0 = {"a":97, "A":65, "B":66, "c":99} map 需要接受一个函数 map的第二种方式 可以接受多个序列 按照指定格式对序列中的元素进行重组 函数中的形参格式 --- 有几个序列就有几个形参 形参的接受元素的顺序 与 序列顺序是一致的 new_dict = dict(map(lambda a, b: (b, a), dict0.keys(), dict0.values())) zip 不需要接受函数 接受多个序列 将序列中每个对应位置的元素取出 重组成序列 [以长度小的序列为标准] new_dict = dict(zip(dict0.values(), dict0.keys())) 4.利用高阶函数 保留列表中的字符串数据 list0 = [99, 28, "like", "zip", "bye", "family", "True", False] 并对最后的字符串按照小写形式进行降序排序 new_list = list(filter(lambda ele : isinstance(ele, str), list0)) #new_list.sort(key = lambda ele : ele.lower(), reverse = True) list1 = sorted(new_list, key = lambda ele : ele.lower(), reverse=True) print(list1)
41. os模块
常用方法: import os ''' cd.. --- 返回上一级 .. 表示的是相对路径中的上一级目录 . - 当前目录 ''' #1.获取当前文件所在的相对目录 dir == directory 文件夹 目录的意思 c_path = os.curdir print(c_path) #. #2. 获得当前文件所在绝对路径 a_path = os.getcwd() print(a_path) #C:\Users\11143\Documents\讲课内容\BJPython1901\day11\code\Day11\os_pack r''' 相对路径 参照物的 参照物 --- 当前文件 如果指定文件的路径与 参照物在同一个目录下 获取其相对路径时 直接写文件名即可 如果指定文件路径 与参照物 不在同一个目录下 起始查找位置 是 当前文件 --- 级别情况 进行路径的设置 .. --- 表示的是上一级目录 ..\..\..\ 想在当前文件中获取 other.py的文件路径: ..\other.py 想在当前文件中获取 test.py的文件路径: ..\other_pack\test.py 绝对路径 从具体的盘符开始 到 指定的文件的路径 C:\Users\11143\Documents\讲课内容\BJPython1901\day11\code\Day11\os_pack ''' #3.获得环境变量 environment = os.environ print(environment) #获取指定环境变量 下某个变量的值 path_value = os.environ.get("path") print(path_value) #4. 与目录的操作 #4.1 创建目录 #只有一个名字 表示这个目录与当前文件平级 只能创建最后一级目录 如果路径前面的目录不存在 会报错 #os.mkdir("new_dir") #如果目录存在 创建的话会报错 #与 os_pack平级 #os.mkdir(r"..\test") #使用绝对路径创建 #os.mkdir(r"C:\Users\11143\Documents\讲课内容\BJPython1901\day11\code\test") #4.2 创建多级 #os.makedirs(r"C:\Users\11143\Documents\讲课内容\BJPython1901\day11\codes\test") #4.3 删除目录 -- 只能删除空目录 #os.rmdir(r"C:\Users\11143\Documents\讲课内容\BJPython1901\day11\codes") #4.4 获得目录下的直接子文件(包含子目录) 结果是列表类型的 列表中存放的是子文件的名字 sub_list = os.listdir(r"..") print(sub_list) #['.idea', 'os_pack', 'other.py', 'other_pack', 'test'] #5.关于文件的操作 #5.1 打开或者创建文件 ''' 第一个参数是: 要进行操作的文件的路径 文件在程序中 最好使用相对路径 第二个参数是: 操作文件的模式 r ---- 表示的是只读 如果文件路径不存在 会报错 w ---- 表示的是只写 如果文件不存在 会创建文件 文件存在 会将文件中的内容清空 > a ----- 表示只写 >> 如果文件不存在 会创建文件 文件存在 将新写入的内容拼接在原有内容的后面 r w a这几种模式表示的操作的是字符数据 r+/w+/a+ ----> 可读可写 以字符格式对文件进行可读可写 rb wb ab--- b表示的是二进制 加b的情况下 表示操作的是字节数据 rb+ wb+ ab+ 以二进制形式数据对文件进行可读可写 第三个参数 encoding 设置的是打开文件的编码方式 open() 会返回一个操作文件的手柄 通过手柄操作文件指针 进行文件读写 ''' # handle = open(r"..\otherss.py", "a") #5.2 删除文件 # os.remove(r"..\otherss.py") #5.3 获得文件信息的描述 info = os.stat(r"..\other.py") print(info) #6.重命名 # os.rename(r"..\test", r"..\tests") #7. os 模块下 有一个path 其下还有一些操作文件的内容 #7.1 获得指定文件的绝对路径 absolute abs_path = os.path.abspath("..") print(abs_path) #7.2 对路径进行拼接 path = r".." sub_list = os.listdir(path) print(sub_list) #想获得other_pack这个路径 第一个参数是父级目录 第二个参数是要拼接的子目录 res_path = os.path.join(path, sub_list[3]) print(res_path) #..\other_pack print(os.path.abspath(res_path)) #7.3 获得指定路径的扩展名 -- 结果是一个元组 ''' 如果路径对应的是一个文件 第一部分是文件的路径 第二部分就是文件的扩展名 如果路径对应的是一个文件夹 第一部分是文件夹的路径 第二部分就是空字符串 ''' new_path = os.path.join(path, sub_list[2]) print(new_path) extend_names = os.path.splitext(new_path) print(extend_names) #7.4 获得最后一级的目录的名字 base_name = os.path.basename(res_path) print(base_name) #7.5 获取除了最后一级的路径 dir_name = os.path.dirname(new_path) print(dir_name) #7.6 获取文件的大小 单位是字节 length= os.path.getsize(new_path) print(length) #7.7 判断路径指定的文件是否存在 res = os.path.exists("a.txt") print(res) #7.8 判断路径指向的是否是一个文件 res = os.path.isfile(res_path) print(res) #7.9 判断路径指向的是否是一个文件夹 res = os.path.isdir(new_path) print(res) #.7.10 判断路径是否使用的是绝对决定 res = os.path.isabs(new_path) print(res)
获得指定目录下的所有文件
递归 import os ''' 获取指定目录下的所有文件 包含 子文件 次级子文件 次次级子文件等等 都得获取 注意: 现在的方法中只能获取直接子文件 ''' # path = r".." # #获取直接子文件 # sub_list = os.listdir(path) # print(sub_list) # #遍历列表 # for file_name in sub_list: # #需要判断file_name指定的是否是一个文件 # # 注意: 如果设置路径的时候 只写的是路径的文件名字 表示这个文件与当前文件是平级的 # # 需要进行路径拼接 根据上一级目录 与 自己的名字 拼出全路径 # all_path = os.path.join(path, file_name) # if os.path.isfile(all_path): # print(all_path) # elif os.path.isdir(all_path): # #获得子文件夹下的所有子目录 # sub_sub_list = os.listdir(all_path) # #遍历 # for sub_file_name in sub_sub_list: # join_path = os.path.join(all_path, sub_file_name) # if os.path.isfile(join_path): # print(join_path) ''' 上面的代码重复 查找目录下子文件这个功能代码是重复的 可以把查找子文件的代码封装成一个函数 ''' ''' 递归的出口: 不用再调用自己了 1. 最后一级目录下全部是文件 或者 是空目录 ''' def get_all_file(path): #判断path指向的路径是否存在 if os.path.exists(path): # 获得路径下的所有直接子文件 sub_list = os.listdir(path) # 遍历 for file_name in sub_list: # 路径拼接 join_path = os.path.join(path, file_name) # 判断这个路径指向的是否是一个文件 if os.path.isfile(join_path): print(join_path) else: get_all_file(join_path) else: print("路径不存在 请检查指定路径") get_all_file(r"..")
#深度遍历 ''' 需要借助栈数据结构来完成 栈的特点: 先进后出 借助于列表模拟栈 ''' # stack = [] # #添加元素 # stack.append(10) # stack.append(12) # print(stack) # # #移除数据 # stack.pop() # print(stack) import os def show_all_file(path): if os.path.exists(path): #将即将遍历的主路径存放于栈中 stack = [] stack.append(path) #接下来需要遍历栈 --- 当栈中没有数据的时候 结束 遍历 while len(stack) != 0: #取出栈中的数据进行遍历 data_path = stack.pop() #获取该目录下的子文件和子目录 sub_list = os.listdir(data_path) for file_name in sub_list: #拼接真实的路径 join_path = os.path.join(data_path, file_name) #判断是否是文件 if os.path.isfile(join_path): print(join_path) elif os.path.isdir(join_path): #将其压栈 stack.append(join_path) else: print("路径不存在") show_all_file(r"..\a")
#广度遍历 ''' 借助队列数据结构来完成 队列的特点: 先进先出 collections 模块下 有一个deque队列类 ''' from collections import deque import os # #空的队列 # d = deque() # #添加元素 # d.append(12) # d.append(13) # print(d) # # #移除 先进先出 # d.popleft() # print(d) def show_all_file(path): if os.path.exists(path): #将即将遍历的路径存放于队列中 queue = deque() queue.append(path) #当队列中空了 没有数据了 停止遍历 while len(queue) != 0: #取出即将遍历的目录 data_path = queue.popleft() #获取目录下的子文件和子目录 sub_list = os.listdir(data_path) #遍历 for file_name in sub_list: #拼接 join_path = os.path.join(data_path, file_name) #进行判断 if os.path.isfile(join_path): print(join_path) elif os.path.isdir(join_path): #压入到队列中 queue.append(join_path) else: print("路径不存在") show_all_file(r"..\a")
删除指定目录
#递归删除 ''' 1.获得指定目录下的所有直接子文件(子目录) 2.遍历 如果是文件 直接删除 如果是目录 再次进行获取 遍历 3. 遍历完成 删除目录 ''' import os # def delete_dir(path): # if os.path.exists(path): # sub_list = os.listdir(path) # for file_name in sub_list: # join_path = os.path.join(path, file_name) # if os.path.isfile(join_path): # #print(join_path) # os.remove(join_path) # else: # delete_dir(join_path) # else: # print("路径不存在") # # delete_dir(r"..") #假设一个目录下都是文件 # path = r"..\a" # # os.rmdir(path) # #获得子文件 # sub_list = os.listdir(path) # print(sub_list) # for file_name in sub_list: # #拼接路径 获得文件的真实路径 # join_path = os.path.join(path, file_name) # if os.path.isfile(join_path): # #移除文件 # os.remove(join_path) # #移除之后查看path指定的路径中是否还有子文件 # print(os.listdir(path)) # #当遍历执行完毕 目录中的内容就被清空了 # os.rmdir(path) #一个目录下既有文件 又有文件夹 # path = r"..\a" # #获取他下面的子文件和子目录 # sub_list = os.listdir(path) # #遍历 # for file_name in sub_list: # #路径拼接 # join_path = os.path.join(path, file_name) # if os.path.isfile(join_path): # #移除文件 # os.remove(join_path) # elif os.path.isdir(join_path): # #获取b文件夹下的子文件和子目录 # b_sub_list = os.listdir(join_path) # #遍历 # for b_file_name in b_sub_list: # #拼接 # b_join_path = os.path.join(join_path, b_file_name) # if os.path.isfile(b_join_path): # os.remove(b_join_path) # #出了遍历 删除b目录 # os.rmdir(join_path) # #a中空了 # os.rmdir(path) ''' 上述代码可以看出: 遍历文件夹 删除文件夹中的文件 的功能是重复的 可以把这个功能封装成函数 ''' def delete_dir(path): if os.path.exists(path): #获得指定路径下的子文件和子目录 sub_list = os.listdir(path) #遍历 for file_name in sub_list: #拼接出来file_name对应的真实路径 join_path = os.path.join(path, file_name) #判断这个路径指向的是否是一个文件 if os.path.isfile(join_path): #移除文件 os.remove(join_path) elif os.path.isdir(join_path): delete_dir(join_path) #当遍历走完 该目录就是一个空目录 os.rmdir(path) else: print("路径不存在 请重新设置") delete_dir(r"..\a")
42. 文件读写
在程序中操作的文件内容 1. 读取文件中的内容 2. 向文件中写入内容 首先: 在程序中与文件建立一个通道 通过通道操作文件指针 达到所要的结果 向文件中不管读和写, 在程序中都需要打开一个文件 打开文件的方式 --- 会返回一个操作文件的手柄 --- 可以理解为程序与文件之间建立的通道 handle = open(打开文件的路径, 打开文件的模式, encoding="操作的文件的编码方式") 讲解: 1. 文件路径即可以使用相对路径 也可以使用绝对路径 2. 打开模式: r --- 只读 如果指定路径对应文件不存在 会报错 w --- 只写 如果文件不存在 会创建新文件 如果存在 会清空文件中原来的内容 文件指针位于0 a --- 只写 如果文件不存在 会创建新文件 如果存在 不会清空源文件的内容 文件指针位于文件的末尾 r/w/a这种模式 操作的字符串数据 rb/wb/ab 这种模式操作的字节数据 r+/w+/a+/rb+/wb+/ab+ 带有+号的是可读可写 3. 编码方式: utf-8/gbk 注意: 1.程序无法处理程序之外产生的垃圾, 需要手动处理垃圾 [手动把通道关闭] 2. 如果是字节模式处理数据打开文件时 不需要设置编码方式 否则会报错 3. txt文档可以使用字符串模式 其他文件均建议使用字节数据模式(带有b的)
42.1 读取文件
读取文件内容 是通过文件手柄来完成的 读取的方式: read([size]) 如果不为size设值 默认是将文件中的内容全部读取 如果设置size值: 模式为"r" -- 表示读取size个字符 模式设置为"rb" --- 表示读取size个字节 设置size: size <= 文件中内容的大小 读到的结果就是size个 size > 文件中内容的大小 文件剩余多少读多少 readline() 一行一行进行读取 模式为r --- 读取到的是字符串数据 模式设置为"rb" --- 读取的是一行的字节数据 readlines() 按照行的样式 将文件中所有的数据全部读出 结果是列表类型的 一行为一个元素 存放于列表中 模式为r --- 列表中的每一个元素是字符串类型 模式设置为"rb" --- 列表中每一个元素是字节数据类型的
#设置操作的文件的路径 path = r"静夜思.txt" #在程序中打开文件 建立文件与程序的通道 # handle = open(path, "r", encoding="utf-8") #ValueError: binary mode doesn't take an encoding argument #handle = open(path, "rb", encoding="utf-8") handle = open(path, "rb") #通过手柄操作文件指针 读取数据 # data_str = handle.read() # print(data_str) #读取部分之后 文件指针到3的位置了 再读的话 从第四个开始 #以r形式读取的 # data_str = handle.read(3) # print(data_str) # # data_str = handle.read(5) # print(data_str) #以rb形式进行读取 # data = handle.read(3) # print(data) # print(data.decode(encoding="utf-8")) # # data = handle.read(5) # print(data) #rb # data = handle.readline() # print(data) # print(data.decode("utf-8")) #r # data_str = handle.readline() # print(data_str) #r # lines = handle.readlines() # print(lines) #rb lines = handle.readlines() print(lines) #此时列表中的数据全部是字节类型的 ----> 将列表中的数据 设置为字符串类型 ''' 字节数据只能和字符串数据进行编码和解码 其他数据不可以 ''' for ele in lines: print(ele.decode("utf-8")) new_list = [ele.decode("utf-8") for ele in lines] print(new_list) #关闭通道 handle.close()
读取大型文件 流程: 读取模式是rb -- 以字节的形式进行读取的 将文件内容分批 按照1024的倍数走的 进行读取 分批读取的结束点: 与每次读取的累加数量进行比较 < 可以获取文件的大小 ''' 读取大型文件 ''' import os path = r"C:\Users\11143\Documents\讲课内容\BJPython1901\day10\video\16.作业.mp4" #打开路径指向的文件 handle = open(path, "rb") #获得文件的大小 file_size = os.path.getsize(path) print(file_size) #分批进行读取 #设置每批读取的数据大小 一般设置为1024的倍数 -- 1024 read_size = 1024 * 10 #已经读取的字节数据 has_read = 0 #循环读取: 已读的 < 文件大小 while has_read < file_size: data = handle.read(read_size) print(data) #为已读进行累加 has_read += read_size print("读取完成") #关闭通道 handle.close()
42.2 写入文件
打开文件的模式: w/wb --- 以字符串/字节数据向文件中写入内容 如果文件不存在 创建文件 文件存在 会清空文件中的内容 文件指针位于0 a/ab --- 以字符串/字节数据向文件中写入内容 如果文件不存在 创建文件 文件存在 不会清空文件中的内容 文件指针位于文件的末尾 写入的方法: write(字符串/字节数据) writelines(列表) 与readlines是一对 列表中的数据 是要写入的内容 如果打开模式是w/a --- 列表中的数据是字符串类型的 如果打开模式是wb/ab --- 列表中的数据是字节类型的 刷新 flush() --- 刷新通道 将通道中的数据快速到达文件中
#打开文件获得与文件关联的通道 path = r"春晓.txt" handle = open(path, "a", encoding="utf-8") # handle = open(path, "ab") #写的方法 #a /w # handle.write("\n夜来风雨声,") # # lines = ["花落知多少.\n", "小桥流水人家\n"] # handle.writelines(lines) # handle.flush() #wb/ ab # handle.write("\n夜来风雨声,".encode("utf-8")) # lines = ["花落知多少.\n".encode("utf-8"), "小桥流水人家\n".encode("utf-8")] # # handle.writelines(lines) handle.flush() handle.close()
文件拷贝
操作的都是文件中的内容 已经知道了读取内容 向文件中写入内容 ----> 就可以完成文件的拷贝 思路: 源文件 读取内容 目的文件 写入内容
import os #源文件 src_path = r"C:\Users\11143\Documents\讲课内容\BJPython1901\day10\video\16.作业.mp4" #目的文件路径 desc_path = r"C:\Users\11143\Documents\讲课内容\BJPython1901\day10\notes\16.作业.mp4" #在程序中打开这两个文件 src_handle = open(src_path, "rb") desc_handle = open(desc_path, "wb") #循环读取 #设置每次读取的数量 read_size = 1024 * 10 #已经读取的数据的大小 has_read = 0 #文件大小 file_size = os.path.getsize(src_path) while has_read < file_size: data = src_handle.read(read_size) #向目的文件中写入 desc_handle.write(data) desc_handle.flush() #设置已读的数据 has_read += read_size #关闭路径 src_handle.close() desc_handle.close()
42.3 with语句
简化代码: 程序无法处理程序之外产生的垃圾 所以需要手动关闭通道 使用with语句打开通道 可以不用自己手动关闭 with语句执行完 通道会自动关闭 格式: with open(文件路径, 打开模式, 编码方式) as 变量名, open(文件路径, 打开模式, 编码方式) as 变量名1...., open(文件路径, 打开模式, 编码方式) as 变量名n: [缩进]执行的文件操作
with open("静夜思.txt", "r", encoding="utf-8") as handle: data_str = handle.read() print(data_str) with open("春晓.txt", "w", encoding="utf-8") as handle: handle.write("新来的") handle.flush()
42.4 数据持久化
永久性的保存数据 现在没有数据库 --- 想进行本地数据保存 -- 只能借助于文件 可以保存任意类型的数据 需要借助一个模块: pickle pickle.dump(保存的数据, 数据存入的指定文件) 保存数据 ====> 将数据写入到指定文件中 ====> 打开文件 这种情况下打开文件的模式只能使用字节模式打开 #将数据在文件中读取出来 变量名 = pickle.load(数据存入的指定文件) ====> 在文件中读取数据 ====> 打开文件 这种情况下打开文件的模式只能使用字节模式打开
import pickle #存放数据 一般情况下存储的是一个容器 with open("data.txt","wb") as handle: # pickle.dump(10, handle) pickle.dump([10, 20, 30, 40, "a"], handle) with open("data.txt","rb") as handle: data = pickle.load(handle) print(data)
听写
s = "good good study day day up" 1. 获得字符串中每个单词出现的次数 - 键值对形式 将最后的结果保存到 word.txt文档中 2. 在word.txt文档中读取保存的数据 获取该数据中出现次数最多的单词 word_list = s.split() word_dict = {} for w in word_list: if w not in word_dict: word_dict[w] = 1 else: word_dict[w] += 1 #要向文件中存放的字典数据 r/w --- 存放或者读取的是字符串数据 rb/wb --- 存放或者读取的是字节数据 可以的 使用普通的write方法吗???? write方法 存的是字节数据 ---> 字典转化为字节数据 字符串 - 字节数据之间的转化 使用的是数据持久化 --- 可以存放任意类型的数据 存的时候就是以字节的形式存放或者读取的 import pickle with open("word.txt", "wb") as handle: pickle.dump(word_dict, handle) 2. 读取出来 with open("word.txt", "rb") as handle: data = pickle.load(handle) #获得出现次数最多的单词 max_list = [k for k, v in word_dict.items() if v == max(word_dict.values())] 列表的标识: [] 字典的标识: {} 注意: 在程序中存放数据 --- 存储的行为 --- 保存数据 --- 数据持久化
43. 面向对象编程
43.1 面向对象和面向过程的区别
这两个都是编程思想 面向过程: 首先分析出解决问题的步骤, 把这些步骤设计成一个一个的函数, 在需要使用直接调用函数来完成最后的需求 --- 主导者 一步一步将问题有自己实现 面向对象: 首先分析出解决问题的步骤, 把这些问体分配具有对应能力的各个对象,由对象之间的互相配合, 最后完成需求的实现 ---- 借助于已经具有该功能的对象 辅助完成需求的实现
现实生活中: 以公司为例 公司中具有各个部门 主导者 -- 大boss 面向过程: 大boss 自己去招生 自己去教学 自己负责就业 面向对象: 院校人员 教学人员 就业人员
面向对象和面向过程的区别: 公司招人就是一个面向对象的 开发: 产品部门 与客户谈好需求 根据需求功能设计出来大致的功能图 [原型图] 与该项目对应的开发人员开会交流 UI设计部门 根据产品的原型图 -- 根据自己的想象 -- 设计出来设计图 --- 抠图 -- 获得小图 技术部门 后台 --- 根据产品的功能需求 设计对应的数据库 设置对应数据提供接口[方法实现的] 前端 拿到设计图 与 产品的原型图进行对比 查看是否有功能的遗漏 电脑端HTML 手机端(iOS, Android, Windows) 开始根据设计图进行前端页面的开发 页面开发好了 --- 与后台进行交互 取获取对应功能的数据 -- 显示在前端 测试 测试前端 / 后台 查看功能是否与产品一致 有偏差 ---> bug ---> 只给对应的人员 --> 对应的人员修改bug ---> 提交修改之后的代码 ---> 回馈给测试人员 bug已解决 ---> 测试人员验收 ----> 真的解决了bug关闭 正式接口和测试接口 一个项目在开发阶段 只能使用测试接口 --- 只在公司有效 --- 项目紧的话 会加班 开发完成之后 ----> 上线到正式服务器上 ---> 在正式服务器上验证没有问题 ----> 推广部门
43.2 Python中的面向对象编程
Python中所有的数据都是一个对象 例如: 10 ----> 整型数据对象 13 -----> 整型数据对象 3.14 --- 浮点型对象 True --- 布尔类型对象 "" ---- 字符串对象 [] ---- 列表类型对象 () ----- 元组类型对象 {} ----- 字典类型对象 set() ---- 集合类型对象 不同的数据对象对应的类型是不一样的 怎么为不同的数据对象 设置对应的类型呢???? 看的对象数据的特征 整型的数据 特征 全部都是整数 浮点类型的数据 全部都是小数 字符串类型的数据 全部都是被引号包含的数据 列表类型的数据 被中括号包含的数据
43.3类型和对象的关系
类型 ---- 表示的是一类具有共同特征的事物 对象 ---- 是这一类事物中某个具体的实例 类是对象的抽象描述 --- 按照对象的特征描述出来的类型 对象是类的实例
类 对象 人类 你 我 他 男人类 苏展 电脑类 正在使用的那个电脑
43.4类型的分类
系统提供的类型 和 自定义类型 系统类型 : Python提供的 直接使用的数据类型 自定义类型: 按照需求 设计出来的类型 自定义类型的格式: 类是对对象的抽象描述 ---- 针对于对象的特征描述出来的 特征描述: 自我介绍 行为描述: 吃饭 睡觉 --- 函数 class 类名: def __init__(self,接受特征描述数据的形参): self.特征描述的变量名 = 对应的特征数据 #其他函数 --- 描述对象的行为的方法 def eat(self): pass 解读: class --- 声明类的关键字 类名 --- 必须满足标识符规范 遵守驼峰命名制 -- 大驼峰 每个单词的首字母大写 __init__方法: 为初始化出来的对象赋予特征值 形参: 有几个特征设置几个形参 self: 表示的是当前调用方法的对象 --- 这个数据不用传值 解释器会自动将创建对象地址赋予给self 只需要赋予创建对象时的特征值 self.特征描述的变量名: 一个类下会有很多的对象 self 是正在调用方法的对象 每个对象特征的值一样吗???? - 不一样 需要给该类对象设置一个对应的特征变量名接受特征数据 其他函数: 根据需求 描述出来的行为 对象的行为 --- 在类中的体现就是一个函数 这个函数第一个形参必须设置为self 原因: 一个类有很多对象 到底是哪个对象来执行行为 需要self来确认 这个self不要自己赋值 解释器自动将使用该行为的对象的地址赋予给self
学生自我介绍 姓名 籍贯 年龄 大学 打篮球 睡觉 吃饭 每个对象的特征 都是一样的 --- 具有共同特征的 形成一个类Student class Student: ''' __init__ 这个方法就是为对象特征赋值的方法 ''' def __init__(self,s_name, s_age, s_address): ''' 使用self点出来的变量名 --- 对应的是对象的特征描述 self 不用自己赋值 解释器会自动将调用该方法的对象的地址赋予给self 然后通过self为指定对象的特征进行赋值 ''' self.name = s_name self.age = s_age self.address = s_address #睡觉 def sleep(self): #获取对象的名字 在睡觉 print(self.name, "在睡觉") #吃饭 def eat(self, food): print(self.name, "在吃", food)
在程序中进行交互的是 该类对应的对象数据 如何根据类创建对象??? 变量名 = 类名(实际特征值) 类名() --- 本质上调用的就是 类中声明的__init__方法 为对象特征赋值 每当使用类名(实际特征值)之后 都会有一个新的对象产生 只要调用类中__init__方法 就会有一个新的对象产生 如何调用对象的特征和行为??? 接受对象地址的变量名.特证名 获取特征值 接受对象地址的变量名.特证名 = 特征值 修改对应的特征值 接受对象地址的变量名.行为方法名([实参]) 在类的内部??? 在类的内部接受对象的地址的变量名是self 所以直接使用self来进行调用
#导入 在模块中导入要使用的类 from class_object.student import Student # import class_object.dog from class_object.dog import Dog #设置程序入口 快捷方式 main回车 if __name__ == '__main__': #创建学生对象 stu = Student("苏展", 18, "葫芦岛") print(stu) #获得姓名的这个特征的值 name = stu.name print(name) stu.age = 19 print(stu.age) #调用方法: stu.sleep() stu.eat("包子") stu1 = Student("苏展", 18, "葫芦岛") print(stu1) # s = "abc" # new_s = s.upper()
练习: 宠物狗类 特征:品种 昵称 年龄 性别 行为: 撒娇 看家 创建宠物狗类的对象 特征值: 二哈 旺财 2 雌 调用撒娇的行为 旺财在撒娇 调用看家的行为 旺财在看家
class Dog: def __init__(self,d_type, d_name, d_age, d_sex): ''' self -- 接受的是调用该方法的对象的地址 因为一个类的对象有多个 要为哪个对象的特征赋值??? 是由self来决定的 因为他拿着对象的地址 ''' self.type = d_type self.name = d_name self.age = d_age self.sex = d_sex #行为 def play(self): ''' self -- 接受的是调用该方法的对象的地址 因为一个类的对象有多个 使用哪个对象的特征值 是由self来决定的 因为他拿着对象的地址 ''' print(self.name, "在撒娇") def lookhome(self): print(self.name, "在看家") #其实调用的是类中__init__方法 dog = Dog("二哈", "旺财", "2", "雌") print(dog) dog.play() dog.lookhome()
43.5名词提取需求中的对象 完成对应类型的设计
名词提炼需求中的对象 --- 根据对象的描述 --- 完成对应类型的设计 ----> 根据需求创建对应的对象 --->由对象之间的交互完成功能的实现
案例: 主人小明让保姆小李洗衣服 -----> 小明的衣服洗了 面向过程: 洗衣服是小明自己 面向对象: 洗衣服是保姆小李的功能 面向对象: 对象: 小明 小李 小明 --- 主人类型 小李 ---- 保姆类型 主人类型 特征描述: 名字 行为描述: 让 - 保姆做事情 保姆类型 特征描述: 名字 行为描述: 洗衣服
#nurse.py class Nurse: def __init__(self,n_name): self.name = n_name #行为 def wash_cloth(self): print("洗衣服") #host.py class Host: #特征 def __init__(self, h_name): self.name = h_name #行为 主人让 保姆 形参 接受的保姆对象 def let(self, nurse): #nurse 地址 ===> main.py中baomu的地址 print("主人",self.name,"让保姆",nurse.name) #洗衣服是一个动作 nurse.wash_cloth() #mian.py from class_object.host import Host from class_object.nurse import Nurse if __name__ == '__main__': #创建一个保姆对象 baomu = Nurse("小李") #创建一个主人类对象 zhuren = Host("小明") #小明让保姆小李洗衣服 zhuren.let(baomu)
案例2: 主人baby 让 保姆小丽 洗白色的衬衫 对象: baby 小丽 衬衫 主人类 特征描述: 名字 行为描述 让 -- 保姆 保姆类: 特征描述: 名字 行为描述: 洗 --- 衣服 衣服类: 特征: 颜色 种类
#host.py class Host: def __init__(self, h_name): self.name = h_name #行为 让保姆 洗 衣服 def let(self, baomu, washed_cloth): print("主人{}让保姆{}".format(self.name, baomu.name)) #洗衣服 是保姆 baomu.wash(washed_cloth) #nurse.py class Nurse: def __init__(self, n_name): self.name = n_name #行为 - 洗 - 衣服 def wash(self,cloth): print("洗{}的{}".format(cloth.color, cloth.type)) #cloth.py class Cloth: def __init__(self, c_color, c_type): self.color = c_color self.type = c_type #main.py from class_object_1.nurse import Nurse from class_object_1.cloth import Cloth from class_object_1.host import Host if __name__ == '__main__': #衣服对象 yifu = Cloth("白色", "衬衫") #保姆 baomu = Nurse("小丽") #主人 zhuren = Host("baby") #行为的发送 zhuren.let(baomu, yifu)
'''练习: 1.baby遛宠物狗旺财 对象: baby 旺财 类型: 人类 特征:名字 行为: 遛 狗类 特征: 类型 名字 ''' #person.py class Person: def __init__(self, p_name): self.name = p_name #行为 def liu(self, gou): print("{}遛{}{}".format(self.name,gou.type, gou.name)) #dog.py class Dog: def __init__(self, d_type, d_name): self.type = d_type self.name = d_name #main.py from class_object_2.dog import Dog from class_object_2.person import Person if __name__ == '__main__': #人类对象 p = Person("baby") #狗类对象 dog = Dog("宠物狗", "旺财") #人发起行为 p.liu(dog)
2.黄渤在西三旗公园穿着特步运动鞋遛旺财 对象: 黄渤 旺财 鞋 公园 类: 人类 特征描述: 姓名 穿着 行为描述 遛 (狗 地点) 有两个形参 狗类 名字 鞋类 品牌 种类 公园类 名字
43.6 构造方法和析构方法
构造方法作用: 当一个对象被创建的时候 为对象特征赋予初始值的方法 称之为构造方法 def __init__(self) 析构方法: 执行时机 --- 当对象在内存中被销毁的时候执行 def __del__(self)
43.7 对象在内存中的存储位置与成员变量
自定义对象的存储位置: 堆中的 堆中对象销毁的时机: 这个对象没有变量名引用其地址的时候 成员变量 ---- 表示的是描述对象的特征名 ----> 又称之为属性 成员变量是伴随着对象在内存中出现的 由于对象在堆中 所以成员变量在堆中
43.8 对象在内存中的管理机制
对象是在堆中的 采用的是引用计数的管理机制 机制: 当有变量通过地址指向对象的时候, 该对象的引用计数器+1 有几个变量指向, 该对象对应的引用计数器就为几 对象在内存中的释放时机: 该对象的引用计数器为0的时候
44. 封装
封装: 广义上的封装 ---- 函数的封装 狭义上的封装 ---- 面向对象语言对对象特征的封装 狭义上的封装: 将无需对外暴露的内容隐藏起来[私有化] 然后提供对应的设置和获取的接口[是用方法体现的] 设置 -- 英文 -- set方法 获取 --- 英文 -- get方法 面向对象语言中封装的体现: 将对象的属性私有化 不让外界随意修改 然后根据需求提供对应的set和get方法供外界获取属性值 和 修改属性值 如何将对象的属性私有化 在特征属性名前添加两个下划线 self.name self.__name 好处: 1. 可以在设置值的时候根据生活实际需求添加对应的逻辑判断 2. 保护了隐私 不允许外界随意修改赋值 3. 降低了代码的重复率
''' 案例演示 ''' #person.py class Person: __slots__ = ("name", "__age", "__sex") def __init__(self, name, age, sex): self.name = name #self.age = age # if age < 0: # self.__age = 0 # else: # self.__age = age #调用为__age赋值的方法 self.set_age(age) # self.sex = sex self.set_sex(sex) #展示对象信息的方法 def info(self): print("姓名:{} age:{} sex:{}".format(self.name, self.__age, self.__sex)) #为属性age进行赋值 def set_age(self,age): if age < 0: self.__age = 0 else: self.__age = age ''' self.__age 私有化 可以使用的范围仅限于在当前类中 如果在类的外部出现了这个名字 也仅仅是与其同名而言 不是一个内容 ''' #为属性age提供对外获取的方法 def get_age(self): return self.__age #性别 不允许外界随意赋值 需要将其私有化 对外提供set和get方法 ''' set_属性名(self, 接受外界传值的变量名) get_属性名(self): return 对应的属性值 ''' def set_sex(self, sex): if sex in ("男", "女"): self.__sex = sex else: self.__sex = "男" def get_sex(self): return self.__sex #main.py from encapsulating_pack.person import Person if __name__ == '__main__': ''' 写项目 是要满足生活实际需求的 如果项目不满足生活实际 这就是个bug ''' p = Person("小明", -18, "男") p.info() #过了一年 # a = -19 # if a < 0: # p.age = 0 # else: # p.age = a # #p.age = -19 # p.info() # # # 过了一年 # a = -20 # if a < 0: # p.age = 0 # else: # p.age = a # # p.age = -19 # p.info() ''' 上面的代码功能重复了 需要封装成方法 --- 为人的对象的age进行赋值 ---> 在方法中进行筛选 由于功能是对该类对象特征进行的操作 所以该方法设置在对应的类中 ''' p.set_age(10) p.info() #为了避免外界可以直接获取age这个属性 进行随意赋值 在类中将给属性 私有化 # p.age = -11 #对象的属性可以被动态赋予 age与类中__age不是同一特征属性了 # p.info() # p.__age = -12 #对象的属性可以被动态赋予 __age与类中__age不是同一特征属性了 # p.info() # p.age = 12 # p.info() #获取对象的年龄 # value = p.__age # print(value) value = p.get_age() print(value) p1 = Person("小丽", 18, "n") p1.info() p1.set_sex("女") p1.info() value = p1.get_sex() print(value)
动物类: 特征:品种 年龄 性别 行为: 捕食 游戏
''' 动物类: 特征:品种 年龄 性别 行为: 捕食 游戏 ''' class Animal: __slots__ = ("__type", "__age", "__sex") def __init__(self,type, age, sex): self.set_type(type) # self.__age = age # self.__sex = sex self.set_age(age) self.set_sex(sex) def set_type(self, type): if type != "": self.__type = type else: self.__type = None def get_type(self): return self.__type def set_age(self, age): if age < 0: self.__age = 0 else: self.__age = age def set_sex(self, sex): if sex in ("雌", "雄"): self.__sex = sex else: self.__sex = "雄" def get_age(self): return self.__age def get_sex(self): return self.__sex def info(self): print("type:{} age:{} sex:{}".format(self.__type, self.__age, self.__sex))
44.1 python中对象的属性是可以被动态赋予的 如何限制对象的属性被动态赋予
限制对象的属性被动态赋予 在对应的类中 添加属性限制 在类中显示化__slots__字段的值 值是元组类型的 元组的内容放置的对象特征的名字
class Person: __slots__ = ("name", "__age", "__sex") def __init__(self, name, age, sex): self.name = name #self.age = age # if age < 0: # self.__age = 0 # else: # self.__age = age #调用为__age赋值的方法 self.set_age(age) # self.sex = sex self.set_sex(sex) #展示对象信息的方法 def info(self): print("姓名:{} age:{} sex:{}".format(self.name, self.__age, self.__sex)) #为属性age进行赋值 def set_age(self,age): if age < 0: self.__age = 0 else: self.__age = age ''' self.__age 私有化 可以使用的范围仅限于在当前类中 如果在类的外部出现了这个名字 也仅仅是与其同名而言 不是一个内容 ''' #为属性age提供对外获取的方法 def get_age(self): return self.__age #性别 不允许外界随意赋值 需要将其私有化 对外提供set和get方法 ''' set_属性名(self, 接受外界传值的变量名) get_属性名(self): return 对应的属性值 ''' def set_sex(self, sex): if sex in ("男", "女"): self.__sex = sex else: self.__sex = "男" def get_sex(self): return self.__sex
44.2 不同格式命名的含义
__自定义名字: __name 带有两个下滑线的私有化的 private 一般是作为对象的属性存在的 表示的是将对象的属性私有化 作用范围仅限于在当前的类中 __自定义名字__ __name__ 一般是系统提供的字段的命名方式 这种自己在自定义的时候不建议使用 __all__ __slots__ __name__ _自定义名字 _name 带有一个下滑线的 受保护的 protected 常见于模块中的自定义名字 模块和模块之间是可以互相访问的 访问方式: import 模块名 from 模块名 import 具体的内容名, 内容名2 from 模块名 import * 注意的是__all__这个字段 如果没有这个字段 默认的可以使用该模块中的非受保护的内容 如果是受保护的内容[名字前面有一个下滑线] 需要在__all__字段中设置 外界才可以使用 受保护的只有在from 模块名 import *的这种模式下才会体现出来受保护的含义
__all__ = ["_Person"] class _Person: def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex def info(self): print("name:{} age:{} sex:{}".format(self.name, self.age, self.sex))
44.3 python没有真正的私有化[了解]
在对象属性名前添加__就是给其进行私有化 外界通过这个名字无法设置该属性的值 和 获取该属性的值 因为解释器在解释私有化属性的时候 为该属性更改了名字 更改为:_类名__属性名 写的时候就认为私有化了 --- 这个知识点只是作为了解 私有化之后 就得提供set/get方法进行赋值和取值
#person.py class Person: def __init__(self, age): self.set_age(age) def set_age(self, age): if age < 0: self.__age = 0 else: self.__age = age def get_age(self): return self.__age def info(self): print("age:{}".format(self.__age)) #main.py from no_private.person import Person if __name__ == '__main__': person = Person(10) person.info() #因为类中将属相age进行私有化 self.__age #假设可以通过__age设置值 # person.__age = 18 #这个__age 和 类中对象特征对应的__age不是一个东西 # person.info() #AttributeError: 'Person' object has no attribute '__age' #print(person.__age) # print(person._Person__age) # person._Person__age = 20 # person.info()
44.4 将set/get方法属性化 — 调用方法时 向使用属性一样使用
class Person: def __init__(self, name, age): self.name = name self.set_age(age) def set_age(self, age): if age < 0: self.__age = 0 else: self.__age = age def get_age(self): return self.__age
person = Person("小明", 18) #获得没有封装的name的值 name的使用就是一般属性的使用 values = person.name #设置没有封装的name的值 person.name = "小小明" #获取封装之后age的值 age_value = person.get_age() #设置封装之后age的值 person.set_age(10)
''' 下面要进行的行为 将set方法和get方法向属性一样使用 去除小括号 因为小括号是方法使用的标记 ''' #使用get方法 age_value = person.get_age #向属性一样使用set方法 person.set_age = 10 ''' 因为没有做任何属性化的行为 上述使用方式是错误 但是我想让其成为正确的 怎么做?? 将方法属性化 -- 属性化之后就可以像属性一样使用 如何属性化?? 有两种方式: 1. 添加装饰器 get方法属性化的方式 在get方法上添加 装饰器@property 添加上这个装饰器之后 get方法使用的时候 只能像属性一样使用 不能再回归到向函数那样调用 set方法属性化 在set方法上添加装饰器 这个装饰器是由其对应的get方法衍生来的 格式: @对应的get方法名.setter 添加上这个装饰器之后 set方法使用的时候 只能像属性一样使用 不能再回归到向函数那样调用 为了真正的像属性一样使用 属性化之后可以给方法改名 --- 将方法名改成对应的属性名 姓名对应的属性名 === name 年龄对应的属性名 === age 2. 借助于property方法 对应的属性名 = property(get的方法名, set的方法名) 在外界使用方法的时候 可以直接 对应的属性名来像属性一样进行赋值和取值 这种形式进行属性化之后 可以使用对应的属性名像属性一样调用方法 也可以直接调用对应的set/get方法取赋值和取值 '''
#添加装饰器 class Person: __slots__ = ("name", "__age") def __init__(self, name, age): self.name = name # self.set_age(age) #self.set_age = age self.age = age # @property # def get_age(self): # return self.__age @property def age(self): return self.__age # @get_age.setter # def set_age(self, age): # if age < 0: # self.__age = 0 # else: # self.__age = age @age.setter def age(self, age): if age < 0: self.__age = 0 else: self.__age = age def info(self): print("name:{} age:{}".format(self.name, self.__age))
#使用property方法 class Student: def __init__(self, age): self.set_age(age) def set_age(self, age): if age < 0: self.__age = 0 else: self.__age = age def get_age(self): return self.__age def info(self): print("age:{}".format(self.__age)) #另外一种属性化方式 借助于property方法 这个方法会有一个数据的返回 返回的属性化的那些方法 ''' 用一个变量接受 这个变量命名的时候 是根据为哪个特征的set/get方法属性化决定的 特证名叫什么 变量名就叫什么 使用方法的时候 直接变量名即可 ''' age = property(get_age, set_age)
from set_get_property.student import Student if __name__ == '__main__': stu = Student(10) stu.info() #属性化之后的get方法 age_value = stu.age print(age_value) #set stu.age = 11 stu.info() stu.set_age(20) stu.info()
45. 继承
从父辈获取父辈拥有的东西 在程序中的继承: 从两个或者两个以上的相关的普通类中提取共同的特征和行为到一个共通类中, 再使用继承的体系,普通类从对应的共通类中获取被提取的特征和行为 共通类: 称之为父类, 超类 基类 普通类: 称之为子类 类和类之间的关系: 有两种: is a 什么是什么的一个关系 可以把一个类归到另外一个类的其下 男人 是 人类 is a体现就是继承的关系 has a 什么拥有什么的关系 一个类中的内容可以拥有另外一个类的信息 人类 鞋类
父类的声明格式 class 父类类名: def __init__(self, 从众多子类中提取出来的特征): pass 众多子类中的共同行为 子类的声明格式 class 子类类名(父类类名, 父类1, 父类2,.., 父类n): def __init__(self, 被提取的共同特征, 自己特有的特征): pass 子类中特有的行为
案例: 工人类: 特征: #姓名 年龄 性别 工龄 行为: #吃饭 工作 学生类: 特征: #姓名 年龄 性别 学号 行为: #吃饭 学习 人类: 子类中共同的特征: 姓名 年龄 性别 共同的行为: 吃饭 如果从子类中把共同的特征和行为抽取出来 子类中不用再次声明, 直接从父类中继承
class Person: def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex #共同的行为: def eat(self): print(self.name, "在吃饭") #工人类 class Worker(Person): #注意创建工人的时候 共有特征的需要赋值 init方法就得设置形参接受对应的数据 ''' self --- 使用在当前类中 表示调用方法的对象本身 super -- 使用在子类中 表示的是父类 注意:共有的特征已经在父类中的init方法中可以被赋值了 子类可以直接调用父类中的init方法 取给共同赋值 调用方式: 1.super(当前子类的类名, self).__init__(共同特征的数据) super 表示的是父类 当前子类类名 --- 告知父类是哪个子类调用的父类 self --- 告知父类是哪个子类的对象需要给特征信息赋值 2. 是上述方式的简化 super().__init__(共同特征的数据) ---- 常用的 -- 因为平常使用的时候都是继承一个父类 3. 常用于继承多个父类的关系中使用 父类类名.__init__(self, 共同特征信息值) self是可以不用传值的 解释器会自动将调用该方法的对象传递给self 但是这种形式不是对象调动的父类中的init方法 而是类型调用的 所以需要将被赋予特征信息值的对象传递过去 ''' def __init__(self, name, age, sex, wage): #super(Worker, self).__init__(name, age, sex) #super().__init__(name, age, sex) Person.__init__(self, name, age, sex) self.wage = wage #独特的行为 def work(self): ''' 子类使用父类中的未私有化的信息 就像使用自己的一样 ''' print(self.name, "在工作") class Student(Person): def __init__(self, name, age, sex, sid): super(Student, self).__init__(name, age, sex) self.sid = sid #学习 def study(self): print(self.name, "在学习")
''' 狗类: 特征 品种 年龄 性别 行为: 拆家 看家 猫类 特征: 品种 年龄 性别 花色 行为: 吃鱼 抓老鼠 ''' class Animal: def __init__(self, type, age, sex): self.type = type self.age = age self.sex = sex #狗类 class Dog(Animal): def __init__(self, type, age, sex): super().__init__(type, age, sex) #行为: def destory_home(self): print(self.type,"拆家") def look_home(self): print(self.type, "看家") #猫类 class Cat(Animal): def __init__(self, type, age, sex, color): super().__init__(type, age, sex) self.color = color #行为 def eat_fish(self): print(self.type, "吃鱼") def catch_mouse(self): print(self.type, "抓老鼠")
继承体系中: 子类可以从父类中继承非私有化的内容 私有化的内容一般指的是特征属性 -- 封装 --- 不允许外界随意赋予不满足实际需求的值 所以需要进行封装, 将对应的属性特征私有化, 私有化的内容只能在当前类中使用 出了类借助对应类提供的set/get接口获取其对应的数据 在继承体系中 有类就免不了有封装, 就有对应的私有化 在子类中使用父类中封装的东西 --- 间接使用 调用父类中提供的set/get方法来使用
''' 狗类: 特征 品种 年龄 性别 行为: 拆家 看家 猫类 特征: 品种 年龄 性别 花色 行为: 吃鱼 抓老鼠 ''' class Animal: def __init__(self, type, age, sex): self.type = type # self.age = age self.set_age(age) self.sex = sex def set_age(self, age): if age < 0: self.__age = 0 else: self.__age = age def get_age(self): return self.__age #狗类 class Dog(Animal): def __init__(self, type, age, sex): super().__init__(type, age, sex) #行为: def destory_home(self): #几岁的狗拆家 # print(self.type,"拆家") #父类隐藏 子类不能直接调用 # print("{}的{}在拆家".format(self.__age, self.type)) #根据父类提供的获取接口来使用 print("{}岁的{}在拆家".format(self.get_age(), self.type)) def look_home(self): print(self.type, "看家") #猫类 class Cat(Animal): def __init__(self, type, age, sex, color): super().__init__(type, age, sex) self.color = color #行为 def eat_fish(self): print(self.type, "吃鱼") def catch_mouse(self): print(self.type, "抓老鼠")
45.1 继承的分类
分为单继承和多继承 单继承: 一个类的直接父类只允许有1个, 但是可以间接继承多个父类 孙子 -- 父亲 -- 爷爷 一脉单传 java 多继承: 一个类可以继承自多个直接父类, 也可以间接继承其他类 Python是支持多继承的 C++
从多继承中演示类的继承体系: 动物类 哺乳类 鸟类 地上跑的动物类 天上飞的动物类 动物: 狗类 -- 哺乳类 地上跑的类 蝙蝠 --- 哺乳类 天上飞的 鸵鸟 --- 鸟类 地上跑的 麻雀 ---- 鸟类 天上飞的
#class.py class Animal: def __init__(self): print("动物总类") #哺乳类 class Mammals(Animal): def __init__(self): super().__init__() print("哺乳类") #鸟类 class Bird(Animal): def __init__(self): super().__init__() print("鸟类") #能跑的 class Running(Animal): def __init__(self): super().__init__() print("能跑的动物类") #能飞的 class Flying(Animal): def __init__(self): super().__init__() print("能飞的动物类") #狗类 class Dog(Mammals, Running): def __init__(self): super().__init__() print("狗类") #鸵鸟 class Ostrich(Bird, Running): def __init__(self): super().__init__() print("鸵鸟类") #蝙蝠 class Bat(Mammals, Flying): def __init__(self): super().__init__() print("蝙蝠类") #main.py from extend_pack.many_extend_classes import * if __name__ == '__main__': #创建狗的对象 dog = Dog() #查看一个类的继承体系 类名.mro() res = Dog.mro() print(res) #继承体系采用的是广度遍历 ''' [<class 'extend_pack.many_extend_classes.Dog'>, <class 'extend_pack.many_extend_classes.Mammals'>, <class 'extend_pack.many_extend_classes.Running'>, <class 'extend_pack.many_extend_classes.Animal'>, <class 'object'>] ''' ''' 动物类 能跑的 狗 哺乳 狗 '''
多继承中 子类有多个父类 并且从每个父类中都继承了特征, 在子类中如果使用super().__init__(特征信息) 取调用父类中的构造方法, 只会调用继承的第一个父类 之后的父类的构造方法无法调用, 如果想调用其他父类的构造方法, 需要指明调用的父类
class Num1: def __init__(self, num1): self.num1 = num1 class Num2: def __init__(self, num2): self.num2 = num2 #既有数值1类的数据 还有数值2类的数据 class Num(Num1, Num2): def __init__(self, num1, num2): super().__init__(num1) # super().__init__(num2) Num2.__init__(self, num2) def info(self): print("num1:{} num2:{}".format(self.num1, self.num2)) if __name__ == '__main__': num = Num(12, 13) num.info()
45.2 继承体系中限制对象动态增加属性的设置
为类的对象限制动态赋予属性的设置 只对当前类有效, 在继承体系中对于子类是无效的 所以为了限制子类的对象随意动态增加属性 需要在子类中也设置__slots__这个字段的值 如果父类中设置了__slots__这个字段的值 子类没有设置 对于子类是无效的 如果父类中设置了__slots__这个字段的值 子类也设置了, 字段中的数据子类可以继承父类中的, 父类中设置的子类可以不用再设置, 直接只设置自己的特性即可
class Animal: __slots__ = ("name", "age", "sex") def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex class Tiger(Animal): __slots__ = ("color",) def __init__(self, name, age, sex, color): super().__init__(name, age, sex) self.color = color if __name__ == '__main__': tiger = Tiger("东北虎", 2, "雌", "花色") tiger.color = "豹纹" # a = Animal("旺财", 2, "雄") # a.color = "黄色"
45.3 方法的重写
object 是所有类的直接或者间接的父类, 是继承体系中的根类 如果一个类没有显示继承任何类 默认继承自object 查看一个类的父类: 类名.__bases__ 通过这个字段取查看
什么时候会有方法的重写??? 子类中共同的行为被提取到父类中. 如果子类中该行为的实现与父类中行为的实现不一致, 子类可以在自己的类中重写这个行为. 当子类将该行为重写过后, 对于子类而言 原来父类中的行为相当于被覆盖了, 调用这个行为的时候 调用的是自己重写过后 自定义方法的重写 系统方法的重写 __str__ __repr__ 运算符重载
自定义方法的重写: 案例: 愤怒的小鸟: 红鸟 -- 攻击 死撞 黄鸟 --- 攻击 除了死撞 还有附加技能加速 绿鸟 --- 攻击 除了死撞 --- 还有回旋的技能 黑鸟 -- 攻击 除了死撞 --- 还有爆炸的技能 鸟类 行为: 攻击的行为 死撞
class Bird: def __init__(self, color): self.color = color #提取行为 def attach(self): print("{}鸟的攻击行为是死撞".format(self.color)) class RedBird(Bird): def __init__(self, color): super().__init__(color) class BlackBird(Bird): def __init__(self, color): super().__init__(color) #黑色鸟的攻击行为的实现 与 父类中攻击行为的实现不一致 可以在当前类中对该方法进行重写 ''' 重写的时候: 子类中的方法声明要与 父类中方法声明一致 def 方法名(形参): ''' def attach(self): #由于此处还需要父类中的实现 如何操作??? super().attach() #在子类的方法实现中 通过父类对象去调用父类中的行为 ''' 上面的super().父类中方法名() 这个代码不一定有 如果需要父类中的实现 可以通过上述代码调用 如果不需要 没有必要去进行调用 ''' print("{}的鸟还有附加技能是爆炸".format(self.color))
系统方法的重写 __str__ __repr__ 这两个方法都具有返回值 返回值类型是字符串类型的 他们的作用 是来对外描述对象的特征信息的 区别: __str__: 直接输出对象, 查看对象特征信息时调用的方法 print(对象) __repr__: 以其他容器为视角,查看对象特征信息是调用的方法
class Person: def __init__(self, name, age): self.name = name self.age = age def __str__(self): return "Person name:{} age:{}".format(self.name, self.age) # def __repr__(self): # return "Person name:{} age:{}".format(self.name, self.age) ''' __str__ 与 __repr__这两个方法返回的数据一般是一样的 可以将其中一个函数实现 并把这个函数当做值赋给另一个函数 ''' __repr__ = __str__ # def info(self): # print("name:{} age:{}".format(self.name, self.age)) if __name__ == '__main__': person = Person("小明", 10) print(person) #想看到的是 对象的特性信息 list0 = [12, 34, person] print(list0) # person.info()
在一个类中 如果包含了多个其他类的对象 采用列表来存放 需求中 大多数的情况下是根据指定内容查找对象 --- 这种情况下使用字典来存放
听写
鱼类: 特征:品种 性别 年龄 行为: 呼吸 吃饭 蛙类:Batrachia 特征:品种 性别 年龄 行为: 呼吸 吃饭 知识点的体现: 1.封装 继承 限制对象随意增加属性 鱼类: 呼吸用腮呼吸 蛙类: 呼吸用肺和皮肤呼吸 class Animal: __slots__ = ("type", "__age", "__sex") def __init__(self, type, sex, age): self.type = type self.set_age(age) self.set_sex(sex) #对年龄进行判定 def set_age(self, age): if age < 0: self.__age = 0 else: self.__age = age #对性别进行判定 def set_sex(self, sex): if sex in ("雄", "雌"): self.__sex = sex else: self.__sex = "雄" #获取的接口 def get_age(self): return self.__age @property def get_sex(self): return self.__sex #呼吸 def breath(self): pass #吃饭 def eat(self): print(self.type, "吃饭") #鱼类 class Fish(Animal): __slots__ = () def __init__(self,type, sex, age): super().__init__(type, sex, age) #重写 def breath(self): print(self.type, "用腮呼吸") #重写 def __str__(self): return "Fish type:{} age:{} sex:{}".format(self.type, self.get_age(), self.get_sex) __repr__ = __str__ #蛙类 class Batrachia(Animal): __slots__ = () def __init__(self,type, sex, age): super().__init__(type, sex, age) #重写 def breath(self): print(self.type, "用肺和皮肤呼吸") #重写 def __str__(self): return "Fish type:{} age:{} sex:{}".format(self.type, self.get_age(), self.get_sex) __repr__ = __str__ #创建对象 fish = Fish("金鱼", "雌", 2) print(fish) fish.breath() fish.eat() batr = Batrachia("青蛙", "雄", 3) batr.breath() batr.eat()
46. 运算符重写
之前讲的一些数据类型,例如: str + * % >= <= == != list + * >= <= == !=
class Pointer: def __init__(self, x, y): self.x = x self.y = y #重写运算符对应的方法 def __add__(self, other): #self 是运算符前的对象 other是运算度符的对象 return Pointer(self.x + other.x, self.y + other.y) #- def __sub__(self, other): return Pointer(self.x - other.x, self.y - other.y) #* def __mul__(self, value): return Pointer(self.x * value, self.y * value) # / def __truediv__(self, value): return Pointer(self.x / value, self.y / value) #// def __floordiv__(self, value): return Pointer(self.x // value, self.y // value) #% def __mod__(self, value): return Pointer(self.x % value, self.y % value) #> def __gt__(self, other): if self.x > other.x or self.y > other.y: return True else: return False # >= def __ge__(self, other): if self.x >= other.x or self.y >= other.y: return True else: return False # < def __lt__(self, other): if self.x < other.x or self.y < other.y: return True else: return False # <= def __le__(self, other): if self.x <= other.x or self.y <= other.y: return True else: return False # == def __eq__(self, other): if self.x == other.x and self.y == other.y: return True else: return False #!= def __ne__(self, other): if self.x != other.x or self.y != other.y: return True else: return False #in def __contains__(self, item): pass def __str__(self): return "Pointer x:{} y:{}".format(self.x, self.y) __repr__ = __str__ if __name__ == '__main__': #两个点对象相加 p1 = Pointer(20, 30) p2 = Pointer(5, 7) #p1 + p2 ---> 生成一个新的点对象 点对象的信息为p1和p2的坐标和 new_p = p1 + p2 print(new_p) print(id(p1), id(p2), id(new_p)) #p1 - p2 ---> 生成一个新的点对象 点对象的信息为p1和p2的坐标差 new_p = p1 - p2 print(new_p) print(id(p1), id(p2), id(new_p)) # * #将坐标对象乘以一个整数 生成一个新的点对象 点对象的信息为原始坐标数据的n倍 new_p = p1 * 5 print(new_p) print(id(p1), id(p2), id(new_p)) # 除法 / 会有小数 // 整除 # 将坐标对象除以一个整数 生成一个新的点对象 点对象的信息为原始坐标数据/n # / new_p = p1 / 5 print(new_p) print(id(p1), id(p2), id(new_p)) new_p = p1 // 5 print(new_p) print(id(p1), id(p2), id(new_p)) #> < >= <= == != 这些比较坐标系数据 res = p1 > p2 print(res) res = p1 >= p2 print(res) res = p1 < p2 print(res) res = p1 <= p2 print(res) res = p1 == p2 print(res) res = p1 != p2 print(res) new_p = p1 % 5 print(new_p) print(id(p1), id(p2), id(new_p))
''' == 如果对应的类中没有重写==对应的方法, 默认是根据对象的地址来进行比较的 一般情况下对齐重写是为了 符合生活实际需求 对于内存而言进行伪相等判定 根据生活实际需求 设置对应的条件判断 根据这些判断验证数据是否相等 ''' class Person: ''' name -- 姓名 cardid-- 身份证号 两个身份证号一致 ---> 对应的是一个人 == 来验证的时候 结果应该是True ''' def __init__(self, name, cardid): self.name = name self.cardid = cardid #重写eq方法 def __eq__(self, other): if self.cardid == other.cardid: return True else: return False if __name__ == '__main__': p1 = Person("小乔", "1101") p2 = Person("小乔", "1101") res = p1 == p2 print(res)
47. 多态
多态: 事物的多种形态 变量的多种形态 ----> 是根据赋值情况产生的多种形态 ----> 查看 变量的数据类型根据赋予对应的数据值来决定的 静态语言 和 动态语言 动态语言 - Python 声明变量时 不用限制变量的类型, 具体类型根据赋值来决定 a = 10 a = "10" 静态语言: 属于强类型语言, 声明变量的时候必须设置该变量所属的类型, 而且类型确定之后 不能再赋予其他类型的数据, 除非进行相关类型之间的类型强转 java int a = 10 a = "10"
主人杨夫人 向客人 李小姐介绍自己家的宠物狗, 介绍宠物猫 宠物狗: 昵称是:贝贝 年龄是:2 性别:雌 会两条腿行走的才艺 宠物猫 昵称是:花花 年龄是 1 性别是:雄 会装死的才艺 向上提取一个类型: 宠物类: 特征: 昵称 年龄 性别 才艺 宠物猫类 宠物狗类 主人类 特征:姓名 行为: 介绍 宠物狗 介绍宠物猫 客人类 特征: 姓名
体现多态的地方 -- 一般情况下就是一个变量根据接受的数据处于不同的类型 处理的数据的时候 不同的类型 数据的体现格式不一致 ----> 需要根据变量对应的类型做出对应的操作 -----> 如果判断变量是哪个类型 isinstance(变量, 类型) 根据判断做出对应的操作
class Pet: __slots__ = ("name", "__age", "__sex", "skill") def __init__(self, name, age, sex, skill): self.name = name self.age = age self.sex = sex self.skill = skill @property def age(self): return self.__age @age.setter def age(self, age): if age < 0: self.__age = 0 else: self.__age = age @property def sex(self): return self.__sex @sex.setter def sex(self, sex): if sex in ("雌", "雄"): self.__sex = sex else: self.__sex = "雄" class PetDog(Pet): __slots__ = () def __init__(self, name, age, sex, skill): super().__init__(name, age,sex, skill) class PetCat(Pet): __slots__ = () def __init__(self, name, age, sex, skill): super().__init__(name, age,sex, skill) #HOST.PY from polymorphic_pack.pet_classes import PetCat, PetDog class Host: def __init__(self, name): self.name = name #行为 def introduce(self,keren, pet): ''' 介绍狗的时候 pet接受的地址对应的类型PetDog 介绍猫的时候 pet接受的地址对应的类型PetCat 这里体现了多态 --- 由于不同类型展示的信息不一致 -- 所以需要确定变量的类型 根据对应的类型 输出对应的信息 ''' if isinstance(pet, PetDog): print("主人{}向客人{}介绍宠物狗昵称是{},年龄是{},性别是{},才艺是{}".format( self.name, keren.name, pet.name, pet.age, pet.sex, pet.skill)) elif isinstance(pet, PetCat): print("主人{}向客人{}介绍宠物猫昵称是{},年龄是{},性别是{},才艺是{}".format( self.name, keren.name, pet.name, pet.age, pet.sex, pet.skill)) class Guest: def __init__(self, name): self.name = name #MAIN.PY from polymorphic_pack.pet_classes import * from polymorphic_pack.host import * if __name__ == '__main__': #创建主人对象 host = Host("杨夫人") #创建客人对象 guest = Guest("李小姐") #宠物狗 dog = PetDog("贝贝", 2, "雄", "两条腿走路") #宠物猫 cat = PetCat("花花", 3, "雌", "装死") #介绍宠物狗 host.introduce(guest, dog) #介绍宠物猫 host.introduce(guest, cat)
48. 类属性/类方法/静态方法
1.类属性的讲解: 类属性和对象属性的区别: 对象属性: 从众多对象中提取出来的特征, 描述对象的信息的, 对于每个对象来说是相互独立[每个对象都有对应的对象属性, 其中一个对象的属性值发生修改 不影响其他对象], 对象属性是在构造方法中使用self.来进行修饰的 类属性:直接在类中声明的属性[变量], 类属性有且只有一份, 它是被这个类所有对象所共享的[如果类属性的值发生变化, 影响的是所有对象] 类属性的使用时机: 当该类所有对象对应的属性的值都是一样的, 这个时候需要把这个属性提升为类属性 提升为类属性的好处: 减少堆中内存的占用. 类信息存放的位置: 方法区的 注意: 1.修改类属性的时候 千万不要用对象进行修改, 因为对象可以动态的增加属性, 如果用对象给修改 其实本质上是给这个对象增加了一个独立属性而已, 与类属性无关. 修改类属性只能使用类名.属性取进行修改 2.对象属性与类属性同名了, 通过对象调用的时候 调用的是对象属性的数据 对于对象而言 对象属性的优先级高于类属性 可以看出: 类属性既可以被类进行调用 也可以被对象进行调用. 建议通过类来使用. 但是进行修改的时候 必须是类来进行修改
案例: 人类信息: 姓名 年龄 性别 国籍 ----> 中国的
class Person: country = "CH" age = 10 # __slots__ = ("name", "age", "sex") def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex # self.country = country if __name__ == '__main__': #创建多个人类对象 # p1 = Person("小明",17, "男", "CH") # p2 = Person("小花", 18, "女", "CH") # p3 = Person("小丽", 17, "女", "CH") # p4 = Person("小强", 15, "男", "CH") # p5 = Person("小红", 16, " 女", "CH") p1 = Person("小明", 17, "男") p2 = Person("小花", 18, "女") p3 = Person("小丽", 17, "女") p4 = Person("小强", 15, "男") p5 = Person("小红", 16, "女") print(p1.country) print(p2.country) print(p3.country) print(p4.country) print(p5.country) #用对象对类属性进行修改 # p1.country = "Chinese" Person.country = "chinese" print(p1.country) print(p2.country) print(p3.country) print(p4.country) print(p5.country) #age 对与对象而言 有对象属性 还有类属性 #通过对象来调用 调用的是对象属性 print(p1.age)
练习: 学生类: 特征: 学号 姓名 年龄 学生对象中特殊的数据 --- 描述当前创建对象的个数的 - 检测创建对象的个数的 s1 ---> 数据 ---> 1 s1 s2 ----> 数据 ----> 2 最后用创建的对象获取学生个数的数据结果都是一致的 如何检测对象被创建了?????? 在对象被创建的行为中对应 个数+1即可 在类中只要__init__方法执行一次 就出现一个新的对象
class Student: stucount = 0 def __init__(self,sid, name, age): self.sid = sid self.name = name self.age = age #这个方法可以检测对象是否被创建了 创建了计数器+1 Student.stucount += 1 if __name__ == '__main__': s = Student("001", "小明", 10) s1 = Student("001", "小明", 10) s2 = Student("001", "小明", 10) s3 = Student("001", "小明", 10) print(s.stucount) print(s2.stucount)
2.类方法: 对象方法和类方法的比较: 对象方法: 提取的是所有对象中的行为, 对象方法的第一个参数设置的self - 接受的是调用该行为的对象的地址. 对象方法调用时 是通过对象来调用的 在类中声明的普通方法都是对象方法 类方法: 需要一个装饰器对方法进行修饰, 该方法才可以成为类方法 装饰器: @classmethod 类方法的第一个参数设置的是cls --- 默认接受的是当前的类型 类方法: 属于类的 可以被类进行调用 也可以被对象调用 什么时候使用类方法?? 当需要处理类属性的时候 一般情况下使用就是类方法
特殊的类方法: @classmethod def __new__(cls, *values, **kwargs) 作用: 是新建当前类的对象 构造方法: -- 对象方法 def __init__(self): 为创建出来的对象特征进行初始化赋值的
class Student: __stucount = 0 #这个变量是不能被外界随意修改的 def __init__(self,sid, name, age): self.sid = sid self.name = name self.age = age #这个方法可以检测对象是否被创建了 创建了计数器+1 Student.__stucount += 1 #提供一个获取该类属性的方式 @classmethod def get_stuxount(cls): print(cls) return cls.__stucount if __name__ == '__main__': s = Student("001", "小明", 10) s1 = Student("001", "小明", 10) s2 = Student("001", "小明", 10) s3 = Student("001", "小明", 10) # print(s.stucount) # # print(s2.stucount) print(Student.get_stuxount()) print(s.get_stuxount())
注意: 在类方法中不能使用self 原因: 在程序中是类先被加载到内存中?? 还是对象先被加载到内存中??? 有对象的时候肯定有类 ====> 类是先于对象在内存中存在的 有类的时候一定有对象吗??? ----> 不一定 self ----> 属于对象 类属性和类方法都是属于类的,加载到内存中的时机与类是一致的 在类方法存在的时候 有对象吗??? ---> 不一定 ---> 没有 没有这个对象能使用self吗??? ---> 不能 总结原因: 类方法在内存中的加载时机优先于对象, 不能使用在内存中不存在的数据
对象创建的流程: 从内存角度分析对应的流程 person = Person("小明", 10) 1. 先找到Person类 将Person加载到方法区中 同时加载的有类属性和类方法 2. 调用该类 __new__类方法 创建一个对象 将对象存放于堆区 随着对象加载到堆区的有 对象的属性 3. 调用对象的__init__方法 为对象的属性进行赋值 4. 将对象对应地址赋予给变量person
3.静态方法[了解] 声明在类中的, 需要一个装饰器修饰 @staticmethod 与对象方法和类方法的区别: 对象方法有表示当前调用方法的对象的变量self 类方法中有表示调用当前方法的类的变量cls 静态方法中没有多余形参, 与一开始接触函数时一样 分析功能 有几个未知项设置几个形参 静态方法: 可以使用类进行调用 也可以使用对象进行调用 在其他语言中 静态方法 === 类方法 但是在Python单独分出来一部分 什么情况下使用静态方法??? 如果想设计一个类 --- 工具类 --- 为其他地方提供服务的 类中的方法可以设计为静态方法 --- 使用方法的时候直接使用类来进行调用 在Python中有一个东西叫做模块 ----> 单独的就设计为一个工具模块
49. 综合案例:银行操作系统
小沐银行操作系统: 要求:每个用户只能拥有该银行的一张卡 卡类: 卡号 密码 金额[默认是10] 是否锁卡[默认是没锁] 账户类: 姓名 身份证号 所拥有的卡[一个账户对象一张卡] 操作类: 行为: 1.开户:需要身份证号和姓名 2.查询 3.存款 4.取款 5.转账 6.修改密码 7.锁卡 8.解卡 9.销户 0.退出 发生锁卡的情况: 输入密码三次错误 锁卡 或者执行锁卡行为 卡号随机产生: 100000 - 999999 提示界面类: 欢迎提示:欢迎进入小沐银行系统 操作提示:请输入指定操作 【操作数字按照操作类中的行为符号进行提示】 用户选择对应的符号 执行操作类中对应的行为
银行如果有用户注册 --- 得将这些用户全部存储起来 ----> 字典 字典肯定有用户 不同的用户卡号是不能相同的 键: 卡号 值:用户 银行刚开营业的时候: 没有用户 字典中没有任何键值对 {} 营业了一些时间之后: 有用户了 程序启动起来 需不需要将这些用户加载出来 存放在字典中??? 需要 程序刚启动的时候 用户列表分为两种情况: {} {之前注册过的用户} 这种情况如何展示??? 没有注册用户 直接使用{}即可 注册用户之后 银行下班了 -- >用户信息存储 ---> 字典 ---> 字典 ---> 数据持久化 ---> 跟文件关联了 ---有文件的时候 是已经有用户了 银行再上班的时候 需要从文件中 将用户信息下载下来
#card class Card: #卡号 密码 金额[默认是10] 是否锁卡[默认是没锁] def __init__(self, cardid, password): self.cardid = cardid self.password = password self.money = 10 self.islock = False def __str__(self): return "Card id:{} psw:{} money:{} islock:{}".format(self.cardid, self.password, self.money, self.islock) __repr__ = __str__
#account.py class Account: #姓名 身份证号 所拥有的卡[一个账户对象一张卡] def __init__(self, name, id, card): self.name = name self.id = id self.card = card def __str__(self): return "Account name:{} id:{} card:{}".format(self.name, self.id, self.card) __repr__ = __str__
#bank.py import os import pickle import random from bank_pack.card import Card from bank_pack.account import Account class Bank: def __init__(self): #需要加载用户信息 self.load_accounts() #加载用户信息 def load_accounts(self): #银行刚营业 -- 没有存放过任何用户信息 ----> 就没有对应的文件产生 if not os.path.exists("account.txt"): self.account_dict = {} else: #银行营业一段时间 有过用户的开户 银行上班 加载用户信息 with open("account.txt", "rb") as handle: self.account_dict = pickle.load(handle) print(self.account_dict) #生成卡号 def get_cardid(self): while True: cardid = random.randint(10000, 99999) if cardid not in self.account_dict: return cardid #1.开户 def create_account(self): #让用户输入姓名 和 身份证号 一个人只有一张卡 人唯一的标识是身份证号 name = input("请输入姓名:") aid = input("请输入身份证号:") #需要验证这个人的身份证号是否已经存在 for acc in self.account_dict.values(): if acc.id == aid: print("该用户已经注册过了, 不能重新注册") return #结束函数 不再继续向下进行 #该用户对应的卡 #卡号 cid = self.get_cardid() #密码需要用户输入 psw = input("请输入密码:") #创建一张卡 card = Card(cid, psw) #根据以上信息创建一个用户 account = Account(name, aid, card) #将用户存放在爱字典的容器中 卡号为键 用户为值 self.account_dict[cid] = account print(self.account_dict) #验证密码是否正确 卡的的密码是否正确: def check_psw(self, card): #三次错误 锁卡 count = 0 while True: input_psw = input("亲输入密码:") if input_psw != card.password: count += 1 else: return True if count == 3: #三次输入错误 #锁卡 card.islock = True return False #获取卡 def get_card(self): #1. 输入卡号 cid = int(input("亲输入卡号:")) #验证卡号在先有的用户中是否存在 if cid not in self.account_dict: print("卡号不存在 请先注册") return None#结束进行的流程 else: #卡号存在 #根据卡号获得对应的卡 account_card = self.account_dict[cid].card if account_card.islock: print("您的卡已锁, 请先解卡") return None #验证这张卡的密码 res = self.check_psw(account_card) if res == False: print("密码输入三次错误 以锁卡 先进性解卡") return None else: return account_card #查询 def search_balance(self): card = self.get_card() if card == None: return else: print("卡中余额为{}".format(card.money)) #存款 def save_money(self): card = self.get_card() if card == None: return else: balance = int(input("请输入要存入的金额:")) card.money += balance print("存款成功, 卡中余额为:{}".format(card.money)) # 取款 def get_money(self): card = self.get_card() if card == None: return else: balance = int(input("请输入要取出的金额:")) if balance > card.money: print("余额不足") else: card.money -= balance print("取款成功, 卡中余额为:{}".format(card.money)) #转账: def transform(self): card =self.get_card() if card == None: return else: #请输入对方的卡号 #限制输入次数 count = 0 while True: other_cid = int(input("请输入对方的卡号:")) #验证这个卡号是否存在 if other_cid not in self.account_dict: print("卡号错误") count += 1 else: break if count == 3: print("请确认卡号, 重新进行转账行为") break #根据对方的卡号获得对方的卡 other_card = self.account_dict[other_cid].card balance = int(input("请输入要转出的金额:")) if balance > card.money: print("余额不足") else: card.money -= balance other_card.money += balance print("转账成功, 卡中余额为:{}".format(card.money)) #修改密码: def update_password(self): card = self.get_card() if card != None: new_psw = input("请输入新的密码:") card.password = new_psw print("修改成功") #锁卡 def lock_card(self): #获取卡号 cid = int(input("请输入卡号:")) #验证 if cid not in self.account_dict: print("卡号不存在 请重新操作") return account = self.account_dict[cid] while True: #请输入身份证号 aid = input("请输入身份证号:") if account.id != aid: print("重新输入") else: break #锁卡 account.card.islock = True print("锁卡成功") # 解卡 def unlock_card(self): # 获取卡号 cid = int(input("请输入卡号:")) # 验证 if cid not in self.account_dict: print("卡号不存在 请重新操作") return account = self.account_dict[cid] while True: # 请输入身份证号 aid = input("请输入身份证号:") if account.id != aid: print("重新输入") else: break # 解卡 account.card.islock = False print("解卡成功") #销户 def kill_account(self): # 获取卡号 cid = int(input("请输入卡号:")) # 验证 if cid not in self.account_dict: print("卡号不存在 请重新操作") return account = self.account_dict[cid] while True: # 请输入身份证号 aid = input("请输入身份证号:") if account.id != aid: print("重新输入") else: break # 余额清零 account.card.money = 0 #从字典中移除 self.account_dict.pop(cid) print("注销成功") #退出 def exit(self): #需要把银行的所有信息存起来 #退出了操作系统 -- 之前的数据更改 得在本地也得进行更新吧 with open("account.txt", "wb") as handle: pickle.dump(self.account_dict, handle)
#main.py from bank_pack.bank import Bank if __name__ == '__main__': print("欢迎使用小沐银行操作系统") bank = Bank() while True: print("1.开户 2.查询 3.存款 4.取款 5.转账 6.修改密码 7.锁卡 8.解卡 9.销户 0.退出") num = int(input("请输入需要的操作序号:")) if num == 1: bank.create_account() elif num == 2: bank.search_balance() elif num == 3: bank.save_money() elif num == 4: bank.get_money() elif num == 5: bank.transform() elif num == 6: bank.update_password() elif num == 7: bank.lock_card() elif num == 8: bank.unlock_card() elif num == 9: bank.kill_account() elif num == 0: bank.exit() break
听写
'''根据描述设计类: 包含知识点: 限制对象随意增加属性 输出对象时是按照自己设计的特定格式显示对象信息 __slots__ = () __str__ __repr__ 员工类: 特征: 姓名 性别 薪资 所属部门 公司类: 特征: 名称 员工列表 行为: a.根据员工薪资进行升序排序 b.根据部门对员工进行分类''' class Employee: __slots__ = ("name", "sex", "salary", "depart") def __init__(self,name, sex, salary, depart): self.name = name self.sex = sex self.salary = salary self.depart = depart def __str__(self): return "Employee name:{} sex:{} salary:{} depart:{}".format(self.name, self.sex, self.salary, self.depart) __repr__ = __str__ class Company: __slots__ = ("name", "em_list") def __init__(self, name, em_list): self.name = name self.em_list = em_list #存放的数据是员工对象 def sort_by_salary(self): #遍历列表 #for ele in self.em_list: #print(ele.salary) self.em_list.sort(key = lambda ele : ele.salary) print(self.em_list) def classes_by_depart(self): ''' 根据部门进行分类 --- 使用字典数据显示 键:部门 值:容器存放所有的员工对象 [] 字符串中字符出现的次数 第一次出现 1 之后再出现 +1 键: 字符 值: 数值 遍历列表 部门 第一次出现 值:[ele] 之后再出现 值在原有的基础上append ''' depart_dict = {} for ele in self.em_list: if ele.depart not in depart_dict: depart_dict[ele.depart] = [ele] else: depart_dict[ele.depart].append(ele) print(depart_dict) def __str__(self): return "Company name:{} em_list:{}".format(self.name, self.em_list) __repr__ = __str__
50. 反射机制
通过字符串格式的字段 获得该字段对应的数据 需求: 模块 功能实现: 求和 求差 求乘 求除 main模块中 用户输入工具模块中 功能的名字 获取对应功能 进而去执行功能
#from reflect_pack.tool import * import reflect_pack.tool as rt if __name__ == '__main__': func_name = input("请输入要执行的功能的名字:") #找到tool模块下对应的功能 去执行该功能 print(type(func_name)) # func = eval(func_name) # print(func) # res = func(1, 3) # print(res) ''' 第一个参数: 字段的位置 --- 可以设置模块 也可以设置对象 第二个参数: 要获取的字段的字符串格式 ''' #在对象或者模块中获得字符串格式的字端对应的内容 # func = getattr(rt, func_name) # print(func) #判断在指定的模块/对象中是否有要获取的字段 # res = hasattr(rt, func_name) # print(res) if hasattr(rt, func_name): func = getattr(rt, func_name) res = func(1, 5) print(res) else: print("tool模块下还没有改功能") class_name = input("请输入要获取的类型的名字:") if hasattr(rt, class_name): person_class = getattr(rt, class_name) print(person_class) #根据获取的类型创建对象 person_obj = person_class("小明", 10) print(person_obj) #根据对象字符串格式的特征名 获取对应的特征值 # name age ---> #print(person_obj.name) attribute_name = input("请输入要获取的特征的名字:") if hasattr(person_obj, attribute_name): value = getattr(person_obj, attribute_name) print(value) #设置模块/对象中某个字段的值 setattr(person_obj, attribute_name, "小小明") value = getattr(person_obj, attribute_name) print(value) print(person_obj) #删除模块/对象中的指定的内容 # delattr(person_obj, attribute_name) # print(person_obj)
51. 单例设计模式
单例: singleton 使用场景: 遍布整个应用程序, 某个类的对象只需要1个即可完成所有动作的实现, 就可以将这个类设计单例类. 设计为单例类之后, 使用其对象时 只能使用1个, 并且有且仅有1个
1.借助天然单例 模块 --- 实现单例的操作 --- 需要掩耳盗铃 2.借助于装饰器的操作 --- 设计被装饰的类只会创建1次对象 3.借助于类本身 --- 设计类方法 通过类方法将该类唯一的对象应用于整个应用程序 --- 需要掩耳盗铃 4.借助于类方法 __new__ --- 让这个方法中只创建1次对象 2和4 是最常用的
1.借助于天然单例 --- 模块 模块: 模块在使用的时候 需要被导入的 也就是将模块中的内容加载到内存中 导入的时候有个规则: 第一次被导入的时候 解释器会根据指定模块生成对应的一个描述文件.pyc文件 之后再导入, 直接使用的就是.pyc文件中的内容 [模块中的内容只是在第一次的时候被加载了, 将数据存放于内存中 之后的导入是直接使用内存中的数据]
#单例模块 class King: def __init__(self, name, age): self.name = name self.age = age #在当前模块中创建国王对象 讲对象的地址交给指定的变量 外界再使用这个对象的时候 直接对应的变量获取对象即可 king = King("拿破仑", 30) print("single_model---",king) #使用单例模块 import singleton_pack.singleton_model as single o_obj = single.king print("other_model----",o_obj) #入口模块 from singleton_pack.singleton_model import * import singleton_pack.other_model if __name__ == '__main__': obj = king print("main----",obj) #结果: ''' single_model--- <singleton_pack.singleton_model.King object at 0x000002E842198DD8> #模块中的内容只有第一次导入的时候执行 other_model---- <singleton_pack.singleton_model.King object at 0x000002E842198DD8> main---- <singleton_pack.singleton_model.King object at 0x000002E842198DD8> '''
2.借助于装饰器 装饰器的作用:在不修改原有功能的基础上 为此增加额外的功能 要装饰的是类 --- 这个类的对象只会被创建一次 之后再创建的时候 获取的是第一次创建的对象的地址 ----> 借助于一个知识点: 是装饰器的外部函数只会被执行1次 装饰器函数的格式: 函数嵌套的 def outer(被装饰的功能/类): def inner(*values, **kwargs): 增加对应的装饰 完成原本功能的实现 return 原本功能的结果 return inner
def single_class(cls): instance = None def wrapper(*values, **kwargs): nonlocal instance if instance == None: #创建cls对应的对象 instance = cls(*values, **kwargs) #要求对象只被创建1次 也就是这行代码只能被执行1次 return instance return wrapper @single_class class King: def __init__(self, name, age): self.name = name self.age = age ''' @single_class 这个行为的时候 外部函数就已经被执行了 --- 原有的功能名不改变的规则 使用原有的功能名接受外部函数执行的结果 King = single_class(King) ====> King <====> 内部函数了 再执行King(a,b) ===> 其实执行的内部函数 ''' king = King("拿破仑", 30) print(king) king1 = King("拿破仑", 30) print(king1)
练习: 乾隆皇帝弘历带着大臣和珅和纪晓岚游玩 名词提取对象 根据对象的描述生成类 --- 应该把皇帝类设计为单例类 对象: 弘历 和珅 纪晓岚 类: 皇帝类 特征: name 年号 行为: 游玩(两个大臣) 大臣类 特征: 名字
def singe_class(cls): res = None def inner(*values, **kwargs): nonlocal res if res is None: res = cls(*values, **kwargs) return res return inner @singe_class class King: def __init__(self,name, year_name): self.name = name self.year_name = year_name def play(self, minister1, minister2): print("{}皇帝{}带着大臣{}和{}游玩".format(self.year_name, self.name, minister1.name, minister2.name)) class Minister: def __init__(self, name): self.name = name def __str__(self): return "{}".format(self.name) __repr__ = __str__ if __name__ == '__main__': m1 = Minister("和珅") m2 = Minister("纪晓岚") #皇帝 king = King("弘历", "乾隆") king1 = King("弘历", "乾隆") king2 = King("弘历", "乾隆") king.play(m1, m2) print(king) print(king1) print(king2) #修改对象的特征值: king.name = "爱新觉罗.弘历" king2.play(m1, m2)
class King: def __init__(self, name, age): self.name = name self.age = age instance = None #类属性 如果赋值之后 不再修改值 会一直拥有该值 @classmethod def get_instance(cls, name, age): if cls.instance == None: cls.instance = cls(name, age) return cls.instance #外界想获取对象 --- 通过类方法获取 if __name__ == '__main__': k = King.get_instance("乾隆", 40) print(k) k1 = King.get_instance("乾隆", 40) print(k1) k2 = King.get_instance("乾隆", 40) print(k2) # King()
4.借助于系统提供的类方法 __new__ 作用: 创建对象 这个方法需要返回一个对象 需求: 只需要一个对象 ----> 这个方法如果创建过对象 就一直使用对象的地址即可
class King: def __init__(self, name, age): self.name = name self.age = age instance = None @classmethod def __new__(cls, *args, **kwargs): if cls.instance == None: cls.instance = object.__new__(cls) print("__new__把对象创建",cls.instance) return cls.instance if __name__ == '__main__': k = King("A", 10) print(k) k2 = King("B", 20) print(k2)
52. 枚举类型
如果一个类的对象的个数有限, 且对象的值是固定的 这种情况下 这个类型就可以设计为枚举类型 如果想让一个类称为枚举类 需要继承Enum 或者 IntEnum类 需要导入一个模块 enum 有需要的三个内容: Enum类 IntEnum类 和装饰器 unique IntEnum这个类型对应的对象的数据必须是可以转化为整型的才可以 装饰unique ---> 保证枚举类型中每个对象的数据都是唯一的 声明枚举类型的方式 class 枚举类型的名称(Eunm/IntEnum): 对象名称 = 对象对应的数据 对象名称 = 对象对应的数据 注意: 对象名称需要满足表示符规范 书写格式为每个字母都是大写的 单词和单词之间使用下划线隔开 枚举类型对象的值是一个常量 --- 确定的数据 如何获取每个对象对应的数据值: 1.获得每个枚举类型的对象: 类名.对象名 2.获得每个枚举类型的对象对应的值: 类名.对象名.value
描述季节的类型: from enum import Enum, IntEnum, unique @unique class Season(Enum): SPRING = "春季" SUMMER = "夏季" AUTUMN = "秋季" WINTER = "冬季" # WINTER1 = "冬季" print(Season.SPRING) print(Season.SPRING.value) class Week(IntEnum): MON = 1 TUE = 2 WED = 3 THU = "4" # FRI = "星期五" FRI = 5 SAT = 6 SUN = 7 print(Week.SUN.value)
练习: 颜色: red green blue ---> 这三个色系调和而成的 每一个字段对应的数值是0-255 列出红色颜色: (255, 0, 0) 列出绿色 (0, 255, 0) 蓝色 (0, 0, 255) 黑色 (0, 0, 0) 白色 (255, 255, 255) 灰色系 (122, 122, 122) 三个值一样 调出的是灰色系 声明一个枚举类型:继承自Enum 列举出 红 绿 蓝 黑 白 色系 设置一个静态方法 ----> 功能是随机获取一种颜色
import random class Color(Enum): RED = (255, 0, 0) GREEN = (0, 255, 0) BLUE = (0, 0, 255) BLACK = (0, 0, 0) WHITE = (255, 255, 255) @staticmethod def random_color(): r = random.randint(0, 255) g = random.randint(0, 255) b = random.randint(0, 255) return (r, g, b) print(Color.random_color())
三方模块: 不是系统提供的 也不是自己自定义 使用别人写好的 想在程序中使用需要进行安装 安装格式: pip install 三方模块名 图像/游戏操作进行的模块 pygame pip install pygame ----> 在命令提示符控制台 或者 项目中 terminal
import pygame from enum import IntEnum, unique from pygame_pack.game import Color import random #方向 上下左右 @unique class Direction(IntEnum): UP = 0 RIGHT = 1 DOWN = 2 LEFT = 3 #墙类 x y width length color class Wall: def __init__(self,x, y, width, height, color): self.x = x self.y = y self.width = width self.height = height self.color = color #画的行为 def draw(self, screen): pygame.draw.rect(screen,self.color,(self.x, self.y, self.width, self.height), 4) #食物 class Food: def __init__(self, x, y, size, color): self.x = x self.y = y self.size = size self.color = color #是否隐藏 被吃了要隐藏 self.hidden = False def draw(self, screen): if not self.hidden: pygame.draw.circle(screen,self.color,(self.x + self.size//2, self.y + self.size // 2), self.size // 2) #蛇的节点 class SnakeNode: def __init__(self, x, y, size, color=Color.GREEN.value): self.x = x self.y = y self.size = size self.color = color # 画的行为 def draw(self, screen): pygame.draw.rect(screen, self.color, (self.x, self.y, self.size, self.size)) pygame.draw.rect(screen, Color.BLACK.value, (self.x, self.y, self.size, self.size), 1) #蛇 class Snake: ''' 蛇: 起始坐标 蛇节点的大小 蛇初始具有多少个节点 蛇初始移动的默认方向 蛇新的方向 蛇的存活状态 一开始的是活着的 ''' def __init__(self, x, y, length = 5, size = 20): self.dir = Direction.LEFT.value #默认方向 #新的方向 self.newdir = None self.isalive = True #存放节数的容器 self.nodes = [] #因为初始有长度 for i in range(length): node = SnakeNode(x + i * size, y, size) self.nodes.append(node) #蛇的行为: ''' 移动 切换方向 撞墙 吃食物 吃自己 ''' #切换方向: def change_dir(self, new_dir): #同一轴的方向无法切换 if new_dir == self.dir or (self.dir + new_dir) % 2 == 0: print("同一轴不能切换方向") else: self.newdir = new_dir #新的方向就有数据了 #获得头 @property def head(self): return self.nodes[0] #移动 def move(self): #先判断是否切换方向了 if self.newdir != None: #切换方向 当前的方向就发生变化 变成新的方向值 self.dir = self.newdir self.newdir = None #等待接受新的方向 #方向发生变化 坐标点要发生变化 #先要记录原来的数据的坐标点 x, y ,size= self.head.x, self.head.y, self.head.size if self.dir == Direction.UP.value: y -= size elif self.dir == Direction.LEFT.value: x -= size elif self.dir == Direction.DOWN.value: y += size elif self.dir == Direction.RIGHT.value: x += size #会有新的头的坐标 new_head = SnakeNode(x, y, size) self.nodes.insert(0, new_head) #去掉旧尾 self.nodes.pop() #装个墙 def collide_wall(self, wall): #边界判断 if self.head.x <= wall.x or self.head.x + self.head.size >= wall.x + wall.width \ or self.head.y <= wall.y or self.head.y + self.head.size >= wall.y + wall.height: #蛇撞到抢了 self.isalive = False #吃食物 def eat_food(self, food): ''' 什么情况下是吃到食物了 ''' if self.head.x == food.x and self.head.y == food.y: #蛇的长度等增加了 #先把蛇尾取出 tail = self.nodes[-1] #在添加一次 self.nodes.append(tail) #食物隐藏 food.hidden = True return True return False #关于食物新生成 #吃自己 def eat_self(self): for index in range(5, len(self.nodes)): node = self.nodes[index] if self.head.x == node.x and self.head.y == node.y: self.isalive = False #吃到自己 #画蛇 def draw(self, screen): for node in self.nodes: node.draw(screen) if __name__ == '__main__': def create_food(): f_size = snake.head.size #因为食物位置随机出现 row = wall.height // f_size col = wall.width // f_size random_row = random.randint(0, row-1) random_col = random.randint(0, col-1) food = Food(wall.x + random_col * f_size , wall.y + random_row * f_size, f_size, Color.RED.value) return food pygame.init() #设置游戏屏幕大小 game_screen = pygame.display.set_mode((800, 800)) pygame.display.set_caption("贪吃蛇") #游戏围墙 wall = Wall(10, 10, 780, 780, Color.BLACK.value) #创建蛇 snake = Snake(310, 310) food = create_food() isRunning = True while isRunning: #背景颜色 game_screen.fill(Color.WHITE.value) wall.draw(game_screen) snake.draw(game_screen) food.draw(game_screen) pygame.display.flip() #设置蛇移动的帧数 pygame.time.Clock().tick(5) #设置蛇的行为 if snake.isalive: snake.move() snake.collide_wall(wall) if snake.eat_food(food): food = create_food() snake.eat_self() for event in pygame.event.get(): if event.type == pygame.QUIT: isRunning = False elif event.type == pygame.KEYDOWN: #获得键盘按下的键的符号 key = event.key #蛇要切换方向 if snake.isalive: if key == pygame.K_w: new_dir = Direction.UP.value elif key == pygame.K_a: new_dir = Direction.LEFT.value elif key == pygame.K_s: new_dir = Direction.DOWN.value elif key == pygame.K_d: new_dir = Direction.RIGHT.value #蛇更改方向 snake.change_dir(new_dir)
53. 异常机制
异常 --- 不正常 在运行程序的过程中出现的 --- 导致程序异常中断 indexerror 写程序 --- 肯定是要避免异常的出现 Python就提供了处理异常的机制: 捕获异常 抛出异常 ----> 一般与自定义异常类型结合使用 Python中处理异常的运行机制: Python解释器运行程序时 如果在运行的程序中发现了异常情况, 会先生成对应的该异常类型的对象, 检测该异常周围是否有处理异常的操作, 有处理异常的操作 直接跳转到处理异常的行为处. 如果没有Python解释器会直接将该异常对象抛出, 捕获这个异常对象, 输出对应的异常信息, 使程序异常终止
想让程序不异常终止, 需要在异常周围有处理异常的操作 ----> 处理异常的操作只是告诫程序员该出出现了问题, 及时解决问题 ----> 并不是异常机制自动解决问题 处理异常的操作 ---- 捕获异常: 格式1: try: 可能会出现异常的代码 except 对应的异常类型 as 变量名: #出现异常的时候 抛出的是异常对象 需要有指定异常类型的变量进行接收该对象 处理异常 --- 告诫程序员出现了指定异常 请及时解决 except 对应的异常类型2 as 变量名: 处理异常 ... except 对应的异常类型n as 变量名: 处理异常
try: 可能会出现异常的代码 except 对应的异常类型 as 变量名: #出现异常的时候 抛出的是异常对象 需要有指定异常类型的变量进行接收该对象 处理异常 --- 告诫程序员出现了指定异常 请及时解决 except 对应的异常类型2 as 变量名: 处理异常 ... except 对应的异常类型n as 变量名: 处理异常 else: try快中没有任何异常 就会执行else
try: 可能会出现异常的代码 except 对应的异常类型 as 变量名: #出现异常的时候 抛出的是异常对象 需要有指定异常类型的变量进行接收该对象 处理异常 --- 告诫程序员出现了指定异常 请及时解决 except 对应的异常类型2 as 变量名: 处理异常 ... except 对应的异常类型n as 变量名: 处理异常 finally: 无论try快中有没有异常都会执行finally 常见使用模式: 程序与外界资源关联 获取外界资源的数据 关闭通道的操作 不管处理中是否出现异常 都需要将通道关闭
执行流程: 如果try块中有异常 try块中的代码停止继续向下执行, 去执行对应的异常的except块,如果没有异常执行else
异常的父类: Exception 如果在捕获异常的时候不知道发生了什么异常可以使用Exception这种异常类型接受, 他可以接受所有的异常对象
''' 练习: 用户输入一个数据 将其转化为整数 如果转换失败 让用户重新输入 直到输入正确为止 输入错误的时候提示 输入格式不正确 请重新输入 输入错误 程序不能异常结束 ''' while True: try: num = int(input("请输入一个整数:")) print(num) except ValueError as e: print("输入数据错误, 请重新输入", e) else: print("输入没有错误") break
try: list0 = [15, 71, 28, 99] ''' 索引范围: [0, 3] 或者 [-4, -1] ''' value = list0[-4] # IndexError: list index out of range print(value) print("结束") # int() 转化字符串的时候有要求的: 字符串的内容必须满足整数数据的格式 value = int("10") # ValueError print(value) # except IndexError as e: # print("索引异常:",e) #e接受的是异常对象 # #当打印对象的时候 打印的不是地址 而是指定的内容数据 --- 重写了系统方法__str__ # #e = IndexError() print(e) # except ValueError as e: # print("转换异常:",e) except Exception as e: print("异常的出现:", e) else: print("没有异常才会执行") class Person: def __init__(self, name): self.name = name def __str__(self): return "Person name:{}".format(self.name) p = Person("小明") print(p)
''' 程序与文件进行数据传递 ----> 由于程序时访问外界资源 访问结束之后需要进行手动关闭 如果不进行手动关闭 产生垃圾 程序无法处理 ''' #1. 打开指定文件 并获得操作文件的手柄 ''' 因为模式是r --- 要求文件路径必须存在 ''' # handle = open("excpetion_demo.py", "r", encoding="utf-8") # # #进行读取 # lines = handle.readlines() # print(lines) # print(len(lines)) # print(lines[32]) # # #关闭通道 # handle.close() handle = None try: #打开文件出错 就不能获取操作文件的手柄 也就意味着handle的值没有被重新赋予 handle = open("exception_demo.py", "r", encoding="utf-8") except Exception as e: print("异常信息:", e) finally: print("finally") if handle != None: handle.close()
抛出异常: 如果不手动抛出, 是由Python解释器抛出的 手动抛出如何抛出: 注意: 抛出的是异常对象 在存在异常的代码下方书写: raise 异常类型(异常描述信息) 如果代码没有问题 遇到raise python解释器也不会向下继续执行 而是直接将异常抛出 有异常的抛出 就得有异常的捕获 如果没有捕获 程序会异常中断 捕获的形式: 1. 可以抛出异常的周围 直接将此异常捕获 def div1(a, b): try: if b == 0: raise ZeroDivisionError("除数为0") except ZeroDivisionError as e: print(e) else: return a / b 2.不在异常周围捕获 #不在周围捕获 --- 表示这个函数被调用是可能会有异常的出现, 处理方式:哪里调用哪里进行捕获 def div2(a, b): if b == 0: raise ZeroDivisionError("除数为0") return a / b try: res = div2(10, 0) except ZeroDivisionError as e: print(e) else: print(res) 如果一个函数存在异常, 并在函数中没有做异常处理, 当调用函数时 哪里调用哪里就会有对应异常出现, 程序员需要最少在捕获异常的最后时机处 处理异常 否则该异常有Python解释器处理 会导致程序异常终止 def div3(a, b): if b == 0: raise ZeroDivisionError("除数为0") return a / b #这里的调用函数是程序员最后可以捕获异常的时机 #res = div3(10, 0) #如果在程序员最后捕获异常的时机处没有做出捕获的处理 将由解释器对其进行捕获 def div4(a, b): value = div3(a, b) return value try: res = div4(10, 0) except ZeroDivisionError as e: print(e) else: print(res)
自定义异常类型: 普通的类型是不能被抛出和捕获的, 需要继承自Exception 该类对象才可以被抛出和捕获 创建了一个人类: Person: name age 可以随意赋值 --- 为了避免随意赋值 ---> 封装 ---> 提供了赋值方式 ---> 在set方法中做出对应的数据判断 if age < 0: #self.__age = 0 出现了异常 抛出异常 人类年龄不能为负数异常 --- 自己进行自定义 else: self.__age = age sex p = Person("小明", -20, "男") 一个有-20年龄的人类对象在现实生活中是不该存在的 p应该获取的数据是None print(p) ===> None 自定义的格式: class 自定义的类型名字(Exception): def __init__(self, message): self.__message = message def __str__(self): return "{}".format(self.__message) __repr__ = __str__ 自定义类型对应的异常 Python解释器无法抛出指定的异常对象 需要手动进行抛出
class AgeNevigateError(Exception): def __init__(self, message): self.__message = message def __str__(self): return "{}".format(self.__message) __repr__ = __str__
from exception_pack.cus_exception.agenevigateexception import AgeNevigateError class Person: def __init__(self, name, age, sex): self.name = name #age self.set_age(age) #sex self.set_sex(sex) #age def set_age(self, age): if age < 0: #self.__age = 0 #是一个异常 需要抛出异常 raise AgeNevigateError("年龄为负数异常") else: self.__age = age def get_age(self): return self.__age #sex def set_sex(self, sex): if sex in ("男", "女"): self.__sex = sex else: self.__sex = "男" def get_sex(self): return self.__sex
from exception_pack.cus_exception.person import Person if __name__ == '__main__': p = None try: p = Person("小明", -20, "男") #在try块中有独立的作用范围 except Exception as e: print(e) print(p)
54. 正则表达式
什么叫做正则表达式?? 正则表达式其实就是特殊的字符串, 帮助进行检索, 校验, 查询等行为 "hello" -- 就是一个正则表达式 --- 验证字符串内容是否是hello regular expression
正则表达式使用场景: 验证的作用: 密码的验证 用户名的验证 邮箱 手机号等等 爬虫: 查询校验
正则表达式的规则: 正则表达式处理字符串的强大工具, 拥有自己独特的语法, 并且拥有一个独立处理正则表达式的引擎. 正则表达式处理字符串的效率要比系统本身提供的低, 如果系统的能够完成, 就不用正则表达式 替换 ---- 将字符串中b小写字母 替换成B --- 系统的能完成直接使用系统的即可 替换 --- 将第一个替换成B 第二个替换成 m --- 系统不能完成 使用正则表达式
Python提供的正则表达式机制: 需要导入模块 re
在re模块下常用的方法: 1. compile(正则表达式的语法) 根据正则表达式语法生成对应正则表达式对象 2. 正则表达式对象.match(要进行验证的字符串) 如果正则表达式语法中没有限制头尾, 验证字符串是否以正则表达式对应字符串开头 如果限制了头尾, 也就意味限制了字符串的长度, 验证的字符串内容是否满足正则表达式的需求 如果满足 返回的match对象 不满足返回的是None 3.正则表达式对象.search(待查找的字符串) 在待查找的字符串中 查找是否有正则表达式对应的字符串内容 如果有获取查找到的第一个内容 以及索引区间 找到的话返回的Match对象 找不到返回None 4. 正则表达式对象.findall(待查找的字符串) 在待查找的字符串中 查找是否有正则表达式对应的字符串内容 将所有满足需求的字符串内容存放于列表
import re #1. 根据正则表达式语法 获取其对应的正则表达式对象 pattern_obj = re.compile("hello") #match 匹配的方法 res = pattern_obj.match("hellogoodstudy") print(res) #<_sre.SRE_Match object; span=(0, 5), match='hello'> match表示的是匹配到的内容 span表示的是匹配到的内容的范围区间 前闭后开 #获取匹配到的区间 span = res.span() print(span) #获得匹配到的内容 content = res.group() print(content) #限制开头和结尾 --- 在程序中做验证 肯定限制了头和尾 -- 使表达式长度固定下来了 ''' ^x ----> 表示验证的字符串必须以指定的字符开头 x$ ----> 表示正则表达式必须以指定字符结尾 ''' # pattern_obj = re.compile("^hello$") #待验证的字符串必须是hello # # #match 匹配的方法 # res = pattern_obj.match("hellogood") # print(res) pattern_obj = re.compile("dog") res = pattern_obj.search("ihaveadog,thedoghasablackskin") print(res) s = "abc12defg34opq67" ''' 正则表达式语法: [0-9] --- 表示的是0-9中任意一个数字字符 x+ ----> 表示的是前面的字符至少连续出现1次 ''' pattern_obj = re.compile("[0-9]+") res = pattern_obj.findall(s) print(res) value = "+".join(res) print(eval(value))
正则表达式的语法: 1.匹配单个字符的语法: a. 通配符. [英文情况下的句号] 匹配除了\n之外的任意一个字符 注意: 在正则表达式中 .具有通配符的含义, 但是在正常的语句中 英文符号下的句号 需求: 仅匹配字符串中 英文符号下的句号 . --- 这个正则表达式怎么写?? \. ---> 对.进行转义 b.设置范围 -- 在指定的范围中获取其中的任意一个字符 使用中括号来设置范围的 [] i. 获取的字符不是连续的 需要一一列举 [amo] ii. 字符是连续的 将起始字符与最终字符使用横线进行连接 [0-9] -- 数字的字符中的任意一个 [0-5] [5-9] [a-z] --- 小写英文字母中的任意一个 [A-Z] ---- 大写英文字母中的任意一个 [a-zA-Z] --- 所有的英文字母中任意一个 [0-9a-zA-Z] ---英文字母和数字中的任意一个字符 "0" iii.对范围取反 [^amo] --- 除了amo之外的任意一个字符 c. 一些被转义的字母具有的含义 \d =====> [0-9] ----> 数字字符中的任意一个 \D =====> [^0-9] -----> 除了数字字符之外的任意一个 \s =====> 任意一种空白字符 空格 \n \t \r \S =====> 匹配的是非空白字符中的任意一个 \w =====> 匹配的是数字 字母 下滑线 汉字中的任意一个字符 \W ======> 非数字 字母 下滑线 汉字中的任意一个字符 需求: \d 匹配的是数字中的任意一个字符 就想让\d匹配\d ----> 对其进行转义 \\d 在程序中 \\ == \ 要想转义一个斜杠 本质上其实是转义两个斜杠 \ ===> \\ \\ ===> \\\\ 向获取\d本意的话 需要 \\\\d 为了避免书写麻烦 借助r辅助\表示是使用的是其本意 转义的时候 \\d 建议: 以后写正则表达式时 前面添加r window下目录分割符 \ ====> 想在程序的字符串中显示的话 \\
import re #通配符. pattern_obj = re.compile(".") res = pattern_obj.match("\nbc") print(res) #想使用其本意 需要进行转义 pattern_obj = re.compile("\.") res = pattern_obj.match(".bc") print(res) pattern_obj = re.compile("[amo]") res = pattern_obj.match("zbc") print(res) pattern_obj = re.compile("[a-zA-Z0-9]") res = pattern_obj.match("0bc") print(res) pattern_obj = re.compile("[^a-zA-Z0-9]") res = pattern_obj.match("\nbc") print(res) pattern_obj = re.compile("\d") res = pattern_obj.match("6bc") print(res) pattern_obj = re.compile("\D") res = pattern_obj.match("*bc") print(res) pattern_obj = re.compile("\s") res = pattern_obj.match("abc") print(res) pattern_obj = re.compile("\S") res = pattern_obj.match("\nbc") print(res) pattern_obj = re.compile("\w") res = pattern_obj.match("*bc") print(res) pattern_obj = re.compile("\W") res = pattern_obj.match("9bc") print(res) pattern_obj = re.compile("\\d") res = pattern_obj.match("0bc") print(res) path = r"c:\test\a.txt" print(path) #c:\test\a.txt #程序中 \\ === \ ''' \d ===> \\d ===> 要想转义的话 本质上需要转义两个斜杠 一个斜杠转义 \\ 两个斜杠转义 \\\\ ''' pattern_obj = re.compile("\\\\d") res = pattern_obj.match("\dbc") print(res) pattern_obj = re.compile(r"\\d") res = pattern_obj.match("\dbc") print(res)
2.关于正则表达式的开头和结尾的设置 设置了开头和结尾时候 --- 意味着要进行匹配的字符串的长度已经确定了 --- 验证待验证的字符串是否符合正则表达的规则 "^x" --- 以指定符号x开头 "x$" --- 以指定符号x结尾
pattern_obj = re.compile(r"^[0-9].[a-zA-Z]$") res = pattern_obj.match("0app") print(res)
3.关于出现字符数量的匹配 x{m,n} ----> 表示的前面的字符x最少连续出现m次 最多连续出现n次 x{m,} ----> 表示的前面的字符x最少连续出现m次 x{m} ----> 表示的前面的字符x必须连续出现m次 x* ----> 表示的前面的字符x可以连续出现任意次 [可有可无] x+ -----> 表示的前面的字符x可以连续出现最少1次 x? ------> 表示的前面的字符x可以连续出现最多1次 * 和 + 可以向更多匹配 ----> 管这种匹配模式成为贪婪模式 限制贪婪: 借助? *? +?
#密码长度 8-16位 pattern_obj = re.compile(r"^[0-9].{6,14}[a-zA-Z]$") res = pattern_obj.match("0abcdefppoklwerjm") print(res) pattern_obj = re.compile(r"^[0-9].{6,}[a-zA-Z]$") res = pattern_obj.match("1ansdfkpdfghjklvbnm,vb") print(res) pattern_obj = re.compile(r"^[0-9].{6}[a-zA-Z]$") res = pattern_obj.match("9hsjdke") print(res) #贪婪匹配 ''' <div>可以有任意内容</div> ''' pattern_obj = re.compile(r"<div>.+?</div>") res = pattern_obj.match("<div>这是一个块级标签</div></div></div>") print(res)
4.分组匹配 使用小括号包含一组内容 如果想匹配的话 必须与组内内容一致才可以匹配成功 (qq) ---> 匹配的内容必须是qq 组内的内容可能不是一个 这些内容之间是或者 的关系 使用|进行连接 (qq|sina|163|126) (com|cn|net) \num模式 前面的正则表达式中有一个分组 验证匹配数据 后面也有一个分组 验证匹配数据 ---> 后面匹配到的内容与前面那个分组匹配的内容是一模一样的 这个时候后面这个分组就可以使用\num模式站位 num标签的前面第几个分组 从1开始 标签:" 成对出现的 <html> <p>内容</p> </html>
#匹配分组 ''' 匹配邮箱 用户名@qq/sina/163/126.com/cn/net 验证qq邮箱 用户名@qq.com 用户名 都是数字 [1-9][0-9]{4,10}@(qq)\.(com) ''' pattern_obj = re.compile(r"^[1-9][0-9]{4,10}@(qq|sina|163|126)\.(com|cn|net)$") res = pattern_obj.match("12345@sina.cn") print(res)
#\num模式: pattern_obj = re.compile(r"<([a-z]+)>.*?</\1>") res = pattern_obj.match("<html>呵呵呵</html>") print(res) ''' <div> <p></p> </div> ''' pattern_obj = re.compile(r"<([a-z]+)><([a-z]+)>.*?</\2></\1>") res = pattern_obj.match("<html><p>呵呵呵</p></html>") print(res)
练习: 1.验证18位的身份证号 第一位是1-9中的任意一个 23456位时0-9中的任意一个 年份 1900 - 2200年 (19[0-9]{2} | 2[0-1][0-9]{2} | 2200) 月份 01 -- 12 (0[1-9]|1[0-2]) 日期 01 - 31 (0[1-9]|[1-2][0-9]|3[0-1]) 后面三位数 数字随意 [0-9]{3} 最后一位 x X 数字 [xX0-9] ^[1-9][0-9]{5}(19[0-9]{2}|2[0-1][0-9]{2}|2200)(0[1-9]|1[0-2])(0[1-9]|[1-2][0-9]|3[0-1])[0-9]{3}[xX0-9]$ 2. 验证IP地址 0.0.0.0 ~ 255.255.255.255 每一位上: 0-9 10-99 [1-9][0-9] 100-199 1[0-9]{2} 200 - 249 2[0-4][0-9] 250-255 25[0-5] "^([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"
5.边界处理符: \b --- 边界 --- 查找单词 包含起始位置 结束位置 以空格或者其他符号分割的 明确单词的内容 m[a-z]{2}n \bx --> 以x为左边界 x\b ---> 以x为右边界 \B --- 非边界 \Bx ---> x不是内容的左边界 x\B ---> x不是内容的右边界 被包含在内容中
''' 需求 在下列的英文语句中查找单词 mean moon 以m和n为边界 中间带有两个其他字母的单词 ''' import re pattern_obj = re.compile(r"\bm[a-z]{2}n\b") word_list = pattern_obj.findall("mean, look like moodle moon min") print(word_list) pattern_obj = re.compile(r"\bm[a-z]{2}n\B") word_list = pattern_obj.findall("mean, look like miinle moon min") print(word_list)
6.常用方法补充: a.切割方法 split 字符串中也有原生的切割方法 s = "abcamkaob" ---> 以a为切割符 切割字符串 s = "love13you14too" ----> 以数字为切割符 切割字符串 --- 原生的无法解决了 ---- 正则表达式提供的切割方法
#切割字符串 s = "abcamkaob" #以"a"为切割符 sub_list = s.split("a") print(sub_list) s = "love13you14too" #以数字为切割符 pattern_obj = re.compile(r"[0-9]+") sub_list = pattern_obj.split(s) print(sub_list) #以切割的形式 获取上述字符串中的数值字符序列 13 14 pattern_obj = re.compile(r"[a-zA-Z]+") sub_list = pattern_obj.split(s) print(sub_list) sub_list = list(filter(lambda ele: len(ele)!=0, sub_list)) print(sub_list) res = "+".join(sub_list) print(eval(res)) s = "love13.14you14too2good0.14day+--31" #提取字符串中数值 包含整数和小数 包含符号 ''' 整数 小数 带有+-号的数据[数值] ''' pattern_obj = re.compile(r"[a-zA-Z]+") sub_list = pattern_obj.split(s) print(sub_list) sub_list = list(filter(lambda ele: len(ele)!=0, sub_list)) print(sub_list) res = "+".join(sub_list) print(res) print(eval(res)) print(round(29.28-31,2)) ''' 整数[+-]*[1-9][0-9]* 小数[+-]*[1-9][0-9]*\.?[0-9]* 带有+-号的数据[数值] [+-]*[1-9][0-9]*\.?[0-9]* findall 更好找 ''' s = "love13.14you14too2good0.14day+-#-31" pattern_obj = re.compile(r"([+-]*[1-9][0-9]*\.?[0-9]*|[+-]*[0-9]+\.?[0-9]*)") res = pattern_obj.findall(s) print(res) res = "+".join(res) print(eval(res))
7.方法补充: 查找替换 字符串原生的方法中: replace(旧内容, 新内容, 替换的数量) 正则表达式中提供的替换的方法: sub s = "我们班有40人, 5个女生, 35个男生" 要求: 将数字替换成0 s = "我们班有0人, 0个女生, 0个男生"
s = "我们班有40人, 5个女生, 35个男生" #将数值替换成0 new_s = s.replace("40", "0").replace("5", "0", 1).replace("35", "0") print(new_s) pattern_obj = re.compile(r"[0-9]+") ''' 第一个是替换成的新内容 第二个是被进行替换的字符串 第三个参数 是替换的个数 不设置默认全部替换 ''' res = pattern_obj.sub("0", s, 1) print(res) ''' 需求 将 40 ---> 四十 将5 ---> 五 将35 ----> 三十五 替换的内容已经不一样 --- 还是由sub解决 sub可以接受一个函数当做参数 函数的返回值为替换的新内容 函数有一个参数 ---> 接受的查找到的数据对应的match对象 函数的返回值就是要替换的新内容 ''' def replace(m_obj): print("函数中的:",m_obj) #从match对象中获取匹配到的内容 content = m_obj.group() if content == "40": return "四十" elif content == "5": return "五" elif content == "35": return "三十五" return "0" s = "我们班有40人, 5个女生, 35个男生" pattern_obj = re.compile(r"[0-9]+") res = pattern_obj.sub(replace, s) print(res)
55. 网络通信
1.计算机网络??? 多态独立的计算机, 通过网络设备连接起来 进行数据的传递和资源的共享
2.通信的时候通信的流程 --- 描述的具体的通信的过程 OSI模型来完成 --- 包含7层 发送端: 应用层 ---> 客户端的应用 数据传递时遵守的协议 http/https 表示层 ----> 对发送的信息进行解释和加密 会话层 ----> 与接收端之间建立了一个通道 传输层 -----> 指定了数据传递的协议[UDP/TCP] 和 端口号 -- [确定进行通信的应用程序] 网络层 ----> 封装了发送者/接受者的ip地址 [通过ip地址确定通信的计算机] 和 数据 数据链路层 ---> 将数据传递给下一层 进行再次封装 物理层 ----> 网络设备 接收端: 物理层 ----> 网络设备 --- 已经通过对应ip地址找到进行通信的另外一台计算机 数据链路层 ----> 将数据传递给下一层 进行解封 网络层 ----> 获得发送者的ip地址 和 数据 传输层 -----> 指定了数据传递的协议[UDP/TCP] 和 端口号 -- [确定进行通信的应用程序] 会话层 ----> 与发送端之间建立了一个通道 表示层 ----> 对接受的信息进行解释和解密 应用层 ---> 客户端的应用 数据传递时遵守的协议 http/https ---> 显示传递过来的数据 A电脑 --- QQ ----> 发送端 ----> 你好 B电脑 --- QQ -----> 接收端 ----> 你好
3.网络通信中必须具备的三个要素: ip地址 端口号 数据传递的协议[通信协议] i.ip地址 它是一个32位的二进制数, 将这32位分成4份, 每份之间使用点进行分割, 每份是8位, 将这8位二进制数据转化十进制数 由于ip地址中没有负数, 8位二进制数可表示的十进制数据的个数256个 每一份的取值范围是0~255 IP地址的分类: ipv4 --- 32为的ip地址 现在常用的IP地址 ipv6 --- 128位的 ----> 应用的网段多 ip地址在通信过程中的作用: 用于标识在通信过程上的计算机, 每个独立的计算机拥有的ip地址是唯一的 ip地址中的特殊网段: 127.0.0.0 ~ 127.255.255.255 --- 用于标识自己使用的那台计算机 在自己的计算机上想使用自己计算机的ip地址 可以直接使用这个网络的IP地址 因为网段范围广 一般使用127.0.0.1表示的是本机地址 ii.端口号 是一个16位的二进制数, 也没有负数, 将其转换为十进制的话将由2^16个数据 --- 66536个数据 取值范围 0-65535 在网络通信中的作用: 确定进行通信的应用程序 注意: 1.相同的通信协议下 不同的应用程序之间的端口号是不一样的 2.尽量不要使用1024以下的端口号, 因为这些端口号已经被知名的企业或者协议占用或者备用 http ---> 80 smtp ---> 25 https ---> 143 iii.通信协议 协议: 规则 通信协议 ----> 指定的通信的规则 通信协议常用的有 udp协议 和 TCP协议 传输层协议 --- 数据传输的时候遵守的协议 UDP协议: 用户数据协议: User Datagram Protocol 特点: 面向无连接的[在进行通信的时候 通信双方不需要建立通信通道直接通信] 发送数据时 是采用数据包的形式发送的 发送数据的大小是受限制的: 64k之内 不安全和不可靠的协议 传递速率快 实例: 视频聊天 如果网络不好 --- 会出现数据丢失的情况 TCP协议: 传输控制协议: Transmission Control Protocol 特点: 面向连接的 [确保数据不会被丢失] 发送数据的时候是通过数据流的形式发送的 发送数据的大小是不受限制的, 如果发送大型数据 双方需要指定数据的格式 可靠的安全的协议 传递速率慢 显示生活中的实例: 打电话 http协议建立的tcp协议之上的 TCP连接的时候有一个三次握手的操作: 第一次: 发送者给接受者发送一个连接请求 第二次: 接受者接受到请求, 给发送者一个标记, 告知自己处于在线等待状态 第三次: 发送者接受到接受者回馈的在线标记, 给接受者也发送一个在线的标记
4.Python中提供的网络通信: 需要导入模块socket 网络通信也是基于socket的 socket ----> 插座 底座的意思 ----> 物理层的手柄 ---> 通过这个物理层手柄来进行通信中设备的查找和数据的传递 不管是发送者 和 接受者 双方各自都有一个socket 进行通信的时候是两个计算机上 两个应用程序之间的交流 通信的时候 --- 和另外一个应用程序之间的交流 ----> 当前应用程序而言 另外一个应用程序属于外界资源 ---> 所以通信完毕之后 建立的回话通道要进行关闭 try-except-finally
基于udp协议的socket通信
#发送端 import socket if __name__ == '__main__': send_scoket = None try: #1. 建立发送的socket 网络通信手柄 type=socket.SOCK_DGRAM 设置UDP通信协议 send_scoket = socket.socket(type=socket.SOCK_DGRAM) #发送数据 ''' 第一个数据: 要发送的数据 格式是字节类型的 第二个参数是: 元组类型的标识的地址 地址中包含两个内容 (接受者的ip地址, 进行通信的应用程序的端口号) ''' #发送者发送消息的时候 设置的ip地址 是接受者的 send_scoket.sendto("你好".encode(encoding="utf-8"), ("10.0.120.236", 10086)) except Exception as e: print(e) finally: if send_scoket != None: send_scoket.close() #接收端 import socket if __name__ == '__main__': receive_socket = None try: #建立一个通信的底座 receive_socket = socket.socket(type=socket.SOCK_DGRAM) #设置自己的ip地址 和 当前应用程序的端口号 #设置的ip地址是自己的 receive_socket.bind(("10.0.120.236", 10086)) #接受数据 #设置一个接受数据的容器的字节大小 # data = receive_socket.recv(1024) # print(data.decode(encoding="utf-8")) #recvfrom 可以显示发送者信息 data = receive_socket.recvfrom(1024) print(data) print("{}向你发送消息说{}".format(data[1][0], data[0].decode(encoding="utf-8"))) except Exception as e: print(e) finally: if receive_socket != None: receive_socket.close()
基于tcp协议的socket通信 tcp是面向连接的 发送消息的时候 两个必须建立通信通道 发送端: 1.建立通信手柄 2.设置连接者ip地址和当前应用程序的端口号 3.发送连接请求 接受端: 1. 建立通信手柄 -- 基于tcp的 2. 为通信手柄绑定自己的ip地址和当前应用程序的端口号 3. 设置监听 -- 允许几个连接者同时发送连接请求 4. 准备接受连接 ---> 获取发送者与接受者之间建立的socket通信通道 通过通道两者进行数据传递
#发送端 import socket if __name__ == '__main__': s_socket = None try: s_socket = socket.socket(type=socket.SOCK_STREAM) #2. 设置要连接的ip地址和端口号 adderss = ("10.0.120.236", 10010) #3. 连接地址 s_socket.connect(adderss) #4.发送数据 ---> 发送到两者建立的通道中 s_socket.send("你好".encode(encoding="utf-8")) #5.接受数据 data = s_socket.recv(1024) print("{}回复消息说:{}".format(adderss[0], data.decode(encoding="utf-8"))) except Exception as e: print(e) finally: if s_socket != None: s_socket.close() #接收端 import socket if __name__ == '__main__': r_socket = None try: #1. 建立通信手柄 r_socket = socket.socket(type=socket.SOCK_STREAM) #2. 为手柄绑定自己ip地址和当前应用程序的端口号 r_socket.bind(("10.0.120.236", 10010)) #3. 设置监听 允许连接者进行连接 r_socket.listen(5) #4. 等待接受连接 # tuple_data = r_socket.accept() # print(tuple_data) s_r_socket, s_address = r_socket.accept() ''' (<socket.socket fd=444, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('10.0.120.236', 10010), raddr=('10.0.120.236', 51040)>, ('10.0.120.236', 51040)) 元组中第一个数据是一个socket 这个socket包含了发送者和接受的数据 ----> 表示的是两者连接的一个通道 元组中第二个数据是 发送者地址信息 a, b = (10, 11) ''' if s_r_socket != None: print("连接成功") #对方吧数据发送到通道中 从通道中获取数据 data = s_r_socket.recv(1024) print(data) print("{}发送消息说:{}".format(s_address[0], data.decode(encoding="utf-8"))) #回复消息 s_r_socket.send("你也好".encode(encoding="utf-8")) except Exception as e: print(e) finally: if r_socket != None: r_socket.close()
'''' 需求: 只要一方说拜拜 聊天才能结束 '''
#发送端 import socket if __name__ == '__main__': s_socket = None try: #1. 设置socket s_socket = socket.socket(type=socket.SOCK_STREAM) #2. 设置要连接的ip地址和当前应用程序的端口号 address = ("10.0.120.236", 10011) #3. 连接该地址 s_socket.connect(address) #连接成功之后 只要不断开连接 连接就一直存在 没有必要说一次话 连接一次 while True: s_message = input("请输入要发送的消息:") s_socket.send(s_message.encode(encoding="utf-8")) if s_message == "拜拜": break #接受 data = s_socket.recv(1024) print("{}回复说:{}".format(address[0], data.decode(encoding="utf-8"))) if data.decode(encoding="utf-8") == "拜拜": break except Exception as e: print(e) finally: if s_socket!= None: s_socket.close() #接受端 import socket if __name__ == '__main__': r_socket = None try: r_socket = socket.socket(type=socket.SOCK_STREAM) #设置自己的ip地址和当前应用程序的端口号 r_socket.bind(("10.0.120.236", 10011)) #设置监听 r_socket.listen(5) #等待连接 s_r_socket, s_address = r_socket.accept() if s_r_socket != None: print("连接成功") while True: data = s_r_socket.recv(1024) print("{}发送消息说:{}".format(s_address[0], data.decode(encoding="utf-8"))) #对方断开 也断开 if data.decode(encoding="utf-8") == "拜拜": break #回复消息 r_message = input("请输入要回复的消息:") s_r_socket.send(r_message.encode(encoding="utf-8")) if r_message == "拜拜": break except Exception as e: print(e) finally: if r_socket != None: r_socket.close()
#客户端与服务器端的请求是基于socket的 import socket if __name__ == '__main__': s_socket = None try: #接受浏览器发送的请求 s_socket = socket.socket(type=socket.SOCK_STREAM) #绑定自己的ip地址 和 程序的端口号 s_socket.bind(("10.0.120.236", 8888)) #设置监听 s_socket.listen(5) while True: #等待连接 因为服务器可以接受多个用户的连接 每个用户连接完成之后都会形成独立的通道 s_r_socket, s_address = s_socket.accept() if s_r_socket != None: print("连接成功") #接受一下传递过来的数据 data = s_r_socket.recv(1024) #回馈消息: #回馈的是服务器的响应状态 200 表示请求成功 成功返回数据 s_r_socket.send("HTTP/1.1 200 OK\r\n\r\n".encode(encoding="gbk")) #回馈信息 s_r_socket.send("<h1>连接成功</h1>".encode(encoding="gbk")) #这个连接信息回馈结束 结束这个回话 s_r_socket.close() except Exception as e: print(e) finally: if s_socket != None: s_socket.close()
网络图片的服务器地址: http://www.guangyuanol.cn/uploads/allimg/190128/12435242L-0.jpg 服务器 -- 一个独立的计算机 计算机的标识: www.guangyuanol.cn 计算机下有 uploads/allimg/190128 文件夹 在190128这个文件夹下有12435242L-0.jpg 下载的时候要做的操作: 服务器上将图片对应的信息 传递给另外一台电脑 ----> 文件拷贝的过程 传递的信息:文件的名字 文件中的内容 文件大小 文件拷贝的流程: 源文件 源文件中读取内容 循环读取 -- 与文件大小进行比较 --- 写入到目的文件 目的文件 下载的操作: 不同的两个电脑 计算机A --tcp--> 计算机B 计算机A --- 数据传递给通道中 ----> 传递到数据大小 >= 文件大小 通道中读取数据 ---> 计算机B ----> 需要知道文件的大小 读到的数据>= 文件的大小 传递的先后顺序: B计算机如果想接受文件的数据 ----> 提前知道文件的名字 ---> 根据这个名字创建一个文件 在通道中读取数据的时候 需要提前知道文件的大小 ---> 在读取的时候好进行判断 文件传递时: 需要传递的数据有: 文件大小 文件名字 文件的内容 文件上传: QQ 你给别人传递文件 本地的文件消失了吗?? --- 没有 文件上传 与 文件下载 是一个类似的过程 上传 --- 客户端给服务器端 ----> 客户端想服务器端发送了一个传递文件一个请求 下载 --- 服务器端给客户端 ----> 客户端想服务器发送一个想要文件的请求吧 发送者: ----> 主动连接者 客户端 接受者: ----> 被动接受连接 服务器端
#文件上传 客户端连接服务器端 将数据传递给服务器端 #客户端 #客户端 --- 想服务器端发送连接请求 --- 发送者 import socket import os if __name__ == '__main__': clinet_socket = None try: #1. 建立底座 clinet_socket = socket.socket(type=socket.SOCK_STREAM) #设置要连接的地址 和当前应用程序的端口号 address = ("10.0.120.236", 10012) #建立连接 clinet_socket.connect(address) #上传文件 ---> 客户端将文件信息传递给服务器端 #文件的名字 文件的大小 文件的内容 #文件的名字+文件的大小 path = r"12435242L-0.jpg" #获取文件的大小和名字 filename = os.path.basename(path) filesize = os.path.getsize(path) #传递过去 message = "{}+{}".format(filename, filesize) clinet_socket.send(message.encode(encoding="utf-8")) #发送文件的数据 #源文件中读取数据的内容 has_read = 0 #记录的是已经读取的文件的大小 with open(path, "rb") as handle: while has_read < filesize: data = handle.read(1024) clinet_socket.send(data) has_read += 1024 except Exception as e: print(e) finally: if clinet_socket != None: clinet_socket.close() #服务器端 #被动接受请求者 ---> 接受端 ---> 把图片存放于day18/notes文件夹中 import socket import os if __name__ == '__main__': server_socket = None try: #设置socket底座 server_socket = socket.socket(type=socket.SOCK_STREAM) #绑定自己的ip地址和当前应用程序的端口号 server_socket.bind(("10.0.120.236", 10012)) #设置监听 server_socket.listen(5) #等待接受请求 s_c_scoket, c_address = server_socket.accept() if s_c_scoket != None: print("连接成功") #接受数据 data = s_c_scoket.recv(1024) message = data.decode(encoding="utf-8") print(message) #需要单独获取文件的名字 和 文件的大小 filename, filesize = message.split("+") filesize = int(filesize) #知道文件名字之后 需要拼接一个存放文件的路径 super_path = r"C:\Users\11143\Documents\讲课内容\BJPython1901\day18\notes" file_path = os.path.join(super_path, filename) #记录从通道中读取的数据的大小 has_read = 0 #接受数据 with open(file_path, "wb") as handle: while has_read < filesize: data = s_c_scoket.recv(1024) handle.write(data) handle.flush() has_read += 1024 except Exception as e: print(e) finally: if server_socket != None: server_socket.close()
56. 邮件的发送
在Python程序中借助于发送邮件的模块email, 并通过发送邮件的协议smtp将邮件发送给指定邮箱用户 发送邮件的协议是smtp协议 在Python中将这个协议封装在smtplib模块中 在程序中发送邮件的话 需要判断发送者的邮箱是否开启了遵守smtp协议的需求 163邮箱 qq邮箱 163邮箱用户如何开启遵守smtp协议 1.登录163邮箱账户 2.设置 ----> pop3/smtp/ ---> 客户端授权 ---> 点击开启 ----> 获取授权码[记住] 发送邮件的时候 需要通过授权码告知smtp协议对应的服务器 这个账户已经遵守smtp协议了 在Python程序中通过授权的用户发送邮件: 发送纯文本 发送带有附件的 文本 + 附件 邮件的相关信息设置 存放email模块中的
#发送纯文本 #导入smtp协议对应的模块 import smtplib #写邮件是使用的邮件相关类型 ---> 发送文本 ---> 文本对应的类型MimeText from email.mime.text import MIMEText if __name__ == '__main__': #准备工作: #1. 发送邮件的服务器地址 # server = "smtp.163.com" server = "smtp.qq.com" #2. 该协议对应的端口号 port = 25 #3. 发件人的邮箱账户 #sender = "15650726528@163.com" sender = "1114354656@qq.com" #4. 授权码 #code = "abc123" code = "kzumbiaunzvebafh" #5. 收件人 receiver = "1114354656@qq.com" #要发送的内容 content = "新年刚开学,大家状态不太好" #邮件内容的准备 message = MIMEText(content, "plain", "utf-8") #设置内容发送的发送者 message["From"] = sender #接受者 message["To"] = receiver #设置主题 message["Subject"] = "新学期" #连接服务器 登录账号 发送邮件 smtp_obj = smtplib.SMTP(server, port) #登录要发送邮件的账户 并告知服务器 该账户已经遵守的smtp协议 smtp_obj.login(sender, code) #发送邮件 smtp_obj.sendmail(sender, receiver, message.as_string()) print("邮件发送成功")
#发送带有附件的 ----> 附件对应的类型MIMEApplication ''' 最终内容出现拼接 MIMEText的纯文本内容 MIMEApplication的附件内容 最终发送的时候 需要将以上两部分拼接在一起 发送出去 需要一个类型 这个类型是可以将最后发送的内容拼接在一起的 MIMEMulitPart ''' import smtplib from email.mime.text import MIMEText #纯文本类型 from email.mime.application import MIMEApplication #附件类型 from email.mime.multipart import MIMEMultipart #拼接类型 将发送的数据拼接在一起 if __name__ == '__main__': # 准备工作: # 1. 发送邮件的服务器地址 # server = "smtp.163.com" server = "smtp.qq.com" # 2. 该协议对应的端口号 port = 25 # 3. 发件人的邮箱账户 # sender = "15650726528@163.com" # sender = "1114354656@qq.com" sender = "1114354656@qq.com" # 4. 授权码 # code = "abc123" code = "kzumbiaunzvebafh" # 5. 收件人 # receiver = "1114354656@qq.com" receiver = ["1114354656@qq.com", "15650726528@163.com"] #发送邮件的准备工作 mulitpart = MIMEMultipart() #将数据都拼接在mulitpart上 发送出去 #绑定发送者 mulitpart["From"] = sender #接受者 mulitpart["To"] = ",".join(receiver) #主题 mulitpart["Subject"] = "新年快乐" #设置要发送的文本内容 text = MIMEText("2019年新春快乐, 万事如意", "plain", "utf-8") #绑定上可拼接的部分 mulitpart.attach(text) #设置要发送的附件: 发送文件 --- 其实发送的是文件的内容 #读取内容 with open("send_text.py", "rb") as handle: #读出来的数据就是附件的内容 application = MIMEApplication(handle.read()) #为附件设置头部信息 -- 设置文件的名字 讲名字传递过去 application.add_header('content-disposition', 'attachment', filename='send_text.py') #将附件添加在可拼接内容上 mulitpart.attach(application) with open(r"..\server.py", "rb") as handle: # 读出来的数据就是附件的内容 application1 = MIMEApplication(handle.read()) # 为附件设置头部信息 -- 设置文件的名字 讲名字传递过去 application1.add_header('content-disposition', 'attachment', filename='服务器端.py') # 将附件添加在可拼接内容上 mulitpart.attach(application1) # 连接服务器 登录账号 发送邮件 smtp_obj = smtplib.SMTP(server, port) # 登录要发送邮件的账户 并告知服务器 该账户已经遵守的smtp协议 smtp_obj.login(sender, code) # 发送邮件 smtp_obj.sendmail(sender, receiver, mulitpart.as_string()) print("邮件发送成功")
57. 代码调试
程序写完之后运行不出现错误的概率 1%, 都需要取修改bug
'''调试方式1: print(输出可能出现错误的数据) ''' list0 = [5, 17, 28 ,"19"] #列表中的和 total = 0 for i in range(len(list0)): print(list0[i], i) total += list0[i] print(total) print(total)
''' 调试方式2 断言 需要输出错误信息的位置处 不使用print 而是使用assert 格式: assert 满足需求的条件, 出现不满足条件的数据提示的错误信息 说明: 如果满足条件 代码继续向下执行 不满足条件 直接在断言的位置报错 提示错误信息 ''' list0 = [5, 17, 28 ,"19"] #列表中的和 total = 0 for i in range(len(list0)): assert isinstance(list0[i], int), "{}位置处的数据不是整型的".format(i) total += list0[i] print(total) print(total)
''' 调试方法3: 模块logging中的内容 把输出错误信息的位置 替换成logging 1. 导入模块logging 2. 在需要输出错误信息的位置 logging.info(提示的数据) 如果只写这一句 不会显示想输出的数据内容 如果想显示提示的数据 在导入模块的下方添加 logging.basicConfig(level=logging.INFO) ----> 使设置的info中的信息被输出 ''' import logging logging.basicConfig(level=logging.INFO) list0 = [5, 17, 28 ,"19"] #列表中的和 total = 0 for i in range(len(list0)): logging.info("{}位置处的数据类型为{}".format(i, type(list0[i]))) total += list0[i] print(total) print(total)
''' 调试方法4: pdb调试 --- 可以实现让代码一行一行执行 使用pdb测试的时候 需要启动pdb 启动语句是在控制台Terminal上书写的 前提进入到py文件所在的路径 指令: python -m pdb 执行的py文件 进入到pdb环境下 输入n执行单行代码 进入pdb环境下可以指令p 变量名 查看该变量的值 结束调试 输入q ''' list0 = [5, 17, 28 ,"19"] #列表中的和 total = 0 for i in range(len(list0)): total += list0[i] print(total) print(total)
''' 上述调试方式 如果代码行数多 调试起来就比较费劲 对于pdb还有另外一种调试方式: 需要导入pdb模块 在模块写有一个方法 pdb.set_trace() 放在可能出错的位置 放在此处就相当于设置一个断点 运行代码时 运行到pdb.set_trace() 这个位置 会暂停代码 进行pdb环境中 通过(p 变量) 指令查看变量的数据 或者是 c指令继续向下执行 ''' import pdb list0 = [5, 17, 28 ,"19"] #列表中的和 total = 0 for i in range(len(list0)): total += list0[i] pdb.set_trace() print(total) print(total)
''' 调试方式5: debug 断点调试 在页面的行数栏 需要调试的那一行数 用鼠标点击一下 出现红色原点 运行的时候 找小蜘蛛debug执行 在红点处暂停 在页面的位置处控制向下的箭头 一行一行的向下执行 直到找到错误位置 '''
58. 单元测试
单元测试: 对一个模块, 一个函数或者是一个类进行正确检验的测试工具 比如: 系统提供的函数: int() ----> 将数据转化为整型 需要导入模块 unittest 并且设置一个测试类 继承自unittest.TestCase
# value = [12, 13] # res = int(value) # print(res) def cus_int(n): import math if type(n) == int: return n elif isinstance(n, float): return math.floor(n) elif isinstance(n, str): if n.isdigit(): return eval(n) else: raise ValueError("数据不能转化为整型") elif isinstance(n, bool): return 1 if n else 0 else: raise TypeError("不能转换指定类型") def sort_list(src_list): #排序算法 : 冒泡 对列表数据大小进行降序排序 for out in range(1, len(src_list)): for inner in range(0, len(src_list) - out): if src_list[inner] < src_list[inner+1]: src_list[inner], src_list[inner + 1] = src_list[inner + 1], src_list[inner] return src_list
from unittest_pack.test import cus_int, sort_list import unittest class Test(unittest.TestCase): #测试的方法 def test_cus_int(self): ''' 第一个参数 要进行测验的执行函数 第二个参数 设置函数运行之后的预期值 第三个参数 如果函数运行结果 与 预期值不一致 提示的错误信息 ''' self.assertEqual(cus_int(5.12), 5, "转换结果不一致") def test_sort_list(self): self.assertEqual(sort_list([19, 27, 35, 41]), [41, 27, 35, 19], "排序失败") if __name__ == '__main__': unittest.main()
59. 文档测试
针对于方法的 在对应的方法中添加注释, 在注释中举例使用函数的功能 为使用者提供使用方案 提供的实例 应该结果是正确的 怎么验证是正确的 ----> 文档测试 导入模块 doctest
import doctest #删除列表中指定元素 有几个删几个 影响原列表 def delete_ele(src_list, key): #不能影响原列表 # new_list = [] # for ele in src_list: # if ele != key: # new_list.append(ele) # return new_list # count = src_list.count(key) # for _ in range(count): # src_list.remove(key) # return src_list ''' 在src_list列表中删除与key相等的所有元素 :param src_list: 待删除元素的列表 :param key: 要删除的元素 :return: 返回的是删除元素之后的列表 三个箭头与代码之间必须有1个空格 Example: >>> print(delete_ele([12, 56, 71, 12, 38, 12], 12)) [56, 71, 38] ''' while key in src_list: src_list.remove(key) return src_list doctest.testmod()
60. 读写csv文件
需要导入csv模块
#写入内容 import csv ''' 写的话 也是操作文件 将数据写入到文件中 csv写文件内容本身就是一行一行写入的 不需要newline设置其他内容 ''' # with open("person.csv", "w", encoding="utf-8", newline="") as handle: # #1. 使csv工具获取操作文件的手柄 # write_handle = csv.writer(handle) # #2. 一行一行写入的 写入的内容 将内容存放于列表中 将列表中的数据写入到csv文件中 # write_handle.writerow(["姓名", "年龄", "性别"]) # #3. 写入多行 --> 二维列表 # write_handle.writerows([["小花", 15, "女"],["老王", 40, "男"],["小菜", 25, "男"]]) header = ["name", "id", "score"] students = [{"name":"小明", "id":"1010", "score":71}, {"name":"小强", "id":"1009", "score":85}, {"name":"小红", "id":"1011", "score":92}, {"name":"小刚", "id":"1008", "score":65} ] #另外一种写入方式 with open("student.csv", "w", encoding="utf-8", newline="") as handle: #1. 将操作文件的手柄交给csv工具 ''' 文件 列的属性名 ''' writer = csv.DictWriter(handle, header) #先把列名写进去 writer.writeheader() #一行一行写入 # for s_dict in students: # writer.writerow(s_dict) #同时写入多行 writer.writerows(students)
#读csv文件 import csv from csv_pack.person import Person #需要存放所有的人类信息 person_list = [] with open("person.csv", "r", encoding="utf-8") as handle: #交出权限 --- 就已经把内容读完了 reader = csv.reader(handle) print(reader) #reader是可迭代的 已经读出后 就没有了 列表形式读取 迭代器中存放的是列表 data = next(reader) print(data) for item in reader: person = Person(item[0], item[1], item[2]) person_list.append(person) print(person_list) #将人类信息 按照年龄降序排序 person_list.sort(key=lambda p:p.age, reverse=True) print(person_list) #将排好序的数据存放于csv文件中 new_list = [] for p in person_list: sub_list = [p.name, p.age, p.sex] new_list.append(sub_list) print(new_list) with open("person.csv", "w", encoding="utf-8", newline="") as handle: writer = csv.writer(handle) writer.writerow(["name", "age", "sex"]) writer.writerows(new_list) with open("person.csv", "r", encoding="utf-8") as handle: reader = csv.DictReader(handle) # data = next(reader) # print(data) #OrderedDict([('name', '老王'), ('age', '40'), ('sex', '男')]) #在reader中存放的就是一个一个的字典 # for info_list in reader: # print(info_list) # for ele in info_list.keys(): # print(ele) #既然是字典 取某一列的数据 字典形式读取, 迭代器中存放的字典 for info_dict in reader: # value = info_dict["name"] # print(value) person = Person(info_dict.get("name"), info_dict["age"], info_dict["sex"]) print(person) ''' 读student.csv 将数据按照成绩升序排序 再存入文件中 '''
61. 读写excel文件
''' 写excel 安装xlwt pip install xlwt ''' import xlwt #xl wt ---> write path = "员工表.xls" #工作簿 work_book = xlwt.Workbook(encoding="utf-8") #在这个工作簿下创建一个sheet单元 sheet = work_book.add_sheet("Employee") #向工作簿中写入数据 row0 = ["姓名", "编号", "薪资", "部门"] row1 = ["老王", "1001", 6000, "行政部"] row2 = ["小乔", "1002", 7000, "财务部"] row3 = ["大乔", "1003", 5000, "行政部"] row4 = ["周瑜", "1004", 8000, "财务部"] row_list = [row0, row1, row2, row3, row4] #写的内容是一行一行走的 # for col in range(len(row0)): # sheet.write(0, col, row0[col]) # sheet.write(1, col, row1[col]) # sheet.write(2, col, row2[col]) # sheet.write(3, col, row3[col]) # sheet.write(4, col, row4[col]) #自定义样式 ''' 设置字体 设置字号 设置是否加粗 默认是不加粗 ''' def cus_style(font_name, font_size, bold=False): #声明一个样式对象 style = xlwt.XFStyle() #获取设置文字的样式 font = xlwt.Font() font.name = font_name font.height = font_size font.bold = bold #将设置的文字样式给样式对象添加上 style.font = font return style for r in range(len(row_list)): for c in range(len(row0)): if r == 0: sheet.write(r, c, row_list[r][c], cus_style("楷体", 300, True)) else: sheet.write(r, c, row_list[r][c]) #创建本地员工表 -- 将工作簿保存到当地文件 work_book.save(path)
''' 读excel 安装xlrd pip install xlrd ''' import xlrd from excel_pack.employee import Employee #1. 打开excel表格 获得工作簿 work_book = xlrd.open_workbook("员工表.xls") #2. 可以获取该工作簿下的所有sheet单元 # sheets = work_book.sheets() # print(sheets) # # for s in sheets: # print(s.name) #获得sheet的名字 #3.. 可以获取该工作簿下的所有sheet单元名 sheet_names = work_book.sheet_names() print(sheet_names) ##4. 根据指定的名字获得指定的sheet单元 sheet = work_book.sheet_by_name("Employee") print(sheet) #获得sheet单元下的行数 rows = sheet.nrows #获得sheet单元下的列数 cols = sheet.ncols print("行数:{} 列数:{}".format(rows, cols)) #获得指定行的内容 row_content = sheet.row_values(0) print(row_content) #获得指定列的内容 col_content = sheet.col_values(0) print(col_content) #获得指定单元格的内容 cell_content = sheet.cell_value(0, 0) print(cell_content) em_list = [] #获取excel表中所有的内容 并根据指定内容赋值给对应的员工对象 for r in range(1, rows): row_content_list = sheet.row_values(r) em = Employee(row_content_list[0], row_content_list[1], row_content_list[2], row_content_list[3]) em_list.append(em) print(em_list)
''' 修改excel 安装 xlutils pip install xlutils 修改excel的内容 需要提前将内容读取出来 由于采用读的方式 所以没有办法直接使用写的行为 所以需要拷贝一份新的出来 进行写的操作 ''' import xlrd # from xlutils import copy #将工作簿拷贝出来一份新的 from xlutils.copy import copy #获得工作簿 work_book = xlrd.open_workbook("员工表.xls") #将工作簿拷贝一份 new_work_book = copy(work_book) #获得要操作的sheet单元 # sheet = new_work_book.sheet_by_name("Employee") #通过索引来获取 sheet = new_work_book.get_sheet(0) #进行修改 --- 意味着将原来单元格的内容给从新写入 sheet.write(1, 2, "8000") #内容保存 new_work_book.save("员工表.xls")