这一篇主要是学习python里面的模块,篇幅可能会比较长

模块的概念:在Python中,一个.py文件就称之为一个模块(Module)。

模块一共三种:

  • python标准库
  • 第三方模块
  • 应用程序自定义模块

模块导入方法(重要):

# 1、import 语句
import sys
import test # 导入其他模块时,解释器先通过sys.path搜索路径找到test.py,然后执行完test.py后将内容赋值给了test

print(test.add(1,2))    # 调用test模块内的方法
print(sys.path)

如果说test里面我只需要导入其中一个方法,并不需要导入整个模块的方法咋搞呢?

# 2、from…import 语句
# from test import *      # 导入模块内的所有方法、变量。。。(一般不推荐使用)
from test import add    # 从模块调用方法,只导入了具体方法,变量啥的都不会导入(推荐使用),但是会从上到下执行test.py文件
print(add(2,3))
from test import add as ad  # 给add方法起个别名
print(ad(1,2))

那如果后续模块很多了怎么办呢?此时就引入了包的概念,为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package)。

注意,每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录(文件夹),而不是一个包。__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块,而它的模块名就是对应包的名字

# 包内的模块调用
from package1.package2 import module    # 从包中引入模块
from package1.package2.module import function   # 从包下的模块引入方法
import package1     # 执行了package1下的__init__.py文件

 

import sys,os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))      # 返回当前目录的上一级目录
sys.path.append(BASE_DIR)   # 将总的大目录加进来,这样就可以直接通过大目录下的包进行调用

from test01 import loggertest
loggertest.timem()

一、time模块

# -*- coding:utf-8 -*-

import time

# print(help(time))
# 1、time()  返回时间戳
print(time.time())  # 返回当前时间的时间戳 1541819905.0988002

# 2、clock() 返回CPU处理时间
print(time.clock()) # 返回程序开始后cpu的处理时间  这里实际上cpu只处理了一个print所以是6.413790161951408e-07

for i in range(100000000):
    i*i
print(time.clock()) # 这里cpu处理花了7.8202384583044315

# 3、sleep() 等待
time.sleep(3)    # 等待3秒

# 4、gmtime()    以元祖形式返回UTC世界标准时间
# 5、localtime() 以元祖形式返回本地时间
print(time.gmtime())    # time.struct_time(tm_year=2018, tm_mon=11, tm_mday=10, tm_hour=3, tm_min=27, tm_sec=9, tm_wday=5, tm_yday=314, tm_isdst=0)
print(time.localtime()) # time.struct_time(tm_year=2018, tm_mon=11, tm_mday=10, tm_hour=11, tm_min=27, tm_sec=9, tm_wday=5, tm_yday=314, tm_isdst=0)
# 从这两个结果来看我们知道相差了8小时,实际上localtime()是以这种元祖的形式返回本地时间,而gmtime()是返回UTC世界标准时间,也就是本初子午线那的格林尼治时间

# 6、asctime()   将元祖格式的时间转换成字符串格式,如果不传参默认就是time.asctime(time.localtime())
print(time.asctime(time.gmtime()))  # 返回UTC世界标准时间,不过是按照这种格式Sat Nov 10 03:34:26 2018

# 7、ctime()  将数字格式的时间转换成字符串格式,不传参默认就是time.ctime(time.time())
print(time.ctime(1))    # 代表从1970 1月1日 8点开始后1秒,所以返回Thu Jan  1 08:00:01 1970
# 注意asctime()和ctime()只是传入的参数不同,实际上返回的格式是一样的

# 8、mktime()   将元祖格式的时间转换成时间戳
print(time.mktime(time.localtime()))    # 返回当前时间戳,精度没有time.time()高 1541821331.0

# 9、strftime(format, tuple)  将元祖格式的时间转换成自定义格式的时间,这个还是比较实用的,不过似乎没看到毫秒的表达式
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))   #  以自定义字符串格式化返回当前时间,可以不写time.localtime(),默认就是
# 下面是format中的含义
"""
%y 两位数的年份表示(00-99)
%Y 四位数的年份表示(000-9999)
%m 月份(01-12)
%d 月内中的一天(0-31)
%H 24小时制小时数(0-23)
%I 12小时制小时数(01-12)
%M 分钟数(00=59)
%S 秒(00-59)
%a 本地简化星期名称
%A 本地完整星期名称
%b 本地简化的月份名称
%B 本地完整的月份名称
%c 本地相应的日期表示和时间表示
%j 年内的一天(001-366)
%p 本地A.M.或P.M.的等价符
%U 一年中的星期数(00-53)星期天为星期的开始
%w 星期(0-6),星期天为星期的开始
%W 一年中的星期数(00-53)星期一为星期的开始
%x 本地相应的日期表示
%X 本地相应的时间表示
%Z 当前时区的名称
%% %号本身
"""

# 10、time.strptime(string, format)  将字符串格式时间通过format解析成元祖格式时间
print(time.strptime("2018-11-10 11:52:56", "%Y-%m-%d %H:%M:%S"))   # 所以这里返回time.struct_time(tm_year=2018, tm_mon=11, tm_mday=10, tm_hour=11, tm_min=52, tm_sec=56, tm_wday=5, tm_yday=314, tm_isdst=-1)

# 11、tzset()    根据环境变量TZ重新初始化时间相关设置,使用很少,可百度查阅资料

下面是时间关系的转换图: 

 

二、datetime模块

# -*- coding:utf-8 -*-

import datetime,time

print(datetime.datetime.now())   # 返回当前时间,精确度很高2018-11-10 14:54:39.476800
print(datetime.date.fromtimestamp(time.time()))  # 转化成2018-11-10
print(datetime.datetime.now() + datetime.timedelta(3))  # 当前时间+3天
print(datetime.datetime.now() + datetime.timedelta(-3))  # 当前时间-3天
print(datetime.datetime.now() + datetime.timedelta(hours=-3))  # 当前时间-3小时
print(datetime.datetime.now() + datetime.timedelta(minutes=3))  # 当前时间+3分钟
print(datetime.datetime.now() + datetime.timedelta(weeks=3))  # 当前时间+3星期

print(datetime.datetime.now().replace(month=3, day=4, hour=5, minute=6))    # 将月日时分分别替换了,变成了2018-03-04 05:06:23.489800

 

三、random模块 

# -*- coding:utf-8 -*-

import random

#  print(help(random))
# print(random.random())  # 随机生成0-1之间的一个数,比如0.2513054977155842
# print(random.randint(1,3))  # 随机在1-3之间取一个数字,比如3,注意这个包括右边
# print(random.randrange(1, 3))  # 随机在1-2直接取一个数字,比如2,注意这个不包括右边
# print(random.sample([[1,2,3], "test", 2, 3],2)) # 随机从列表中取2个值,并以列表形式返回,比如['test', 3]
# print(random.choice([[1,2,3], "test", 2]))  # 从列表[[1,2,3], "test", 2]中随机取一个,比如[1,2,3]
# print(random.choices([[1,2,3], "test", 2])) # 跟上面类似,不过返回的是列表,比如[test]
# print(random.choice("test"))    # 从字符串中取一个字符,比如e

# 做一个简单的练习,生成一个4位的随机验证码,包含字母和数字
def check_code(n):
    # ord()函数就是用来返回单个字符的ascii值(0-255)或者unicode数值(),chr()则相反
    random_str = chr(random.randint(ord("A"), ord("Z")))    # 生成随机一个字母
    id_code = ''
    for i in range(n):
        id_code+=str(random.choice([random.randint(0,9), random_str]))
    return id_code

a = check_code(4)
print(a)

 

四、OS模块

# -*- coding:utf-8 -*-

import os

print(os.getcwd())  # 获取当前的工作目录
os.chdir(r"C:\Users")   # 切换至C:\Users目录,r这里表示后面的字符串就是原生字符串,直接读取字符串
print(os.curdir)    # 返回当前目录"."
print(os.pardir)    # 返回当前目录的父目录".."
os.makedirs("liu\\long\\kang")    # 在当前目录下生成多层递归目录,如果存在了还创建则报错
os.removedirs("liu\\long\\kang")    # 若目录为空则删除,并递归去判断上一级目录,如果为空也删除,一直递归到非空目录,
                                    # 举个例子如果long下面有其它文件,则只删除kang这个目录,如果都是空目录则全部删除
os.mkdir("liu") # 生成单个文件夹(目录),如果存在了还创建则报错
os.rmdir("liu") # 删除单个空目录
os.listdir(dirname)  # 列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印,注意层级目录下的不会列出
os.remove("test.py") # 只能删除单个文件,不能删除目录
os.rename("oldname", "newname") # 重命名文件/目录
print(os.stat('path/filename'))   # 获取文件/目录信息
dir_info = os.stat(os.curdir)
print(dir_info) # os.stat_result(st_mode=16895, st_ino=16044073672586573, st_dev=699768, st_nlink=1, st_uid=0, st_gid=0, st_size=4096, st_atime=1541857420, st_mtime=1541857420, st_ctime=1541596941)
print(dir_info.st_size) # 目录大小,其中st_atime=1541857420(文件中的数据最后被访问的时间), st_mtime=1541857420(文件内容被修改的最后时间), st_ctime=1541596941(显示的是文件的权限等改变时的时间)
print(os.sep)   # 输出操作系统特定的路径分隔符,win下为"\\",Linux下为"/"
print(os.linesep)    # 输出当前平台使用的行终止符,win下为"\r\n",Linux下为"\n"
print(os.pathsep)    # 输出用于分割文件路径的字符串,win下为";",Linux下为":"
print(os.name)    # 输出字符串指示当前使用平台。win->'nt'; Linux->'posix'
os.system("bash command")  # 运行shell命令,直接显示
os.system("dir")    # 相当于在cmd运行dir,打印当前目录在所有文件
print(os.environ)   # 获取系统的环境变量,返回的是字典
print(os.environ['APPDATA'])    # 取出相应的变量值
os.path.abspath(path)  # 返回path规范化的绝对路径
os.path.split(path) # 将path分割成目录和文件名二元组返回
os.path.dirname(path)  # 返回path的目录。其实就是os.path.split(path)的第一个元素
print(os.path.dirname(__file__))   # __file__是当前文件的绝对路径,所以返回当前文件的目录的绝对路径
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[, ...]])  # 将多个路径组合后返回,第一个绝对路径之前的参数将被忽略
print(os.path.join("c:\\user", "liu\long\kang"))    # 打印c:\user\liu\long\kang
os.path.getatime(path)  # 返回path所指向的文件或者目录的最后存取时间
os.path.getmtime(path)  # 返回path所指向的文件或者目录的最后修改时间

 

五、sys模块

# -*- coding:utf-8 -*-
import sys

sys.argv    # 执行脚本时后面加的参数这个方法能接收,并以列表方式返回,第一个参数为文件本身,第二个开始才是用户填写的参数
            #比如,python xxx.py  test  liu ,这样sys.argv=['xxx.py', 'test', 'liu']
sys.exit(n) # 退出程序,正常退出时exit(0),默认是正常退出
sys.version     # 获取Python解释程序的版本信息
sys.maxsize     # 最大的Int值
sys.path         # 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值,是一个列表,可以通过append添加自定义的模块
sys.platform     # 返回操作系统平台名称,可以根据不同的操作系统执行不同命令

sys.stdout.write('please:') # 在python中调用print时,事实上调用了sys.stdout.write(obj+'\n')
sys.stdout.flush()  # python的stdout是有缓冲区的,如果把这句话sys.stdout.flush()注释的话,你就只能等到程序执行完毕,屏幕上会一次性输出
val = sys.stdin.readline()[:-1]
# sys.stdin.readline( )会将标准输入全部获取,包括末尾的'\n',因此用len计算长度时是把换行符'\n'算进去了的,但是input( )获取输入时返回的结果是不包含末尾的换行符'\n'的。
# 因此如果在平时使用sys.stdin.readline( )获取输入的话,不要忘了去掉末尾的换行符,可以用strip( )函数(sys.stdin.readline( ).strip('\n'))或sys.stdin.readline( )[:-1]这两种方法去掉换行
print(val)

 

六、hashlib加密模块 

# -*- coding:utf-8 -*-
import hashlib

hash = hashlib.md5()    # md5算法的对象
hash.update("hello world".encode("utf-8"))  # 将hello world加密,py3默认是unicode,加密前需要先转化成byte类型
print(hash.hexdigest()) # 以十六进制返回,5eb63bbbe01eeed093cb22bb8f5acdc3

hash.update("test".encode("utf-8"))
print(hash.hexdigest()) # f208a28ca02d96e08bd162a0e3e00b7d
# 其实上面最后加密的f208a28ca02d96e08bd162a0e3e00b7d就是"hello worldtest"的加密

#除了md5加密还有sha加密,常用的是sha256,还有sha1、sha384。。。
hash_sha = hashlib.sha256()
hash_sha.update("hello world".encode("utf-8"))
print(hash_sha.hexdigest()) # b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9

# 以上加密算法虽然依然非常厉害,但时候存在缺陷,即:通过撞库可以反解。所以,有必要对加密算法中添加自定义key再来做加密。
hash_md5 = hashlib.md5('liu'.encode("utf-8"))
hash_sha.update("hello world".encode("utf-8"))
print(hash_sha.hexdigest())

 

 七、logging日志模块

# -*- coding:utf-8 -*-
import logging

# 下面是简单的日志打印
logging.debug("debug test")
logging.info("info test")
logging.warning("warn test")
logging.error("error test")
logging.critical("critical test")  # CRITICAL 表示产生了不可逆的错误,系统无法正常工作

# 结果输出到控制台
# WARNING:root:warn test
# ERROR:root:error test
# CRITICAL:root:critical test
# 这里说明默认的日志级别设置为WARNING(日志级别等级CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET)

  这里是直接输出到控制台,那如果我们想要保持到文本内呢?这里就需要修改配置了

logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s %(filename)s [line:%(lineno)d] %(levelname)s %(message)s',  # 指定日志输出的格式
                    datefmt='%a, %d %b %Y %H:%M:%S',  # 指定时间格式
                    filename='test.log',    # 日志路径,没有则输出到控制台
                    filemode='a')   # 日志写入方式,默认就是a追加

logging.debug('debug message')  # 写入格式为 Sun, 11 Nov 2018 20:22:56 logging模块.py [line:23] DEBUG debug message
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')
# test.log就写入了上面5条,详细参数看下面
"""
可见在logging.basicConfig()函数中可通过具体参数来更改logging模块默认行为,可用参数有
filename:用指定的文件名创建FiledHandler(后边会具体讲解handler的概念),这样日志会被存储在指定的文件中。
filemode:文件打开方式,在指定了filename时使用这个参数,默认值为“a”还可指定为“w”。
format:指定handler使用的日志显示格式。 
datefmt:指定日期时间格式。 
level:设置rootlogger(后边会讲解具体概念)的日志级别 
stream:用指定的stream创建StreamHandler。可以指定输出到sys.stderr,sys.stdout或者文件(f=open('test.log','w')),默认为sys.stderr。若同时列出了filename和stream两个参数,则stream参数会被忽略。

format参数中可能用到的格式化串:
%(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用户输出的消息
"""

  上面我们就将日志写入到了日文件中,但是却发现日志没有在界面上打印,咦,那我想同时又打印又写入日志文件咋搞呢?

# -*- coding:utf-8 -*-

import logging

logger = logging.getLogger("liu")   # 返回一个logger对象,如果没有指定名字将返回root logger

fh = logging.FileHandler("test.log")  # 创建一个handler,用于写入日志文件

ch = logging.StreamHandler()    # 再创建一个handler,用于输出到控制台

# 创建格式对象,自定义日志输出格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

fh.setFormatter(formatter)  # 写入日志文件的格式就用formatter这种
ch.setFormatter(formatter)  # 输入到控制台格式也用formatter

logger.addHandler(fh)   # logger对象添加fh和ch对象
logger.addHandler(ch)

# 可以设置日志级别,不设置就打印warning及其以上的
logger.setLevel(logging.DEBUG)

logger.debug('logger debug msg')
# logger.info('logger info msg')
# logger.warning('logger warning msg')
# logger.error('logger error msg')
# logger.critical('logger critical msg')

'''
结果如下:
2018-11-11 21:20:58,374 - liu - DEBUG - logger debug msg
2018-11-11 21:20:58,374 - liu - INFO - logger info msg
2018-11-11 21:20:58,374 - liu - WARNING - logger warning msg
2018-11-11 21:20:58,374 - liu - ERROR - logger error msg
2018-11-11 21:20:58,374 - liu - CRITICAL - logger critical msg
'''

 

八、ConfigParser配置文件模块

配置文件如下,如果我们要用python写入咋么搞呢?

[DEFAULT]
name = liu
age = 18
job = tester

[email]
qq = 12345
psw = 123

[city]
area = 深圳

  可以通过下面的代码生成上面的配置文件:

# -*- coding:utf-8 -*-
import configparser

config = configparser.ConfigParser()    # 返回一个配置文件对象
config['DEFAULT'] = {'name': 'liu',     # 添加一个默认模块,config是以键值对(字典)保存
                     'age': 18,
                     'job': 'tester'}
config['email'] = {}    # 添加email及其具体内容
email_c = config['email']
email_c['qq'] = '12345'
email_c['psw'] = '123'
config['city'] = {'area': '深圳'}
with open('config.ini', 'w') as file:
    config.write(file)

  那么我们生成了配置文件,我们该如何读取呢?

import configparser

config = configparser.ConfigParser()
config.read('config.ini')   # 创建的config对象是没有任何内容的,所以要读取文件先
print(config.sections())    # 读取除了default其它的一级栏目,这里返回['email', 'city']
print(config['email']['qq'])    # 读取email下qq的值,其它读取类似
# 有一个特殊的看下
for i in config['email']:
    print(i)
# 注意,这里除了返回了email下的key之外,还会返回default下的key,所以结果是qq、psw、name、age、job

  那么我们在使用的时候需要修改又怎么办呢?既然是字典当然可以通过字典的方式去修改。(之前说过文件不能修改只能通过覆盖同名文件的方式修改)

import configparser

config = configparser.ConfigParser()
config.read('config.ini')   # 依旧先读取文件

# 增加内容
config['email']['msg'] = 'hello'
config['new_section1'] = {}     # 新增一级栏目方法一
config.add_section('new_section2')  # 新增一级栏目方法二
config.write(open('config.ini', 'w'))   # 覆盖写将增加内容加入

# 删除内容qq
config.remove_option('email', 'qq')     # remove_option是删除非一级栏目的
config.write(open('config.ini', 'w'))   # 覆盖写将增加内容加入
# 删除email一级栏目
config.remove_section('email')  # remove_section是删除一级栏目
config.write(open('config.ini', 'w'))   # 覆盖写将增加内容加入

# 改内容
config.set('email', 'qq', 'abc')    # 修改qq为abc
config.write(open('config.ini', 'w'))   # 覆盖写将增加内容加入

 

九、re(正则表达式)

 python中通过re模块来实现正则的匹配。下面是11个元字符的讲解:

# -*- coding:utf-8 -*-
# @__author__ : Loris
# @Time : 2018/11/8 20:26

import re

# findall方法是找到所有符合的匹配,并以列表形式返回
ret = re.findall("world", "hello world,i love world")   # 这种属于完全匹配,结果是['world', 'world']

# 元字符".",匹配任意除换行符"\n"外的字符(在DOTALL模式中也能匹配换行符
ret = re.findall("w.r", "hello world,i love world")     # 结果是['wor', 'wor']

# 元字符"^",匹配字符串开头。在多行模式中匹配每一行的开头
ret = re.findall("^he", "hello world,i love world")     # 结果是['he']

# 元字符"$",匹配字符串末尾,在多行模式中匹配每一行的末尾
ret = re.findall(".d$", "hello world,i love world")      # 结果是['ld']

# 元字符"*",匹配前一个字符0或多次,注意下,像*、+、?、{}都是代表数量
ret = re.findall(".*", "hello world,i love world")      # 结果是['hello world,i love world', ''],由于*代表0到多个,所以空字符串也算

# 元字符"+",匹配前一个字符1或多次
ret = re.findall("ov+", "hello world,i love world")      # 结果是['ov']

# 元字符"?",匹配前一个字符0或1次
ret = re.findall("ov?", "hello world,i love world")      # 结果是['o', 'o', 'ov', 'o']

# 元字符"{m,n}",{m}匹配前一个字符m次,{m,n}匹配前一个字符m至n次,若省略n,则匹配m至无限次
ret = re.findall("ld{1,3}", "hello world,i love world")      # 这里是匹配ld,但是d可以是1-3次,所以匹配结果是['ld', 'ld']
# 这里可以有疑问说为啥1-3次,为啥不是1次而是最大次数呢,这就是贪婪模式,默认就是出现最多的


# 元字符"[]",这个是字符集,对应的位置可以是字符集中任意字符。字符集中的字符可以逐个列出,也可以给出范围,
# 如[abc]或[a-c]。[^abc]表示取反,即非abc。所有特殊字符在字符集中都失去其原有的特殊含义,除了-、\和^这三个
ret = re.findall("w[o a]r", "hello wo rld,i love world w r")  # 匹配wor或w r或war,所以结果是['wor', 'w r']
ret = re.findall("w[^ ,o]r", "hello warld,i love world w r")  # 匹配不是w r或wor或w,r之外的,返回['war']
ret = re.findall("w[*,]r", "hello warld,i love world w r")   # 匹配w*r和w,r,这里*就表示普通字符
ret = re.findall("w[\w]{2}r", "hello waerld,i love world w r")  # 匹配wxxr,其中xx代表两个字符,所以结果是:['waer']


# 元字符"\",转义字符,使后一个字符改变原来的意思,原本元字符变成普通字符,部分普通字符变成能实现特殊功能
# \d    匹配任何十进制数字,等价于[0-9]
# \D    匹配任意非数字,等价于[^0-9]
# \s    匹配任意空白字符,等价于 [\t\n\r\f\v]
# \S    匹配任意非空字符,等价于 [^\t\n\r\f\v]
# \w    匹配任意字母数字字符,等价于[A-Za-z0-9_]
# \W    匹配非字母字符,即匹配特殊字符,等价于[^A-Za-z0-9_]
# \b    匹配一个特殊字符的边界比如空格、@等特殊字符,这里举例说明下
ret = re.findall(r"i\b", "hello world,i lo$ivi#e world")     # 这里是'i '和'i#'满足,所以结果是['i', 'i']\

# 下面是\后面接元字符变成普通字符
ret = re.findall("\.", "hello .world,i lo$ivi#e world")     # 这里就是匹配"."所以结果就是['.']
# 那如果是要匹配普通字符\呢,要用\\\\,为啥呢,因为本身正则表达式语法中我们需要匹配\就需要变成\\,然后在python语法中表达\\需要转义,所以是\\\\
ret = re.findall("\\\\", "hello \world,i lo$ivi#e world")


# 元字符"()",配出的内容就表示一个分组。从正则表达式的左边开始看,看到的第一个左括号“(”表示第一个分组,第二个表示第二个分组,依次类推,
# 需要注意的是,有一个隐含的全局分组(就是0),就是整个正则表达式通过groups()来全部访问匹配的元组,也可以通过group()函数来按分组方式来访问
ret = re.findall("(ld)+", "hello world,i love world")  # 匹配到了['ld', 'ld']
print(ret)
ret = re.search("(ld)+", "hello world,i love world")    # search默认找第一个,返回的是一个对象<_sre.SRE_Match object; span=(9, 11), match='ld'>
ret = re.search("(?P<name>\w{3}):(?P<age>\d{1,3})", "my msg is liu:18")
print(ret.group())      # 需要用group()取值,默认返回所有,也就是group(0),结果就是liu:18
print(ret.group('name'))    # 只取name分组,结果就是liu
print(ret.group('age'))     # 只取age分组,结果就是18

# 元字符"|"    或。匹配|左右表达式任意一个,从左到右匹配,如果|没有包括在()中,则它的范围是整个正则表达式
ret = re.findall("(2|ld)+", "hello2 world2,i love wo2rld")   # 匹配2或者ld多次,结果是['2', '2', '2', 'ld']
ret = re.findall("2|ld", "hello2 world,i love world")   # 匹配2或者ld,结果是['2', 'ld', 'ld']
print(ret)

  除了11个元字符,还有几种正则表达式的方法:

# 1、findall()  所有结果都返回到一个列表里
# 2、search()   返回匹配到的第一个对象,对象可以调用group()返回结果

# 3、match()    从字符串开始进行匹配,只返回匹配到的第一个对象,对象可以调用group()返回结果
ret = re.match("\w+ll\w+", "hello world")     # 必须从开头开始写,用的不多
print(ret.group())              # 返回结果就是hello

# 4、split()   切片函数。使用指定的正则规则在目标字符串中查找匹配的字符串,用它们作为分界,把字符串切片,可切多次
ret = re.split('[o,h]', "helloworld")   # 先用o去分,得到'hell'、'w'和'rld',然后再通过h去分别分这三个
print(ret)  #   结果如下:['', 'ell', 'w', 'rld']

# 5、sub()   对字符串的替换和修改
ret = re.sub('l.{2}', 'sb...', 'hello world')
print(ret)  # 返回hesb... world

# 6、    将正则规则编译成一个 Pattern 对象,以供接下来使用.
pattern_obj = re.compile('\.com')
ret = pattern_obj.findall("www.baidu.com")
print(ret)      # 结果是['.com']

   最后来一个题目,计算 "2 - 4 * ( (-20-60 +(4-5*5/3 + 9 /-3*90.2/4*2558 +10 * 5601/10) +(-40/5) * (9-2*5/3 + 7 /3*99/4*29.8 +11 * 568/14 )) - (-4*3)/ (16-3*2) )",不能用eval。

# -*- coding:utf-8 -*-
"""
需求:用户输入 2 - 4 * ( (-20-60 +(4-5*5/3 + 9 /-3*90.2/4*2558 +10 * 5601/10) +(-40/5) * (9-2*5/3 + 7 /3*99/4*29.8 +11 * 568/14 )) - (-4*3)/ (16-3*2) )等类似公式后,
必须自己解析里面的(),+,-,*,/符号和公式(不能调用eval等类似功能偷懒实现),运算后得出结果,结果必须与真实的计算器所得出的结果一致

分析:
    1、首先我们应该先检查一下输入是否合法;
    2、然后需要整理一下用户的输入,去除多余空格,将类似+-,--等替换一下
    3、判断是否有括号,有括号就按照括号的计算,无则不需要找括号,直接按照乘除优先算
    4、按照我们手算的思路来说,先算最里面括号的,所以找出最里面的括号
    5、先算最里面括号的计算,括号内乘除优先,先找乘除法,从左往右,算完后替换回去再算加减
    6、注意计算之前要先格式化一下,整理一下类似+-,--等
"""
import re
s_name = "-2 - 4 * ( (-20-60 +(-4-5*5/3  -9 /-3*90.2/4*2558 +10 * -5601/10) +(-40/5) * (9-2*5/3 + 7 /3*99/4*29.8 +11 * 568/14 )) - (-4*3)/ (16-3*2) )"
print(eval(s_name))         # 正确结果是-599895.3904761905
def check_string(s_name):
    flag = True     # 判断计算表达式是否合法标志位
    ret1 = re.search("[^\d+\-*/ .()]+", s_name)     # 找一下看是否有字母及特殊字符
    ret2 = re.search("[+\-]+[*/]+", s_name)         # 找一下看是否有+*、-/等不合法表达
    if ret1 != None or ret2 != None:
        flag = False
        print("Invalid Input")
    return flag

# 定义一个整理字符串格式的函数
def format_str(s_name):
    s_name = s_name.replace(' ','')     # 去除字符串的空格
    s_name = s_name.replace('++', '+')
    s_name = s_name.replace('--', '+')
    s_name = s_name.replace('+-', '-')
    s_name = s_name.replace('-+', '-')
    s_name = s_name.replace('*+', '*')
    s_name = s_name.replace('/+', '/')
    return s_name

# 计算加减乘除
def calc_apmd(s_name):
    s_name = s_name.strip('()')         # 去除两边括号
    while True:
        # 乘除法计算
        if "*" in s_name or "/" in s_name:
            s_name = format_str(s_name)
            search_str = re.search("\d+\.?\d*[*/][\-]?\d+\.?\d*", s_name).group()  # 从左往右找出乘除法
            if "*" in search_str:  # 判断乘法或除法是不是在找到的第一个表达式里面
                s1, s2 = search_str.split('*')  # 通过*或/分割
                cal_result = float(s1) * float(s2)  # 计算乘法结果
            else:
                s1, s2 = search_str.split("/")
                cal_result = float(s1) / float(s2)
            s_name = s_name.replace(search_str, str(cal_result))  # 将结果替换回去

        # 下面是加减法计算
        elif "+" in s_name.strip("-+") or "-" in s_name.strip("-+"):  # 加减法跟乘除法类似
            s_name = format_str(s_name)
            search_str = re.search("[\-]?\d+\.?\d*[+\-]\d+\.?\d*", s_name).group()  # 从左往右找出加减法
            if "+" in search_str:
                s1, s2 = search_str.split('+')
                cal_result = float(s1) + float(s2)
            else:
                # 判断第一位是否是负数
                if search_str[0] == "-":
                    s1, s2 = search_str.strip("-").split("-")
                    cal_result = -float(s1) - float(s2)
                else:
                    s1, s2 = search_str.split("-")
                    cal_result = float(s1) - float(s2)
            s_name = s_name.replace(search_str, str(cal_result))  # 将结果替换回去
        else:
            break
    return s_name

if __name__ == "__main__":
    if check_string(s_name):
        s_name = format_str(s_name)      # 格式化整理字符串
        while True:
            if re.search("\(", s_name):  # 先判断是否有括号
                inner_string = re.search("\([^()]+\)", s_name) .group() # 找到最里面的括号
                calc_reslut = calc_apmd(inner_string)         # 将值传入计算后返回
                s_name = s_name.replace(inner_string, calc_reslut)  # 将计算后的结果替换
            elif re.search("\d+\.?\d*[+\-*/]\d+\.?\d*", s_name):    # 如果没括号看下有没有加减乘除,有的话就计算,没有就跳出循环
                calc_reslut = calc_apmd(s_name)  # 将值传入计算后返回
                s_name = s_name.replace(s_name, calc_reslut)  # 将计算后的结果替换
            else:
                break
    print(s_name)       # -599895.3904761905,与上面得到的结果

 

posted on 2018-11-10 12:01  ~泪小白~  阅读(204)  评论(0编辑  收藏  举报