模块

在模块中,我们可以定义变量、函数,还有类(这个还没学到)。总之应该就是所有的代码。先建一个文件,取名为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主要应用在身份验证是,它的使用方法是这样的:

  1. 客户端发出登录请求(假设是浏览器的GET请求)

  2. 服务器返回一个随机值,并在会话中记录这个随机值

  3. 客户端将该随机值作为密钥,用户密码进行hmac运算,然后提交给服务器

  4. 服务器读取用户数据库中的用户密码和步骤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模块记录日志涉及四个主要类,使用官方文档中的概括最为合适:

  1. logger提供了应用程序可以直接使用的接口;

  2. handler将(logger创建的)日志记录发送到合适的目的输出;

  3. filter提供了细度设备来决定输出哪条日志记录;

  4. 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. 用户认证要用装饰器,验证登录状态要在每一个方法里都能用,这里要用装饰器。登录成功后会生成一个用户信息的全局变量,每次只要去调用一个这个全局变量就能验证用户的登录状态

  3. 购物商城之前的作业已经做过,可以直接拿来稍微改一下后使用

作业二:模拟计算器开发

实现加减乘除及拓号优先级解析

用户输入 1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )等类似公式后,必须自己解析里面的(),+,-,*,/符号和公式(不能调用eval等类似功能偷懒实现),运算后得出结果,结果必须与真实的计算器所得出的结果一致

补充说明:

  1. 练习正则的使用

  2. 先用正则找到()里的内容,把()计算出来替换掉

  3. 然后按级别,再解析乘除的运算

  4. 最后解析计算加减