0.collections模块
在内置数据类型(dict、list、set、tuple)的基础上,collections模块还提供了几个额外的数据类型:Counter、deque、defaultdict、namedtuple和OrderedDict等。
1.namedtuple: 生成可以使用名字来访问元素内容的tuple
2.deque: 双端队列,可以快速的从另外一侧追加和推出对象
3.Counter: 计数器,主要用来计数
4.OrderedDict: 有序字典
5.defaultdict: 带有默认值的字典
# namedtuple 具名元组 # tuple可以表示不变集合,例如,一个点的二维坐标就可以表示成: # 例如: from collections import namedtuple a = (1,2) p = namedtuple('坐标',['x','y']) location1 = p(1,2) # 元素的个数一定要跟上面第二个参数的个数相同 print(location1) # 坐标(x=1, y=2) print(location1.x) # 1 print(location1.y) # 2 card = namedtuple('扑克牌',['color','number']) A = card('♠','A') print(A) # 扑克牌(color='♠', number='A') print(A.color) # ♠ print(A.number) # A
# 用具名元组来记录一个城市的信息 from collections import namedtuple City = namedtuple('City', 'name country population coordinates') bj = City('BeiJing', 'BJ', 39.9, (116.2317, 39.5427)) print(bj) # City(name='BeiJing', country='BJ', population=39.9, coordinates=(116.2317, 39.5427)) print(bj.population) # 39.9
deque双端队列
# 使用list存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了,因为list是线性存储,数据量大的时候,插入和删除效率很低。 # deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈: from collections import deque q = deque(['s','n','v']) # append 和appendleft 分别向2端添加元素 q.append('1') q.appendleft('2') print(q) # deque(['2', 's', 'n', 'v', '1']) # pop与popleft分别是2端删除元素 q.pop() q.popleft() print(q) # deque(['s', 'n', 'v'])
Counter计数器
# Counter类的目的是用来跟踪值出现的次数。它是一个无序的容器类型,以字典的键值对形式存储,其中元素作为key,其计数作为value from collections import Counter c = Counter('asdfadghfd_asdf 31242431!') print(c) # Counter({'d': 4, 'a': 3, 'f': 3, 's': 2, '3': 2, '1': 2, '2': 2, '4': 2, 'g': 1, 'h': 1, '_': 1, ' ': 1, '!': 1})
OrderedDict: 有序字典
from collections import OrderedDict d = dict([('a',1),('c',2),('b',3),('e',0)]) print(d) # {'a': 1, 'c': 2, 'b': 3, 'e': 0} d['z'] = 5 d['y'] = 6 print(d) # {'a': 1, 'c': 2, 'b': 3, 'e': 0, 'z': 5, 'y': 6} print(d.keys()) # dict_keys(['a', 'c', 'b', 'e', 'z', 'y'])
defaultdict: 带有默认值的字典
from collections import defaultdict # 有下列集合,将所有大于66的值保存至字典的第一个key中,将小于66的值保存在第二个key值中 # 即: {'k1': 大于66 , 'k2': 小于66} ls = [11,22,33,44,55,66,77,88,99] my_dict= defaultdict(list) for i in ls: if i>66: my_dict['k1'].append(i) else: my_dict['k2'].append(i) print(my_dict()) # defaultdict(<class 'list'>, {'k2': [11, 22, 33, 44, 55, 66], 'k1': [77, 88, 99]}) # from collections import defaultdict dd = defaultdict(lambda: 'N/A') dd['key1'] = 'abc' dd['key1'] # key1存在 'abc' dd['key2'] # key2不存在,返回默认值 'N/A'
一、random模块
(0, 1):random.random() [1, 10]:random.randint(1, 10) [1, 10):random.randrange(1, 10) (1, 10):random.uniform(1, 10) 单例集合随机选择1个:random.choice(item) 单例集合随机选择n个:random.sample(item, n) 洗牌单列集合:random.shuffle(item)
import random #模块地址 print(random) # <module 'random' from 'F:\\安装包\\Python36\\lib\\random.py'> #random.random()取值范围0-1之间的小数,不包含0和1 for i in range(2): print(random.random()) #(0,1) #取值0-2之间,但是random.random()输出的值都是0-1之间 #0.6179348878875671 #0.7546476092920288 #random.randint() 取值范围[1,5]:包含1和5 for i in range(5): print(random.randint(1,5)) #[1,5] # random.randrange() #[1,5)取值范围1-4,不包含5 for i in range(5): print(random.randrange(1,5)) #[1,4] # random.uniform() 取值范围(1,5)之间的小数 for i in range(5): print(random.uniform(1,5)) #小数:(1,5) # print('%.3f' % random.uniform(1,5)) #%f 可以定义取值的长度 %.3f取3位小数 #洗牌单列集合:random.shuffle(item) ls = [1,2,3,4,5] print(random.shuffle(ls)) #None print(ls) #对ls随机排序[4, 5, 2, 3, 1] #单例集合随机选择1个:random.choice(item) print(random.choice(ls)) # 随机输出ls中一个值 #单例集合随机选择n个:random.sample(item, n) print(random.sample(ls,3)) # [2, 4, 3] 不会同时出现2个一样的数字
练习:验证码功能
# 方法一 # count验证码位数,根据需求出几位 import random def get_code(count): code = "" # 能产生大小写字母和数字 # 进行字符串拼接 for i in range(count): c1 = chr(random.randint(65,90)) #(65,90) ascii表对应大写字母,chr对应数字转换为字母 c2 = chr(random.randint(97,122)) #(97,122) ascii表对应小写字母 c3 = str(random.randint(0,9)) # 数字 code += random.choice([c1,c2,c3]) return code print(get_code(4)) # 方法二:效率高,只需要循环2个,但逻辑多 def get_code(count): code = "" for i in range(count): r = random.choice([1,2 ,3]) if r == 1: c = chr(random.randint(65,90)) elif r == 2: c = chr(random.randint(97,122)) else: c = str(random.randint(0,9)) code += c return code print(get_code(6)) # d7H7C3 # 第3中方法:将所有字母数字的选项都写在一起,每次选中都不会重复 def get_code(count): target = "1234567890QWERTYUIOPASDFGHJKLZXCVBNMwqertiuopadsfklzvcxbnm" code_list = random.sample(target,count) return ''.join(code_list) #3PqXiR82 return code_list #['p', '1', 'S', '9', 'c', 's', 'k', 'A'] print(get_code(18)) #Kv47ZiznkPD0eO3I5d
需求: 1.允许用户最多选3次 2.每次放出20个车牌供用户选择 3.京【A-Z】-【xxxxx】,可以是字母和数字的组合 import random, string count = 0 while count < 3: car_nums = [] #存储供用户选择的号 for i in range(20): n1 = random.choice(string.ascii_uppercase) # 生成第一个车牌字母 n2 = "".join(random.sample(string.ascii_uppercase+string.digits, 5)) c_num = f"鄂{n1}-{n2}" car_nums.append(c_num) print(i+1, c_num) # print(car_nums) choice = input("输入选择号:").strip() if choice in car_nums: print(f"恭喜你选择了新车牌号:{choice}") exit("Good luck...") else: print(f"不合法选择....") count += 1
# 300名员工,年会抽奖,奖项如下
# 一等奖3名
# 二等奖6名
# 3等奖30名
# 要求:共轴3次,第一次抽3等奖,第二次抽2等奖,第三次抽1等奖,抽中的人不能再抽取
import random
staff_list = []
for i in range(1,301):
staff_list.append(f"员工{i}")
level = [30, 6, 3]
count = 0
# 1. for j in range(3): 里面j 不是一定要用到它的
# 2-2.1 j count 的取值范围相同
# 2-2.2 j count 的数值运动轨迹相同
for j in range(3):
winnerList = random.sample(staff_list, level[j])
for winner in winnerList:
staff_list.remove(winner)
print(f"恭喜获得{3-j}等奖的是:{winnerList}")
print(f"还剩{len(staff_list)}个人未中奖")
二、序列化模块
# 什么是序列化:将对象转化为字符串 # 什么是反序列化:将字符串转化为对象 # 为什么要序列化:数据的存储和传输都采用的是字符串类型 # 序列化的模块:json pickle shelve # json:支持跨语言,用于数据的传输 # pickle:支持py的所有数据类型,所有可以将所有py的对象序列化后存储 # shelve:支持py的所有数据类型,可以即时存与取 # 序列化 dump dumps # 反序列化 load loads
三.Json模块:用于传输(多语言支持)
什么是json:就是完成文本序列化得到的文本字符串,json字符串具有一定的语法规范 1.支持的数据类型:int float str bool dict list null # json中布尔类型转换为小写,不支持set 和tuple 2.复杂的json都是由{}与[]嵌套形成的数据 3.json字符串只能有一个根: json_str = '{}{}' | '{}[]' | '[][]' | '1null' # 报错,都是两个根 4.json中的str类型必须用""包裹(json字符串中的字符串类型不支持'' """""")
import json # python对象 序列化 json字符串 data = None res = json.dumps(data) print(res) # json字符串 反序列化 python对象 json_str = '3.14' json_str = 'true' json_str = 'null' json_str = '{}' json_str = '[]' json_str = '1, null' # 有误,两个根 json_str = "\"abc\"" #反序列化去引号 json_str = '"abc"' obj = json.loads(json_str) print(obj, type(obj))
操作文件:读()存与写(取)
# 序列化 obj = {'name': 'Simon', 'age': 17, 'gender': '男'} with open('a.txt', 'w', encoding='utf-8') as wf: json.dump(obj, wf, ensure_ascii=False) # ensure_ascii 默认为True ansci码;#False跟随文件编码utf-8 # json.dump(obj, wf) #{"name": "Simon", "age": 17, "gender": "男"}{"name": "Simon", "age": 17, "gender": "\u7537"} # 文件内容123456:因为wf文件只打开一次,打开的时候操作一次,之后都是写 # wf.write('123') # wf.write('456') # 反序列化 with open('a.txt', 'r', encoding='utf-8') as rf: obj = json.load(rf) print(obj) # {'name': 'Simon', 'age': 17, 'gender': '男'} # 注:json模块的序列化与反序列化是一一对应关系 print(json.load(open('a.txt', 'r', encoding='utf-8'))) # {'name': 'Simon', 'age': 17, 'gender': '男'}
四、 pickle模块:支持所有数据类型(不支持其他语言,只用于python)
import pickle obj = {'name': 'simon', 'age': 17, 'gender': '男'} res = pickle.dumps(obj) print(res) # b'\x80\x03}q\x00(X\x04\x00\x00\x00nameq\x01X\x05\x00\x00\x00simonq\x02X\x03\x00\x00\x00ageq\x03K\x11X\x06\x00\x00\x00genderq\x04X\x03\x00\x00\x00\xe7\x94\xb7q\x05u.' pickle.dump(obj,open('b.txt','wb')) #内容: �}q (X nameqX simonqX ageqKX genderqX 男qu. # 反序列化 print(pickle.loads(res)) # {'name': 'simon', 'age': 17, 'gender': '男'} print(pickle.load(open('b.txt', 'rb'))) # {'name': 'simon', 'age': 17, 'gender': '男'}
五、shelve模块:支持所有数据类型(优化存与取的方式)
# shelve将dump与load封装为一步 import shelve # 将文件的方法封装到模块中,文件后缀自己随便取的 #新建了3个文件:c.shv.bak c.shv.dat c.shv.dir shv_tool = shelve.open('c.shv') # 序列化 # shv_tool['name'] = 'Simon' # 反序列化 res = shv_tool['name'] print(res) # Simon shv_tool.close() # 文件通过shelve对象来关闭,关闭后就不能再取,否则就报错 # 二次操作:重新打开与关闭 shv_tool = shelve.open('c.shv') print(shv_tool['name']) # Simon shv_tool.close() # 操作方式 with shelve.open('c.shv') as shv_tool: print(shv_tool['name']) # Simon # writeback将反序列化到内存的数据,操作后即时同步到文件中 with shelve.open('c.shv', writeback=True) as shv_tool: shv_tool['stus'] = ['Bob', 'Tom'] # 重置 # print(shv_tool['stus']) # ['Bob', 'Tom'] shv_tool['stus'].append('Jobs') # 将数据取到内存,在内存中添加 print(shv_tool['stus']) # ['Bob', 'Tom', 'Jobs']
六、shutil:可以操作权限的处理文件模块
import shutil # 基于路径的文件复制: shutil.copyfile('source_file', 'target_file') # 绝对路径:复制后的文件名是需要的 shutil.copyfile(r'C:\shelev.py',r'D:\target.py') # 基于流的文件复制: with open('source_file', 'rb') as r, open('target_file', 'wb') as w: shutil.copyfileobj(r, w) # 递归删除目标目录:目录里有文件也直接删除 shutil.rmtree('target_folder') # 文件移动 shutil.remove('old_file', 'new_file') # 文件夹压缩 # file_name: 压缩后得到的文件名 format:压缩格式 archive_path:要压缩的文件夹路径 shutil.make_archive('file_name', 'format', 'archive_path') # 举例 shutil.make_archive('target/abc', 'zip', 'source') #在target 目录中将source目录压缩到target目录中名为abc.zip # 文件夹解压 # unpack_file: 解压的文件 unpack_name:解压得到的文件夹名 format:解压格式 shutil.unpack_archive('unpack_file', 'unpack_name', 'format') # 举例 shutil.unpack_archive('target/abc.zip', 'target/xyz', 'zip') #将abc.zip解压为xyz文件夹
七、加密模块
# 一般加密解密方法: # md5加密:不可逆加密 # 碰撞解密:用数据再进行一次加密,与原加密结果做匹配
hashlib模块加密
import hashlib data = '数据' # 生成对象 lock_obj = hashlib.md5(data.encode('utf-8')) # 生产加密锁对象,传入加密数据 result = lock_obj.hexdigest() # 获取加密后的加密串 print(result) # update可以往锁对象中添加加密数据 lock_obj = hashlib.md5() lock_obj.update(b'123') lock_obj.update(b'abc') lock_obj.update('嘿嘿'.encode('utf-8')) print(lock_obj.hexdigest()) lock_obj.update(b'000') print(lock_obj.hexdigest()) # 000 | '123abc嘿嘿000'.encode('utf-8') print(hashlib.md5('123abc嘿嘿000'.encode('utf-8')).hexdigest()) # 注:要为新数据提供加密,一定要为该数据创建一个加密对象
# 加盐:前提是支持update # 什么是加盐:在原数据前或后添加一些预定的数据,与原数据一起进行加密 # 为什么要加盐: # 1.当原数据过于简单,可以对其加盐,提高数据的复杂度 # 2.盐与数据有一定相似度,混淆对真实数据的提取 data = 'ab_12' lock_obj = hashlib.md5() lock_obj.update(b'a12_d') lock_obj.update(data.encode('utf-8')) lock_obj.update(b'dd_121') print(lock_obj.hexdigest()) # a12_dab_12dd_121在数据前后加盐混淆 # 其他位数加密 lock_obj = hashlib.sha3_256(b'123') print(lock_obj.hexdigest()) lock_obj = hashlib.sha3_512(b'123') lock_obj.update(b'salt') print(lock_obj.hexdigest())
hmac模块加密
import hmac # 与hashlib的不同点:生产锁对象时必须提高数据参数 lock_obj = hmac.new(b'') print(lock_obj.hexdigest())
# 支持加盐 lock_obj = hmac.new(b'') lock_obj.update(b'salt') print(lock_obj.hexdigest())
八、logging:日志模块
# logging记录项目日志的模块 # 记录日志:将项目中产生的一些数据,或是信息,或是错误不再输出到控制台,而是输出到文件中,保存这样信息的文件就称之为日志文件
# 日志级别 在开始记录日志前还需要明确,日志的级别 随着时间的推移,日志记录会非常多,成千上万行,如何快速找到需要的日志记录这就成了问题 解决的方案就是 给日志划分级别 logging模块将日志分为了五个级别,从高到低分别是: 1.info 常规信息 2.debug 调试信息 3.warning 警告信息(默认级别) 4.error 错误信息 5.cretical 严重错误 本质上他们使用数字来表示级别的,从高到低分别是10,20,30,40,50
#1.导入模块 import logging #2.输出日志 logging.info("info") logging.debug("debug调试") logging.warning("warning警告") logging.error("error错误") logging.critical("critical严重错误") # 输出 WARNING:root:warning # 输出 ERROR:root:error # 输出 CRITICAL:root:critical
1.logging模块的基本配置使用:
import logging import sys # 2.日志的基本配置 logging.basicConfig( # 输出级别 level=logging.INFO, # level=10, # 输出位置 # stream=sys.stderr, # sys.stdout 往控制台输出 filename='log/my.log', # 往文件输出 => 如果需要同时往多个位置输出,需要handles # 输出格式 format='%(asctime)s[%(name)s]: %(msg)s', datefmt='%Y-%m-%d %H:%M:%S' )
# my.log日志:2019-05-16 14:39:32[root]: error msg # 标准输入 # print(sys.stdin.readline())
# filename:用指定的文件名创建FiledHandler(后边会具体讲解handler的概念),这样日志会被存储在指定的文件中。 # filemode:文件打开方式,在指定了filename时使用这个参数,默认值为“a”还可指定为“w”。 # format:指定handler使用的日志显示格式。 # datefmt:指定日期时间格式。 # level:设置rootlogger(后边会讲解具体概念)的日志级别 #案例: logging.basicConfig( filename="aaa.log", filemode="at", datefmt="%Y-%m-%d %H:%M:%S %p", format="%(asctime)s - %(name)s - %(levelname)s - %(module)s: %(message)s", level=10 )
格式化全部可用名称:
%(name)s:Logger的名字,并非用户名,详细查看 %(levelno)s:数字形式的日志级别 %(levelname)s:文本形式的日志级别 %(pathname)s:调用日志输出函数的模块的完整路径名,可能没有 %(filename)s:调用日志输出函数的模块的文件名 %(module)s:调用日志输出函数的模块名 %(funcName)s:调用日志输出函数的函数名 %(lineno)d:调用日志输出函数的语句所在的代码行 %(created)f:当前时间,用UNIX标准的表示时间的浮 点数表示 %(relativeCreated)d:输出日志信息时的,自Logger创建以 来的毫秒数 %(asctime)s:字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒 %(thread)d:线程ID。可能没有 %(threadName)s:线程名。可能没有 %(process)d:进程ID。可能没有 %(message)s:用户输出的消息
2.logging的成员组成
logging模块的四个核心角色: 1.Logger 日志生成器 产生日志 2.Filter 日志过滤器 过滤日志 3.Handler 日志处理器 对日志进行格式化,并输出到指定位置(控制台或文件) 4.Formater 处理日志的格式
import logging # 1.打印者:自定义的打印者如何配置 log1 = logging.getLogger('logger name') # 2.输出位置:两个文件输出位置与一个控制台输出位置 hd_a = logging.FileHandler('log/a.log', encoding='utf-8') hd_cmd = logging.StreamHandler() # 3.输出格式 fmt1 = logging.Formatter('%(asctime)s 【%(name)s】- %(msg)s') fmt2 = logging.Formatter('%(asctime)s - %(msg)s') # 4.打印者添加句柄 - 设置打印者的输出位置 log1.addHandler(hd_a) log1.addHandler(hd_cmd) # 5.将格式绑定给输出位置(句柄) hd_a.setFormatter(fmt1) hd_cmd.setFormatter(fmt2) # 6.权限控制 log1.setLevel(logging.DEBUG) # 打印者规定打印级别 hd_a.setLevel(logging.WARNING) # 不同输出位置(句柄)再可以二次限定输出级别 hd_cmd.setLevel(logging.DEBUG) # 不同输出位置(句柄)再可以二次限定输出级别 # 7.不同级别输出信息 log1.debug('debug msg') log1.info('info msg') log1.warning('warning msg') log1.error('error msg') log1.critical('critical msg')
import logging # root打印者,用logging.basicConfig来配置 # logging.critical('12345') # 1.打印者:自定义的打印者如何配置 log1 = logging.getLogger('Owen') # log1.critical('67890') log2 = logging.getLogger('Simon') # log2.critical('00000') # 2.输出位置:两个文件输出位置与一个控制台输出位置 hd_a = logging.FileHandler('log/a.log', encoding='utf-8') hd_b = logging.FileHandler('log/b.log', encoding='utf-8') hd_cmd = logging.StreamHandler() #控制台输出 # )) 为输出者绑定输出位置 log1.addHandler(hd_a) log1.addHandler(hd_b) log2.addHandler(hd_b) log2.addHandler(hd_cmd) # 3.输出格式 fmt1 = logging.Formatter('%(asctime)s 【%(name)s】- %(msg)s') fmt2 = logging.Formatter('%(asctime)s - %(msg)s') # ))将格式绑定给输出位置(句柄) hd_a.setFormatter(fmt1) hd_b.setFormatter(fmt1) hd_cmd.setFormatter(fmt2) # 级别控制: 打印者规定打印级别,输出位置(句柄)再可以二次限定,级别>=打印者级别 log2.setLevel(logging.DEBUG) hd_b.setLevel(logging.WARNING) hd_cmd.setLevel(logging.DEBUG) log2.debug('debug msg') log2.info('info msg') log2.warning('warning msg') log2.error('error msg') log2.critical('critical msg') # 4.输出 log1.critical('log1 输出的 critical msg') log2.critical('log2 输出的 critical msg') # 过滤:少用 # logging.Filter
3.logging配置文件项目开发运用
# 1.将打印者,句柄,与格式封装成配置信息 # 2.加载配置信息 # 3.使用自定义logger,采用的就是配置信息设置的logger # 优势:1,2两步是一劳永逸的,后期开发只需要在要记录日志的文件中使用自定义logger
# 一、基础配置:目录conf/setting.py LOGGING_DIC = { 'version': 1, 'disable_existing_loggers': False, #让系统的默认配置失效 'formatters': { # 名称格式定义 'o_fmt1': { 'format': '%(asctime)s 【%(name)s】- %(msg)s' }, 'o_fmt2': { 'format': '%(asctime)s - %(msg)s' } }, 'filters': {}, 'handlers': { 'o_hd_file': { 'level': 'WARNING', 'class': 'logging.handlers.RotatingFileHandler', # 打印到控制台 'formatter': 'o_fmt1', 'filename': 'log/sys.log', 'encoding': 'utf-8', 'maxBytes': 1024*1024*5, # 日志大小5M 'backupCount': 5, # 文件切分:写满一个就改名为sys1.log,写满5个清空重新记录 }, 'o_hd_cmd': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', # 打印到控制台 'formatter': 'o_fmt2' } }, 'loggers': { 'o_owen': { 'level': 'DEBUG', 'handlers': ['o_hd_file', 'o_hd_cmd'] }, 'o_simon': { 'level': 'DEBUG', 'handlers': ['o_hd_cmd'], # 'propagate': True # 向更高的level logging传递 } } } # 二、加载配置 import logging.config logging.config.dictConfig(LOGGING_DIC) # 三、使用 log = logging.getLogger('o_simon') log.critical('信息') log1 = logging.getLogger('o_owen') log1.critical('信息')
# 目录:lib/common.py from conf.settings import LOGGING_DIC import logging.config logging.config.dictConfig(LOGGING_DIC) def getLogger(name): return logging.getLogger(name)
测试:
from lib.common import getLogger log = getLogger('o_owen') log.debug('12345') log.critical('67890')
standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \ '[%(levelname)s][%(message)s]' #其中name为getlogger指定的名字 simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s' id_simple_format = '[%(levelname)s][%(asctime)s] %(message)s' logfile_path = "配置文件路径" LOGGING_DIC = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'standard': { 'format': standard_format }, 'simple': { 'format': simple_format }, }, 'filters': {}, 'handlers': { #打印到终端的日志 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', # 打印到屏幕 'formatter': 'simple' }, #打印到文件的日志,收集info及以上的日志 'default': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', # 保存到文件 'formatter': 'standard', 'filename': logfile_path, # 日志文件 'maxBytes': 1024*1024*5, # 日志大小 5M 'backupCount': 5, #日志文件最大个数 'encoding': 'utf-8', # 日志文件的编码 }, }, 'loggers': { #logging.getLogger(__name__)拿到的logger配置 'aa': { 'handlers': ['default', 'console'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕 'level': 'DEBUG', 'propagate': True, # 向上(更高level的logger)传递 }, # 把key设置为空 '': { 'handlers': ['default', 'console'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕 'level': 'DEBUG', 'propagate': True, # 向上(更高level的logger)传递 }, }, }
#lib/common.py #日志功能 import logging.config from conf import settings # 在lib 文件夹的common文件中生成日志对象 #生成日志对象 def get_logger(name): # 先把日志配置传给logging logging.config.dictConfig(settings.LOGGING_DIC) # 生产日志对象--》接收的是name,根据name打印相应的日志 my_logger = logging.getLogger(name) return my_logger # conf/settings.py import os # test目录 BASE_PATH = os.path.dirname(os.path.dirname(__file__)) # test/db目录 DB_PATH = os.path.join(BASE_PATH, 'db') # 日志配置文件 ''' logging 配置 ''' import os standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \ '[%(levelname)s][%(message)s]' #其中name为getlogger指定的名字 simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s' id_simple_format = '[%(levelname)s][%(asctime)s] %(message)s' # 日志目录 LOG_PATH = os.path.join(BASE_PATH,'log') log_filename = 'Atm_Shop.log' if not os.path.isdir(LOG_PATH): os.mkdir(LOG_PATH) # log文件全路径 logfile_path = os.path.join(LOG_PATH,log_filename) LOGGING_DIC = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'standard': { 'format': standard_format }, 'simple': { 'format': simple_format }, }, 'filters': {}, 'handlers': { #打印到终端的日志 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', # 打印到屏幕 'formatter': 'simple' }, #打印到文件的日志,收集info及以上的日志 'default': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', # 保存到文件 'formatter': 'standard', 'filename': logfile_path, # 日志文件 'maxBytes': 1024*1024*5, # 日志大小 5M 'backupCount': 5, #日志文件最大个数 'encoding': 'utf-8', # 日志文件的编码 }, }, 'loggers': { #logging.getLogger(__name__)拿到的logger配置 'aa': { 'handlers': ['default', 'console'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕 'level': 'DEBUG', 'propagate': True, # 向上(更高level的logger)传递 }, # 把key设置为空 '': { 'handlers': ['default', 'console'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕 'level': 'DEBUG', 'propagate': True, # 向上(更高level的logger)传递 }, }, } # 引用 from lib import common # 获取银行日志功能 bank_log = common.get_logger('bank') bank_log.info('%s提现%s元成功,手续费为%s' % (user, money2,money3))
九、re模块
# re:正则,全称正则字符串 - re就是有特殊语法的字符串 # re可以将有正则语法的字符串解析为对应的正则对象,用来匹配目标字符串 # 学习re的目的:1.判断目标字符串是否合法 2.在目标字符串中提取想要的信息(信息匹配规则采用正则)
import re # 从123abc123中查找1 r1 = re.findall(r'1', '123abc123') print(r1) # ['1', '1'] r2 = re.findall(r'a', '123abc123ABC', flags=re.I) # re.I 不区分大小写匹配 print(r2) # ['a', 'A'] # 1.将 r'\d' 丢给_compile得到可以匹配数字的 正则对象 # 2.正则对象.findall('目标字符串') r3 = re.findall(r'\d', '123abc123') print(r3) # ['1', '2', '3', '1', '2', '3'] re_obj = re.compile(r'\d') # 将 r'\d' 丢给_compile得到可以匹配数字的 正则对象 r4 = re_obj.findall('123abc123') # 正则对象.findall('目标字符串') print(r4) # ['1', '2', '3', '1', '2', '3']
正则语法
单个字符语法:
import re # 一、单个字符语法 # 匹配a print(re.findall(r'a', '123abc嘿嘿')) # ['a'] # a或b print(re.findall(r'a|b', '123abc嘿嘿')) # ['a', 'b'] 不建议使用 print(re.findall(r'[ab]', '123abc嘿嘿')) # ['a', 'b'] 建议使用 # 非a非b print(re.findall(r'[^ab]', '123abc嘿嘿')) # ['1', '2', '3', 'c', '嘿', '嘿'] # 数字 print(re.findall(r'[0-9]', '12abc嘿嘿12')) # ['1', '2', '1', '2'] 建议使用 print(re.findall(r'\d', '12abc嘿嘿12')) # ['1', '2', '1', '2'] 不建议使用 # 字母 print(re.findall(r'[a-zA-Z]', '12abc[嘿嘿ABC')) # ['a', 'b', 'c', 'A', 'B', 'C'] # 字母数字_常用汉字:\w => 建议使用 [a-zA-Z0-9_] print(re.findall(r'\w', '12abc[_嘿嘿ABC')) # ['1', '2', 'a', 'b', 'c', '_', '嘿', '嘿', 'A', 'B', 'C'] # 汉字 [\u4e00-\u9fa5]代表汉字 print(re.findall(r'[\u4e00-\u9fa5]', '12abc[_嘿嘿ABC')) # ['嘿', '嘿'] # 空白字符:\s => 建议使用[ \f\n\r\t\v] print(re.findall(r'\s', ' \f\n\r\t\v')) # [' ', '\x0c', '\n', '\r', '\t', '\x0b'] # 非\n的任意字符: . print(re.findall(r'.', ' \f\n\r\t\v*&_.')) # [' ', '\x0c', '\r', '\t', '\x0b', '*', '&', '_', '.'] # 只想匹配.字符:\. print(re.findall(r'\.', ' \f\n\r\t\v*&_.')) # ['.'] # re.S: 让.也能匹配\n,就可以理解为 . 可以匹配所有字符 print(re.findall(r'.', ' \f\n\r\t\v*&_.', flags=re.S)) # 取对立面 \d数字 \D非数字 \w=>\W \s=>\S print(re.findall(r'\D', '12abc\f嘿嘿12')) # ['a', 'b', 'c', '\x0c', '嘿', '嘿']
重复字符语法:
print(re.findall(r'ab', 'abacbabc')) # ['ab', 'ab'] # 指定个数: 匹配abb print(re.findall(r'ab{2}', 'aababbabbb')) # ['abb', 'abb'] # 贪婪匹配: 尽可能多的匹配 # a0~2个b: a | ab | abb print(re.findall(r'ab{,2}', 'aababbabbb')) # ['a', 'ab', 'abb', 'abb'] # a0~n个b: print(re.findall(r'ab{0,}', 'aababbabbb')) # ['a', 'ab', 'abb', 'abbb'] # a1~3个b: print(re.findall(r'ab{1,3}', 'aababbabbb')) # ['ab', 'abb', 'abbb'] # *: {0,} print(re.findall(r'ab*', 'aababbabbb')) # ['a', 'ab', 'abb', 'abbb'] # +: {1,} print(re.findall(r'ab+', 'aababbabbb')) # ['ab', 'abb', 'abbb'] # ?: {,1} print(re.findall(r'ab?', 'aababbabbb')) # ['a', 'ab', 'ab', 'ab'] # 非贪婪匹配 print(re.findall(r'ab{1,3}?', 'aababbabbb')) # ['ab', 'ab', 'ab'] # 重点:非贪婪匹配应用场景,一般都是结合有开头与结尾的标识 print(re.findall(r'<.{1,}>', '<a><b>msg</b></a>')) # ['<a><b>msg</b></a>'] # 匹配标签 print(re.findall(r'<.{1,}?>', '<a><b>msg</b></a>')) # ['<a>', '<b>', '</b>', '</a>'] # *?: {0,}? # +?: {1,}? # ??: {,1}? print(re.findall(r'<.+?>', '<a><b>msg</b></a>')) # ['<a>', '<b>', '</b>', '</a>']
分组语法:
# 引子 print(re.findall(r'(?:ab){2}', 'abbabab')) # ['abab'] # findall(): 没有分组情况下,显示匹配的结果;如果有分组,显示分组结果 # 分组:() # 取消分组:(?:) # 有名分组:(?P<名字>) # 案例: # 匹配链接 print(re.findall(r'www\..+?\.com', 'www.baidu.comabcwww.sina.com')) # ['www.baidu.com', 'www.sina.com'] # 获取链接的域名:['baidu', 'sina'] print(re.findall(r'www\.(.+?)\.com', 'www.baidu.comabcwww.sina.com')) # ['baidu', 'sina'] # 分组编号: 从左往右数左(进行分组编号 # [('www.baidu.com', 'baidu', 'com'), ('www.sina.edu', 'sina', 'edu')] res = re.findall(r'(www\.(.+?)\.(com|edu))', 'www.baidu.comabcwww.sina.edu') print(res) print(res[0][1]) # 取消分组:(?:) 应用于,要将一些数据作为整体看待,但由不能产生分组 # [('www.baidu.com', 'baidu'), ('www.sina.edu', 'sina')] res = re.findall(r'(www\.(.+?)\.(?:com|edu))', 'www.baidu.comabcwww.sina.edu') print(res)
其他正则方法的使用:
# match:不是全文匹配,必须从头开始匹配,且只匹配一次 res = re.match(r'(www\.(?P<site_name>.+?)\.(?:com|edu))', 'www.baidu.comwww.sina.edu') # 可以通过分组号直接取出分组内容 print(res.group(1)) print(res.group(2)) # print(res.group(0), res) # 匹配的整体 # 有名分组 print(res.group('site_name')) # split(): 拆分 print('abc def xyz'.split(' ')) print(re.split(r' ', 'abc def xyz')) print(re.split(r'[,@ ]', 'abc,def@xyz opq')) # sub(): 替换 res = re.sub(r'good', 'bed', 'good good day a') print(res) # bed bed day a res = re.sub(r'good', 'bed', 'good good day a', count=1) print(res) # bed good day a res = re.sub(r'good day a', '123', 'good day a!!!') print(res) # 123!!! # 结合分组可以完成数据的重组 res = re.sub(r'(good) (day) (a)', r'today is \3 \1 \2', 'good day a!!!') print(res) # today is a good day!!!
描述 | |
---|---|
\ | 将下一个字符标记符、或一个向后引用、或一个八进制转义符。例如,“\n”匹配\n。“\n”匹配换行符。序列“\”匹配“\”而“(”则匹配“(”。即相当于多种编程语言中都有的“转义字符”的概念。 |
^ | 匹配输入字行首。如果设置了RegExp对象的Multiline属性,^也匹配“\n”或“\r”之后的位置。 |
$ | 匹配输入行尾。如果设置了RegExp对象的Multiline属性,$也匹配“\n”或“\r”之前的位置。 |
* | 匹配前面的子表达式任意次。例如,zo能匹配“z”,也能匹配“zo”以及“zoo”。等价于{0,}。 |
+ | 匹配前面的子表达式一次或多次(大于等于1次)。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等价于{1,}。 |
{n} | n是一个非负整数。匹配确定的n次。例如,“o{2}”不能匹配“Bob”中的“o”,但是能匹配“food”中的两个o。 |
{n,} | n是一个非负整数。至少匹配n次。例如,“o{2,}”不能匹配“Bob”中的“o”,但能匹配“foooood”中的所有o。“o{1,}”等价于“o+”。“o{0,}”则等价于“o*”。 |
{n,m} | m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3}”将匹配“fooooood”中的前三个o为一组,后三个o为一组。“o{0,1}”等价于“o?”。请注意在逗号和两个数之间不能有空格。 |
? | 匹配前面的子表达式零次或一次。例如,“do(es)?”可以匹配“do”或“does”。?等价于{0,1}。 |
? | 当该字符紧跟在任何一个其他限制符(,+,?,{n},{n,},{n,m*})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少地匹配所搜索的字符串,而默认的贪婪模式则尽可能多地匹配所搜索的字符串。例如,对于字符串“oooo”,“o+”将尽可能多地匹配“o”,得到结果[“oooo”],而“o+?”将尽可能少地匹配“o”,得到结果 ['o', 'o', 'o', 'o'] |
.点 | 匹配除“\n”和"\r"之外的任何单个字符。要匹配包括“\n”和"\r"在内的任何字符,请使用像“[\s\S]”的模式。 |
x|y | 匹配x或y。例如,“z|food”能匹配“z”或“food”(此处请谨慎)。“[zf]ood”则匹配“zood”或“food”。 |
[xyz] | 字符集合。匹配所包含的任意一个字符。例如,“[abc]”可以匹配“plain”中的“a”。 |
[^xyz] | 负值字符集合。匹配未包含的任意字符。例如,“abc”可以匹配“plain”中的“plin”任一字符。 |
[a-z] | 字符范围。匹配指定范围内的任意字符。例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写字母字符。注意:只有连字符在字符组内部时,并且出现在两个字符之间时,才能表示字符的范围; 如果出字符组的开头,则只能表示连字符本身. |
[^a-z] | 负值字符范围。匹配任何不在指定范围内的任意字符。例如,“a-z”可以匹配任何不在“a”到“z”范围内的任意字符。 |
\b | 匹配一个单词的边界,也就是指单词和空格间的位置(即正则表达式的“匹配”有两种概念,一种是匹配字符,一种是匹配位置,这里的\b就是匹配位置的)。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”;“\b1”可以匹配“1_23”中的“1”,但不能匹配“21_3”中的“1_”。 |
\B | 匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er” |
\s | 匹配任何不可见字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。 |
\S | 匹配任何可见字符。等价于 \f\n\r\t\v。 |
\w | 匹配包括下划线的任何单词字符。类似但不等价于“[A-Za-z0-9_]”,这里的"单词"字符使用Unicode字符集。 |
\W | 匹配任何非单词字符。等价于“A-Za-z0-9_”。 |
\d | 匹配一个数字字符。等价于[0-9]。grep 要加上-P,perl正则支持 |
\D | 匹配一个非数字字符。等价于0-9。grep要加上-P,perl正则支持 |
\n | 匹配一个换行符。等价于\x0a和\cJ。 |
\r | 匹配一个回车符。等价于\x0d和\cM。 |
\t | 匹配一个制表符。等价于\x09和\cI。 |
( ) | 将( 和 ) 之间的表达式定义为“组”(group),并且将匹配这个表达式的字符保存到一个临时区域(一个正则表达式中最多可以保存9个),它们可以用 \1 到\9 的符号来引用。 |
(?:pattern) | 非获取匹配,匹配pattern但不获取匹配结果,不进行存储供以后使用。这在使用或字符“(|)”来组合一个模式的各个部分时很有用。例如“industr(?:y|ies)”就是一个比“industry|industries”更简略的表达式。 |
| | 将两个匹配条件进行逻辑“或”(Or)运算。例如正则表达式(him|her) 匹配"it belongs to him"和"it belongs to her",但是不能匹配"it belongs to them."。注意:这个元字符不是所有的软件都支持的。 |
十、shell脚本之subprocess模块
#Popen
order = subprocess.Popen('dir',
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
res = order.stdout.read().decode('gbk')
print(res)
print('==============================')
res = order.stderr.read().decode('gbk')
print(res)
# 举例 # windows下测试列出目录文件
# run
import subprocess
order = subprocess.run('dir',
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
res = order.stdout.decode('gbk')
print(res)
print('==============================')
# stderr 输出错误
res = order.stderr.decode('gbk')
print(res)
#Centos系统 >>> a = subprocess.call(['df','-lh'],shell=False) Filesystem Size Used Avail Use% Mounted on /dev/vda1 40G 9.0G 29G 25% / /dev/vdb 99G 16G 78G 18% /db tmpfs 783M 0 783M 0% /run/user/1000 >>> r1=subprocess.getstatusoutput("dir") >>> r1 (0, 'lova test.sh') >>> r2 = subprocess.Popen('ping -c2 www.baidu.com',shell=True) >>> PING www.a.shifen.com (115.239.210.27) 56(84) bytes of data. 64 bytes from 115.239.210.27 (115.239.210.27): icmp_seq=1 ttl=54 time=8.59 ms 64 bytes from 115.239.210.27 (115.239.210.27): icmp_seq=2 ttl=54 time=8.64 ms --- www.a.shifen.com ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3004ms rtt min/avg/max/mdev = 8.595/8.621/8.648/0.094 ms >>> subprocess.Popen('./test.sh',shell=True) <subprocess.Popen object at 0x7f2c51f10160> >>> Hello World! >>> res = subprocess.Popen('./test.sh',stdout = subprocess.PIPE,shell=True,stderr=subprocess.PIPE) >>> res1 = res.stdout.read() >>> res1 b'Hello World!\n' >>> print(res1) b'Hello World!\n'
def subrun(cmd): comply = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) res_stdout = comply.stdout.read() res_stderr = comply.stderr.read() if res_stdout: return res_stdout.decode('utf-8') return res_stderr.decode('utf-8')
十二、configparser 模块
# my.ini 配置文件读写 """ [server] ip = 192.168.66.88 port = 3306 [client] user = root password = root """ import configparser # 1.初始化 parser = configparser.ConfigParser() # 2.读 parser.read('my.ini', encoding='utf-8') # section | option | value # 读取列表 sts = parser.sections() print(sts, type(sts)) # ['server', 'client'] <class 'list'> ops = parser.options(sts[0]) print(ops) # ['ip', 'port'] value = parser.get(sts[0], ops[0]) print(value, type(value)) # 192.168.66.88 <class 'str'> # # get=>str getboolean=>bool getfloat=>float getint=>int print(parser.get('server', 'port')) # 3306 # 3.写 parser.read('my.ini', encoding='utf-8') parser.set('server', 'port', '6666') parser.write(open('my.ini', 'w'))
十三、xml模块
# xml文件:1.作为传输文件用于数据的传输 2.作为配置文件配置信息 # 1.只能由一个根标签 # 2.所有的标签都是自定义的 # 3.标签名就是key,标签的内容就是value # 4.与json不同的是,标签不仅可以有key和value,还有标签的属性 # 注:xml的属性通常用来表示标签间的区分度,用于解析xml来使用
<data> <country name="Liechtenstein"> <rank updated="yes">2</rank> <year>2008</year> <gdppc>141100</gdppc> <neighbor direction="E" name="Austria" /> <neighbor direction="W" name="Switzerland" /> </country> <country name="Singapore"> <rank updated="yes">5</rank> <year>2011</year> <gdppc>88888</gdppc> <neighbor direction="N" name="Malaysia" /> </country> <country name="Panama"> <rank updated="yes">69</rank> <year>2011</year> <gdppc>13600</gdppc> <neighbor direction="W" name="Costa Rica" /> <neighbor direction="E" name="Colombia" /> </country> </data>
""" {"countrys": [ { "rank": 2, "year": 2008, "gdppc": 141100 }, {}, {} ]} { "data": { "countrys": [ {}, {}, {} ] } } """ # xml的文件解析 => 将xml转化为json类型的数据 # dict list => json import xml.etree.ElementTree as ET # 读文件 tree = ET.parse("my.xml") print(tree) # xml.etree.ElementTree.ElementTree # <xml.etree.ElementTree.ElementTree object at 0x0000000001EACA58> # 根节点 root_ele = tree.getroot() print(root_ele) # Element 'data':<Element 'data' at 0x0000000001DBD4A8> # 遍历往下 # print(root_ele[1]) for ele in root_ele: print(ele, ele.attrib) if ele.attrib['name'] == 'Singapore': for e in ele: print(e, e.tag) if e.tag == 'gdppc': print(e.text) e.text = '6666' # 只修改了内容 # 全文搜索指定名的子标签 # ele.iter("标签名") # 非全文查找满足条件的第一个子标签 # ele.find("标签名") # 非全文查找满足条件的所有子标签 # ele.findall("标签名") # print('==============') # cs = root_ele.iter('country') # for c in cs: # print(c) # print('==============') # print(root_ele.find('country').attrib) # {'name': 'Liechtenstein'} # print(root_ele.find('rank').attrib) # 不能跨标签取,只能取子标签 # print('==============') # print(root_ele.findall('country')) # 将内存的数据重新写入文件 tree.write("my.xml")
# 读
data = {'countrys': []}
tree = ET.parse("my.xml")
root = tree.getroot()
for ele in root:
country = {}
for e in ele:
if e.text and e.text.strip():
country[e.tag] = e.text
data['countrys'].append(country)
print(data)
十四、Excel操作
# Excel 年终报表 教学部 市场部 咨询部 总计 Jan-19 10 15 5 30 Feb-19 11 20 5 36 Mar-19 12 25 5 42 Apr-19 13 30 5 48 May-19 14 35 5 54 Jun-19 15 40 5 60 Jul-19 16 45 5 66 Aug-19 17 50 5 72 Sep-19 18 55 5 78 Oct-19 19 60 5 84 Nov-19 20 65 5 90 Dec-19 21 70 5 96
基本操作
imimport xlrd # 读取文件 work_book = xlrd.open_workbook("my.xlsx") # 获取所有所有表格名称 print(work_book.sheet_names()) # 选取一个表 sheet = work_book.sheet_by_index(0) # 表格名称 print(sheet.name) # 行数 print(sheet.nrows) # 列数 print(sheet.ncols) # 某行全部 print(sheet.row(6)) print(sheet.row(13)) # 某列全部 print(sheet.col(4)) # 某行列区间 print(sheet.row_slice(6, start_colx=0, end_colx=4)) # 某列行区间 print(sheet.col_slice(3, start_rowx=0, end_rowx=3)) # 某行类型0:空 1:str 2:num 3:date | 值 print(sheet.row_types(1), sheet.row_values(6)) # 单元格 print(sheet.cell(6,0).value) # 取值 print(sheet.cell(6,0).ctype) # 取类型 print(sheet.cell_value(6,0)) # 直接取值 print(sheet.cell_type(6,0)) # 直接取类型 # 0:以1900年为基准 1:以1904年为基准 print(xlrd.xldate_as_datetime(sheet.cell(6, 0).value, 0))
import xlwt # 创建工作簿 work = xlwt.Workbook() # 创建一个表 sheet = work.add_sheet("员工信息数据") # 创建一个字体对象 # font = xlwt.Font() # font.name = "Times New Roman" # 字体名称 # font.bold = True # 加粗 # font.italic = True # 斜体 # font.underline = True # 下划线 # 创建一个样式对象 # style = xlwt.XFStyle() # style.font = font keys = ['Owen', 'Zero', 'Egon', 'Liuxx', 'Yhh'] # 写入标题 c = 0 for k in keys: # sheet.write(0, keys.index(k), k, style) # sheet.write(0, keys.index(k), k) sheet.write(keys.index(k) + 5, 2, k if k != 'Egon' else 'cool') # sheet.write(c, c, k) # c += 1 # 写入数据 # sheet.write(1, 0, 'cool', style) # sheet.write(0, 0, 'cool') # 保存至文件 work.save("new_my.xls")
python3中
# 创建、写入 from openpyxl import Workbook,load_workbook wb = Workbook() sheet = wb.active sheet.title = "Simon" sheet["B9"] = "black girl" #sheet.append(["xc","xc","xc"]) wb.save("test.xlsx") #打开已有文件 wb = load_workbook("test.xlsx") print(wb.sheetnames) # print(wb.get_sheet_names()) # sheet = wb.get_sheet_by_name("Simon") sheet = wb["Simon"] print(sheet["B9"]) """ 结果 ['Simon'] <Cell 'Simon'.B9> """ for cell in sheet["B1:B9"]: # 指定列的切片数据 print(cell[0].value) # 按行遍历 for row in sheet: for cell in row: print(cell.value, end=",") print() # 按列遍历A1、A2、A3 for column in wb.columns: for cell in column: print(cell.value,end=",") # 按列循环 for col in sheet.iter_cols(min_col==2,max_col=5,min_row=2,max_row=10): for cell in col: print(cell.value, end=",") print() # 遍历指定行或列 # 从第2行到第5行,每行打印5列 for row in sheet.iter_rows(min_row=2,max_row=5,max_col=5): for cell in row: print(cell.value, end=",") print()