模块
在模块中,我们可以定义变量、函数,还有类(这个还没学到)。总之应该就是所有的代码。先建一个文件,取名为module,py,内容如下:
# 一下是module.py的内容 string = "This is module,py" def say_hi(): print("Hi") def test(): return "test in module.py"
在上面的模块中我们定义了1个变量和2个函数,现在我们要在另外一个文件中导入这个模块的一部分或者全部
导入模块
import 导入模块
import module print(module.string) module.say_hi()
上面就可以导入并且调用模块中的方法。
如果需要导入多个模块,可以一次import全部导入,只需要用逗号隔开
import module_name1,module_name2,module_name3
from import 导入模块
from module import string,test print(string) print(test())
向上面这样,可以指定导入一部分,而不用全部导入。但是调用的方法也不同了,这里需要注意。
from module import *
使用*可以一次全部导入,但是这个用法不推荐使用。或者在模块中定义一个__all__的列表(这部分上课没讲),那么*只能导入all列表中存在的对象。
之所以不推荐使用,主要还是调用的方法导致的问题。如果在调用的文件中也有一个同名的变量,就会替换掉import来的变量,比如下面这样:
from module import string,test string = "This is main" def test(): return "test in main" print(string) print(test())
所以使用*这种不可控的导入方式,不推荐。但是还是没解决调用文件和导入文件里变量同名的问题,如果2边都不能改的话。这里还需要用到下面的as
form import as 导入模块
from module import test as module_test def test(): return "test in main" print(test()) print(module_test())
上面使用as起了别名后,就没有同名的问题了。不过这里没法同时导入多个
导入优化(我不信!)
用import导入文件的话,每次调用的时候都会去环境变量中查找一次该文件。如果多次调用,那么会降低程序运行的效率。
用from import后,直接导入了函数的代码,每次调用就不必再去环境变量中查找变量了,这样程序运行效率就会高一点
包、导入包
在创建许多模块后,我们需要将某些功能相近的文件组织在同一文件夹下,这就是一个包。
python包就是,一个有层次的文件目录结构,一个包含__init__.py 文件的目录,该目录下一定得有这个__init__.py文件和其它模块或子包。
从包中导入模块的方法和导入模块一样:
import PackageA.SubPackageA.ModuleA # 使用时必须用全路径名 from PackageA.SubPackageA import ModuleA # 可以直接使用模块名而不用加上包前缀 from PackageA.SubPackageA.ModuleA import functionA # 直接导入模块中的函数
也可以用*,但是要这样用,必须在包下的__init__.py文件中,定义好__all__列表,包的情况这个变量不能省略,否则不能用*
from pacakge import *
另外直接import一个包,是不会导入包下的模块的。
import PackageA
除非你编辑一下PackageA下的__init__.py文件,文件里加一行
from . import ModuleA
上面的.表示__init__.py所在的当前目录,也可以用..表示上一级目录。import不能用*(定义了__all__也没用),必须指定模块名。调用的时候还是要Packagea.ModuleA.functionA(),因为上面那句是加在__init__里的。
但是还是不要这么用了,上课也没完全讲清楚。
import 的本质
import的本质就是,把导入的模块运行一遍。
一般这个模块下都是定义的变量和函数,所以并不会直接运行这些函数,但是如果模块中有可运行的代码,也是会在import的时候被运行的
import包的本质就是,运行包下的__init__.py这个文件
__init__.py文件可以为空,一般为空。其实为空的话,也可以干脆不要这个文件。不过有可以文件做标记可以把这个文件夹和普通的文件夹区分开来。有这个文件,它就是一包。
如果里面有内容,那么会在import 或者 from 这个包的时候,执行里面的内容。这种情况:import PackageA.SubPackageA.ModuleA 会执行每一层包里的每一个__init__.py文件
但是并不是每次import都会执行一编模块或者__init__,只有在第一次import的时候才会执行。
__name__ 变量
要让你的文件既可以被作为包调用,又可以直接运行,但是不要在被调用的时候运行起来,需要用到__name__这个变量。
如果一个文件被作为程序运行的时候,__name__ == ‘__main__‘
如果是作为模块被调用的时候,__name__ == ‘模块名‘ or ‘包名.模块名‘,取决于模块的位置
一般我们只需要判断是不是‘__main__‘。加上if判断,保证你文件中的这段代码在文件被作为模块import的时候,不会被执行。
经常使用if __name__ == ‘__main__‘
,保证你写的文件既可以被import又可以独立运行,用于test。
内置模块
time模块
time.time() # 返回时间戳
time.sleep(n) # 停止n秒
time.gmtime(seconds) #返回一个元祖,UTC时间。参数是时间戳,默认当前时间即time.time()。一般都需要本地时间,看下面一个
时间戳和时间元祖的互相转换:
time.localtime(seconds) # 时间戳转为元祖
time.mktime(tuple) # 元祖转为时间戳
时间元祖和时间字符串的互相转换:
time.strftime(format,tuple) # format是字符串的格式,后面的元祖可以省略,省略就是当前时间
time.strptime(string,format) # string是表示时间的字符串,format是这个字符串的格式
下面2个函数是分别将元祖和时间戳转为字符串,字符串格式不能自定义,只能是这种格式:‘Sat Jun 06 16:26:11 1998‘
time.asctime(tuple) # 缺省元祖就是当前时间
time.ctime(seconds) # 缺省时间戳就是当前时间,所以在缺省参数的情况下,和上面的结果一样
datetime模块
这个主要就将了2个函数:
import datetime print(datetime.datetime.now()) # 获取当前时间 print(datetime.timedelta(3)) # 先看一下效果,不是这么用的 print(datetime.timedelta(2,3,4,5,6,7,1)) # 一共7个参数,天、微妙、毫秒、秒、分、小时、周
timedelta一般是结合now来计算一个过去或者将来的时间的:
import datetime print(datetime.datetime.now()) # 获取当前时间 print(datetime.datetime.now() + datetime.timedelta(3)) # 3天后的时间 print(datetime.datetime.now() + datetime.timedelta(-3)) # 3天前的时间 print(datetime.datetime.now() + datetime.timedelta(hours=4)) # 4小时后的时间 print(datetime.datetime.now() + datetime.timedelta(minutes=5)) # 5分钟后的时间
random模块
import random print(random.random()) # 生成一个[0,1)范围的随机浮点数 print(random.uniform(1,2)) # 基本和上面一样,但是有参数可以指定区间 print(random.randint(1,3)) # 生成一个随机整数。如果a是参数1,b是参数2,结果是n,则a<=n<=b print(random.randrange(1,7,2)) # 参数和range()一样,分别是开始、结束、步长,同样也不包括结束的值 print(random.choice([1,2,3,4,5])) # 参数可以是字符串、列表、元祖这样的序列 print(random.sample([1,2,3,4,5],3)) # 参数1和上面一样,参数2表示取多少位。如果参数2等于长度,那么结果也是随机排序的
最后还有一个洗牌的函数:
import random list1 = [1,2,3,4,5] print(list1) random.shuffle(list1) # 将参数里的变量随机排序了 print(list1) # 看新的结果
random可以用来生成随机验证码,下面的例子只是应用一下这个模块,验证码功能还不够好:
import random checkcode = ‘‘ for i in range(4): current = random.randint(97,122) # ASCII表中这个范围是小写字母 checkcode = "%s%s"%(checkcode,chr(current)) # 用chr将数字根据ASCII转成字母 print(checkcode)
os模块
提供对操作系统进行调用的接口
- os.getcwd() 获取当前工作目录,即当前python脚本工作的目录路径
- os.chdir("dirname") 改变当前脚本工作目录;相当于shell下cd
- os.curdir 返回当前目录: (‘.‘)
- os.pardir 获取当前目录的父目录字符串名:(‘..‘)
- os.makedirs(‘dirname1/dirname2‘) 可生成多层递归目录
- os.removedirs(‘dirname1‘) 若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推
- os.mkdir(‘dirname‘) 生成单级目录;相当于shell中mkdir dirname
- os.rmdir(‘dirname‘) 删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir dirname
- os.listdir(‘dirname‘) 列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印
- os.remove() 删除一个文件
- os.rename("oldname","newname") 重命名文件/目录
- os.stat(‘path/filename‘) 获取文件/目录信息
- os.sep 输出操作系统特定的路径分隔符,win下为"\\",Linux下为"/"
- os.linesep 输出当前平台使用的行终止符,win下为"\t\n",Linux下为"\n"
- os.pathsep 输出用于分割文件路径的字符串
- os.name 输出字符串指示当前使用平台。win->‘nt‘; Linux->‘posix‘
- os.system("bash command") 运行shell命令,直接显示
- os.environ 获取系统环境变量
- os.path.abspath(path) 返回path规范化的绝对路径
- os.path.split(path) 将path分割成目录和文件名二元组返回
- os.path.dirname(path) 返回path的目录。其实就是os.path.split(path)的第一个元素
- os.path.basename(path) 返回path最后的文件名。如何path以/或\结尾,那么就会返回空值。即os.path.split(path)的第二个元素
- os.path.exists(path) 如果path存在,返回True;如果path不存在,返回False
- os.path.isabs(path) 如果path是绝对路径,返回True
- os.path.isfile(path) 如果path是一个存在的文件,返回True。否则返回False
- os.path.isdir(path) 如果path是一个存在的目录,则返回True。否则返回False
- os.path.join(path1[, path2[, ...]]) 将多个路径组合后返回,第一个绝对路径之前的参数将被忽略
- os.path.getatime(path) 返回path所指向的文件或者目录的最后存取时间
- os.path.getmtime(path) 返回path所指向的文件或者目录的最后修改时间
sys模块
- sys.argv 命令行参数List,第一个元素是程序本身路径
- sys.exit(n) 退出程序,正常退出时exit(0)
- sys.version 获取Python解释程序的版本信息
- sys.maxint 最大的Int值
- sys.path 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值
- sys.platform 返回操作系统平台名称
sys.stdout:平时用的print实际就是通过sys.stdout来输出的
import sys sys.stdout.write("Hello") sys.stdout.write("Hello\n") # 这句和下面的print是一样的 print("Hello") # print实际也是在字符串后面加上换行后,再调用stdout.write
sys.stdin:平时用的input实际上就是先print一段内容,然后再捕获屏幕上的输入:
import sys val = input(‘Hello‘) # 上面的和下面的是一样的 print(‘Hello‘) # 这里的效果有的差别,print之后有个换行 val = sys.stdin.readline()[:-1] # 这里切掉了你最后敲的那个回车符
sys.stdout主要是可以用来重定向输出。可以从控制台重定向到文件,或者同时定向到控制台和文件(这个貌似有点复杂),实现日志记录。
同样的sys.stdin和sys.stderr也应该可以重定向,
shutil模块
高级的文件、文件夹、压缩包处理模块
shutil.copyfileobj(fsrc, fdst[, length])
将文件内容拷贝到另一个文件中。这里只是拷贝文件的内容,所以要把2个文件先打开。参数1和参数2是这两个文件的句柄。
import shutil with open(‘test.txt‘,encoding=‘utf-8‘) as f1, open(‘test2.txt‘,‘w‘,encoding=‘utf-8‘) as f2: shutil.copyfileobj(f1,f2)
shutil.copyfile(src, dst)
拷贝文件,这个比较简单,直接输入原文件名和目标文件名就可以了
import shutil shutil.copyfile(‘test.txt‘,‘test3.txt‘)
shutil.copymode(src, dst)
仅拷贝权限。内容、组、用户均不变。就是linux里的chmod的权限(ugo权限)
shutil.copystat(src, dst)
拷贝状态的信息,包括:mode bits, atime, mtime, flags
shutil.copy(src, dst)
拷贝文件和权限。就是copyfile,然后copymod
shutil.copy2(src, dst)
拷贝文件和状态信息。就是copyfile,然后copystat
shutil.ignore_patterns(*patterns)
这个略...
shutil.copytree(src, dst, symlinks=False, ignore=None)
递归的去拷贝文件
src:原目录
dst:目标目录
symlinks:这个默认就好,是处理链接的情况,目标目录里仍然是链接。如果改成True应该会把链接的文件拷贝过去
ignore:忽略哪些文件,让我想到了自动备份,有些扩展名或者目录我是不需要拷贝的。
shutil.rmtree(path[, ignore_errors[, onerror]])
递归的去删除文件
shutil.move(src, dst)
递归的移动文件
shutil.make_archive(base_name, format,...)
创建压缩包并返回文件路径
- base_name: 压缩包的文件名,也可以是压缩包的路径。只是文件名时,则保存至当前目录,否则保存至指定路径,
如:www =>保存至当前路径
如:/Users/wupeiqi/www =>保存至/Users/wupeiqi/ - format: 压缩包种类,“zip”, “tar”, “bztar”,“gztar”
- root_dir: 要压缩的文件夹路径(默认当前目录)
- owner: 用户,默认当前用户
- group: 组,默认当前组
- logger: 用于记录日志,通常是logging.Logger对象
import shutil res = shutil.make_archive(‘test‘,‘gztar‘,‘TXT‘) print(res)
当前文件夹下有一个名为TXT的文件夹,将这个文件夹打包压缩,在当前目录下生成了一个test.tar.gz的压缩文件
json 和 pickle 模块
用户序列化的两个模块,之前的课已经将过了
- json,用于字符串 和 python数据类型间进行转换
- pickle,用于python特有的类型 和 python的数据类型间进行转换
两个模块都提供了名字一样的四个功能,dumps、dump、loads、load,效果也差不多(适用范围不同)。
shelve模块
一个简单的key,value将内存数据通过文件持久化的模块,可以持久化任何pickle可支持的python数据格式
直接抄个例子吧。
import shelve d = shelve.open(‘shelve_test‘) #打开一个文件 class Test(object): def __init__(self,n): self.n = n t = Test(123) t2 = Test(123334) name = ["alex","rain","test"] d["test"] = name #持久化列表 d["t1"] = t #持久化类 d["t2"] = t2 d["str1"] = "abc" d["int1"] = 123 d.close()
取回数据:
接着上面的例子,把数据都取回。另外可以用d.items()取回全部。
import shelve d = shelve.open(‘shelve_test‘) #打开一个文件 print(d.get("test")) print(d.get("str1")) print(d.get("int1")) d.close()
xml模块
古时候用的,以前没有JSON,现在有了,遇到了再去查吧
PyYAML
这不是一个标准库,需要的时候还得去下载。处理yaml文档格式,这种格式主要是做配置文件用的
参考文档:http://pyyaml.org/wiki/PyYAMLDocumentation
configparser模块
用于生产和修改配置文档,一般是ini扩展名。有时候扩展名也可能是.cfg、.conf、.txt,文本格式就是个纯文本文件,只是文本内容要遵循一定的格式。
这也是一种常见的格式,来看一个好多软件的常见文档格式如下:
[DEFAULT]
ServerAliveInterval = 45
Compression = yes
CompressionLevel = 9
ForwardX11 = yes
[bitbucket.org]
User = hg
[topsecret.server.com]
Port = 50022
ForwardX11 = no
下面用代码来生成一个这样的配置文件:
import configparser config = configparser.ConfigParser() config[‘DEFAULT‘] = {‘ServerAliveInterval‘:‘45‘, ‘Compression‘:‘yes‘, ‘CompressionLevel‘:9} # 字典里的数字可以不加引号,不过到文件里肯定还是一样的 config[‘bitbucket.org‘] ={} # 这里也可以先建个空的,然后再定义里面的值 config[‘bitbucket.org‘][‘User‘] = ‘hg‘ config[‘topsecret.server.com‘] = {} topsecret = config[‘topsecret.server.com‘] # 每次的要打一串也很烦,所以这么弄 topsecret[‘Host Port‘] = ‘50022‘ # 这里必须是字符串,因为不支持数字格式 topsecret[‘ForwardX11‘] = ‘no‘ config[‘DEFAULT‘][‘ForwardX11‘] = ‘yes‘ with open(‘test.ini‘,‘w‘) as inifile: config.write(inifile)
下面用代码来取出配置:
import configparser config = configparser.ConfigParser() print(config.sections()) # 现在还是空的 config.read(‘test.ini‘) # 读取配置文件,保存在config里 print(config.defaults()) print(config.sections()) # 打印节点,读到了2个,没有DEFAULT,也没有下面的详细内容 print(config[‘bitbucket.org‘][‘user‘]) # 读取具体的某一条配置信息 topsecret = config[‘topsecret.server.com‘] # 还是嫌名字太长,很麻烦,就这么弄 print(topsecret[‘forwardx11‘]) for key in config[‘bitbucket.org‘]: # 节点会继承DEFAULT的属性,没有就继承,有就替代 print(key) # bitbucket.org下面值定义了一个属性,但是这里继承了DEFAULT的所有属性 print(config[‘bitbucket.org‘][‘forwardx11‘]) # 这里继承了DEFAULT的属性,所以有值,是yes
当然还可以对配置文件进行增删改查,真要改的时候再说吧,一般都是手动修改的。
hashlib模块
用于加密相关的操作,3.x里代替了md5模块和sha模块,主要提供 SHA1, SHA224, SHA256, SHA384, SHA512, MD5 算法。所以上面这些格式用这个就好了。
import hashlib m = hashlib.md5() # 别的格式也一样,只要替换这里的名字就好。比如:hashlib.sha1() m.update(b‘Hello‘) print(m.digest()) # 2进制格式hash,也有16进制的方法 m.update(b"It‘s me") # update()是继续往里追加 print(m.digest()) m2 = hashlib.md5() m2.update(b"HelloIt‘s me") print(m2.digest()) # 这里的输出应该和上面一样 print(m2.hexdigest()) # 16进制格式hash,貌似16进制用的多
如果要进行运算的不是ascii码,就需要用encode转成bytes数据类型
import hashlib m = hashlib.sha512() # 试个sha512的 m.update(‘你好‘.encode(encoding=‘utf-8‘)) # 数据类型必须是bytes print(m.hexdigest())
下面这个是可以进一步加密的模块,更高的安全性。
hmac模块
散列消息鉴别码,简称HMAC,是一种基于消息鉴别码MAC(Message Authentication Code)的鉴别机制。使用HMAC时,消息通讯的双方,通过验证消息中加入的鉴别密钥Key来鉴别消息的真伪。
一般用于网络通信中消息加密,前提是双方先要约定好key,就像接头暗号一样,然后消息发送方用key把消息加密,接收方用key + 消息明文再加密,拿加密后的值跟发送者的相对比是否相等,这样就能验证消息的真实性,及发送者的合法性了。
假设约定的key是“一二三四五”,我们要发一条消息“上山打老虎”:
import hmac # 可以直接把key和消息一起生成hash h_send= hmac.new("一二三四五".encode(encoding=‘utf-8‘),"上山打老虎".encode(encoding=‘utf-8‘)) print(h_send.hexdigest()) # 也可以先把key导入 h_receive = hmac.new("一二三四五".encode(encoding=‘utf-8‘)) # 然后再用update()方法,把消息导入,生成hash h_receive.update("上山打老虎".encode(encoding=‘utf-8‘)) print(h_receive.hexdigest()) # 和上面的hash值是一样的 # 还有第三个参数,上面缺省了,默认是md5格式,如果要用别的格式,就补上参数 h_send= hmac.new("一二三四五".encode(encoding=‘utf-8‘),"上山打老虎".encode(encoding=‘utf-8‘),‘sha1‘) # 使用sha1加密 print(h_send.hexdigest()) h_receive = hmac.new("一二三四五".encode(encoding=‘utf-8‘),digestmod=‘sha1‘) # 这里没有第二个参数,貌似只能用关键参数 h_receive.update("上山打老虎".encode(encoding=‘utf-8‘)) print(h_receive.hexdigest()) # 和上面的hash值是一样的
hmac的应用:
hmac主要应用在身份验证是,它的使用方法是这样的:
- 客户端发出登录请求(假设是浏览器的GET请求)
- 服务器返回一个随机值,并在会话中记录这个随机值
- 客户端将该随机值作为密钥,用户密码进行hmac运算,然后提交给服务器
- 服务器读取用户数据库中的用户密码和步骤2中发送的随机值做与客户端一样的hmac运算,然后与用户发送的结果比较,如果结果一致则验证用户合法
散列算法仅适用于登录验证,但是对于最初的密码设置和以后密码修改的过程不适用。但是散列算法要比对称和非对称加密算法效率高。
加密的总结:上面的2个加密模块,应该都不是用来加密传数据的,因为加密后并不能解密,只是生成一个消息摘要,用来验证消息的完整性的。也可以理解为对消息生成一个数字签名,如果签名一致,则认为消息没有被修改过。
subprocess模块
运行linux的shelll命令,管理子进程。是对这些命令的替换 os.system 和 os.spawn* 。所以尽量用subprocess。没有展开讲
logging模块
用来记录日志的,这个很有用,也很重要。
日志分为5个级别,重要等级一次降低是:critical、error、warning、info、debug
简单的例子:
import logging logging.basicConfig(filename=‘test.log‘,level=logging.INFO) # 没有位置参数,必须用关键参数 logging.warning(‘test warning‘) logging.info(‘test info‘) logging.debug(‘test debug‘)
去看一下文件的内容,应该只有2行。因为参数level设置了只接收info等级及以上的日志,所以debug不会记录下来。
另外日志内容也很少,没有时间。有更加详细的参数可以定义日志的格式
日志格式:
%(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 |
用户输出的消息 |
下面的例子选了那些比较有用的格式,有日期、时间、模块名、代码行、日志级别和消息。
logging.basicConfig(filename=‘test.log‘, level=logging.DEBUG, format=‘%(asctime)s %(module)s-%(lineno)d [%(levelname)s]:%(message)s‘, datefmt=‘%Y-%m-%d %H:%M:%S‘) logging.warning(‘test warning‘) logging.info(‘test info‘) logging.debug(‘test debug‘)
默认的设置就很好用,但是只能输出到文件。如果想同时把log同时打印在屏幕上和文件里,需要自己创建一个logger。贴一点基础知识
Python 使用的logging模块记录日志涉及四个主要类,使用官方文档中的概括最为合适:
- logger提供了应用程序可以直接使用的接口;
- handler将(logger创建的)日志记录发送到合适的目的输出;
- filter提供了细度设备来决定输出哪条日志记录;
- formatter决定日志记录的最终输出格式。
每个类的介绍就不贴了,直接上例子:
import logging # 先创建一个logger logger = logging.getLogger(__name__) # 定义Logger的名字,之前直接用logging调用的名字是root,日志格式用%(name)s可以获得。这里的名字也可以自定义比如"TEST" logger.setLevel(logging.DEBUG) # 低于这个级别将被忽略,后面还可以设置输出级别 # 创建handler和输出级别 ch = logging.StreamHandler() # 输出到屏幕的handler ch.setLevel(logging.INFO) # 输出级别和上面的忽略级别都不一样,可以看一下效果 fh = logging.FileHandler(‘access.log‘,encoding=‘utf-8‘) # 输出到文件的handler,定义一下字符编码 fh.setLevel(logging.WARNING) # 创建日志格式,可以为每个handler创建不同的格式 ch_formatter = logging.Formatter(‘%(name)s %(asctime)s {%(levelname)s}:%(message)s‘,datefmt=‘%Y-%m-%d %H:%M:%S‘) # 关键参数datefmt自定义日期格式 fh_formatter = logging.Formatter(‘%(asctime)s %(module)s-%(lineno)d [%(levelname)s]:%(message)s‘,datefmt=‘%Y/%m/%d %H:%M:%S‘) # 把上面的日志格式和handler关联起来 ch.setFormatter(ch_formatter) fh.setFormatter(fh_formatter) # 将handler加入logger logger.addHandler(ch) logger.addHandler(fh) # 以上就完成了,下面来看一下输出的日志 logger.debug(‘logger test debug‘) logger.info(‘logger test info‘) logger.warning(‘logger test warning‘) logger.error(‘logger test error‘) logger.critical(‘logger test critical‘)
上面这个例子据说能满足90%的需求了。还需要一个日志文件轮训的功能。只需要用另外一个模块重新定义一个fh就好了,就改1句。
import logging from logging import handlers # 需要额外导入这个模块, # 还是要创建logger,这里不是必须的设置都省略了 logger = logging.getLogger(__name__) #fh = logging.FileHandler(‘access.log‘,encoding=‘utf-8‘) # 原来的代码,替换为下面2种,一个是看时间,一个是看大小 #fh = handlers.TimedRotatingFileHandler(filename=‘access.log‘,when="S",interval=5,backupCount=3) fh = handlers.RotatingFileHandler(filename=‘access.log‘,encoding=‘utf-8‘,maxBytes=100,backupCount=3) fh_formatter = logging.Formatter(‘%(asctime)s %(module)s-%(lineno)d [%(levelname)s]:%(message)s‘,datefmt=‘%Y/%m/%d %H:%M:%S‘) fh.setFormatter(fh_formatter) logger.addHandler(fh) # 以上就完成了,多输出几次 for i in range(10): logger.critical(‘logger test critical%d‘%i)
参数说明:
interval是时间间隔。
when参数是一个字符串。表示时间间隔的单位,不区分大小写。它有以下取值:
S 秒
M 分
H 小时
D 天
W 每星期(interval==0时代表星期一)
midnight 每天凌晨,就是每天一个日志文件,很方便。
maxBytes:用于指定日志文件的最大文件大小。如果maxBytes为0,意味着日志文件可以无限大,这时上面描述的重命名过程就不会发生
backupCount:用于指定保留的备份文件的个数。比如,如果指定为2,当上面描述的重命名过程发生时,原有的chat.log.2并不会被更名,而是被删除。
以上号称是能满足95%的需求了(我信了!)。剩下的是日志过滤,要用到四个类里的filter,号称很复杂,且用的不多,就没讲。
re模块
正则表达式,很重要的模块。
常用的正则表达式符号
字符 | 描述 |
---|---|
‘.‘ |
匹配除 "\n" 之外的任何单个字符。若指定flag DOTALL,则匹配任意字符,包括换行。 |
‘^‘ |
匹配输入字符串的开始位置。若指定flags MULTILINE,^ 也匹配 ‘\n‘ 或 ‘\r‘ 之后的位置。如:("^a","\nabc\neee",flags=re.MULTILINE) |
‘$‘ |
匹配输入字符串的结束位置。若指定flags MULTILINE,$ 也匹配 ‘\n‘ 或 ‘\r‘ 之前的位置。 |
‘*‘ |
匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等价于{0,}。 |
‘+‘ |
匹配前面的子表达式一次或多次。例如,‘zo+‘ 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。 |
‘?‘ |
匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 。? 等价于 {0,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{0,1}‘ 等价于 ‘o?‘。请注意在逗号和两个数之间不能有空格。 |
‘?‘ |
当该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 "oooo",‘o+?‘ 将匹配单个 "o",而 ‘o+‘ 将匹配所有 ‘o‘。 |
‘x|y‘ |
匹配 x 或 y。例如,‘z|food‘ 能匹配 "z" 或 "food"。‘(z|f)ood‘ 则匹配 "zood" 或 "food"。 |
‘[xyz]‘ |
字符集合。匹配所包含的任意一个字符。例如, ‘[abc]‘ 可以匹配 "plain" 中的 ‘a‘。 |
‘[^xyz]‘ |
负值字符集合。匹配未包含的任意字符。例如, ‘[^abc]‘ 可以匹配 "plain" 中的‘p‘、‘l‘、‘i‘、‘n‘。 |
‘[a-z]‘ |
字符范围。匹配指定范围内的任意字符。例如,‘[a-z]‘ 可以匹配 ‘a‘ 到 ‘z‘ 范围内的任意小写字母字符。 |
‘[^a-z]‘ |
负值字符范围。匹配任何不在指定范围内的任意字符。例如,‘[^a-z]‘ 可以匹配任何不在 ‘a‘ 到 ‘z‘ 范围内的任意字符。 |
‘\d‘ |
匹配一个数字字符。等价于 [0-9]。 |
‘\D‘ |
匹配一个非数字字符。等价于 [^0-9]。 |
‘\w‘ |
匹配字母、数字、下划线。等价于‘[A-Za-z0-9_]‘。 |
‘\W‘ |
匹配非字母、数字、下划线。等价于 ‘[^A-Za-z0-9_]‘。 |
‘\A‘ | 匹配字符开头,类似^,必须是字符串的开头,无法匹配‘\n‘之后的位置,忽略flags MULTILINE |
‘\Z‘ | 匹配字符结尾,类似$,必须是字符串的结尾,无法匹配‘\n‘之前的位置,忽略flags MULTILINE |
‘\s‘ |
匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。 |
‘\S‘ |
匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。 |
‘\t‘ |
匹配一个制表符。等价于 \x09 和 \cI。 |
‘\v‘ |
匹配一个垂直制表符。等价于 \x0b 和 \cK。 |
‘\f‘ |
匹配一个换页符。等价于 \x0c 和 \cL。 |
‘\n‘ |
匹配一个换行符。等价于 \x0a 和 \cJ。 |
‘\r‘ |
匹配一个回车符。等价于 \x0d 和 \cM。 |
分组匹配:
‘(...)‘ : 需要用group(),返回元祖
‘(?P<name>...)‘ :需要用groupdict(),返回字典。这里的‘name‘替换成你要定义的字典的Key
几个匹配模式:
- re.I (IGNORECASE):忽略大小写
- re.M(MULTLINE) :多行模式,主要影响^和$的匹配
- re.S(DOTALL) :点任意匹配模式,影响(.)点的匹配
上面的括号内是全拼,可以re.I这么写,也可以re.IGNORECASE这么写
常用的匹配语法:
- re.match:从头开始匹配
- re.search:匹配包含,一般都是用这个
- re.findall:把所有匹配到的字符放到列表中,以列表中的元素返回
- re.split:以匹配到的字符当做分隔符,返回列表
- re.sub:匹配字符并替换
例子:
match 和 search
import re test_match = re.match(‘abc‘,‘aabcde‘) # 从头开始匹配 test_search = re.search(‘abc‘,‘aabcde‘) # 匹配包含 print(test_match) # 匹配不到,因为不是以abc开头 #print(test_match.group()) # 这句会报错,匹配不到就是None.没有group属性 print(test_search) # 匹配到了,包含abc字符串 print(test_search.group()) # 要返回匹配到的字符串使用group()
^ 、$ 和 \A、\Z 以及多行模式
在没有换行的情况下,两个的作用是一样的,就看一下有换行的情况。
import re string1 = ‘abc\ndef‘ a1 = re.search(‘^def‘,string1) print(a1) # 不开启多行模式,匹配不到 b1 = re.search(‘^def‘,string1,re.M) print(b1) # 开启多行模式才能匹配到 c1 = re.search(‘\Adef‘,string1) print(c1) d1 = re.search(‘\Adef‘,string1,re.M) print(d1) # 用\A会忽略多行模式,都是匹配不到的 string2 = ‘abc\ndef\n‘ a2 = re.search(‘def$‘,string2) print(a2) # 这种有个换行符结尾的情况,$可以匹配到 b2 = re.search(‘def\Z‘,string2) print(b2) # 这种有个换行符结尾的情况,\Z就匹配不到 c2 = re.search(‘abc$‘,string2) print(c2) d2 = re.search(‘abc$‘,string2,re.M) print(d2) # 不过不是最后一个换行符,需要开启多行模式才能匹配
分组匹配
举一个身份证号码的例子
import re id = ‘姓名:XXX 身份证号码:31010119990919935x 籍贯:XXX 职业:XXX‘ a = re.search(‘(\d{6})(\d{4})(\d{2})(\d{2})\d{3}(\d|x)‘,id) print(a) print(a.group()) # 只是把身份证号码提取出来 print(a.groups()) # 这里实现了分组,分别是地址码、年、月、日,中间3我没用小括号,性别。
上面是元祖的形式返回,还可以用字典返回,需要定义Key
import re id = ‘姓名:XXX 身份证号码:31010119990919935x 籍贯:XXX 职业:XXX‘ a = re.search(‘(?P<city>\d{6})(?P<Year>\d{4})(?P<Month>\d{2})(?P<Day>\d{2})\d{3}(?P<Sex>\d|x)‘,id) print(a.groups()) # 用group输出还是一样 print(a.groupdict()) # 要用groupdict看字典
findall 和 split
import re string = ‘abc123abc123x0y9z8‘ test_find = re.findall(‘\d+‘,string) # ‘\d+‘是匹配连续的数字 test_split = re.split(‘\d+‘,string) print(test_find) # 返回了所有的数字组合 print(test_split) # 把所有的数字作为分隔符,相当于返回了所有的字母组合。由于split的特性,最后是数字结尾的,最后会有一个空字符元素 test_find2 = re.findall(‘[^\d]+‘,string) # [^]是对中括号这的字符集合取反 print(test_find2) # 和上面一样,返回了所有的非数字组合
sub 匹配并替换
sub(pattern, repl, string, count=0, flags=0)
pattern:要匹配的正则表达式
repl:要替换的字符串
string:带匹配和替换的字符串
count:匹配和替换的次数,默认0全部匹配
flags:3种匹配模式,默认不开启
import re string = r"C:\Users\Public\Pictures\test.jpg" string2 = re.sub(r‘\\‘,r‘/‘,string) # 将\替换成/ print(string2) string3 = re.sub(r‘\\‘,r‘/‘,string,2) # 规定只替换2次,默认是0全部替换 print(string3) text = "Alex is a goodboy , he is coll , clever , and so on..." text2 = re.sub(‘\s+,\s+‘,‘,‘,text) # 把字符串中(,)逗号前后的空字符都去掉 print(text) print(text2)
作业
作业一:ATM+购物商城程序
额度 15000或自定义
实现购物商城,买东西加入购物车,调用信用卡接口结账
可以提现,手续费5%
支持多账户登录
支持账户间转账
记录每月日常消费流水
提供还款接口
ATM记录操作日志
提供管理接口,包括添加账户、用户额度,冻结账户等。。。
用户认证用装饰器
补充说明:
- 使用软件开发目录规范来组织目录结构,存放代码、配置文件和数据
- 用户认证要用装饰器,验证登录状态要在每一个方法里都能用,这里要用装饰器。登录成功后会生成一个用户信息的全局变量,每次只要去调用一个这个全局变量就能验证用户的登录状态
- 购物商城之前的作业已经做过,可以直接拿来稍微改一下后使用
作业二:模拟计算器开发
实现加减乘除及拓号优先级解析
用户输入 1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )等类似公式后,必须自己解析里面的(),+,-,*,/符号和公式(不能调用eval等类似功能偷懒实现),运算后得出结果,结果必须与真实的计算器所得出的结果一致
补充说明:
- 练习正则的使用
- 先用正则找到()里的内容,把()计算出来替换掉
- 然后按级别,再解析乘除的运算
- 最后解析计算加减