python学习笔记 (6)
快速排序
在一个无序序列中,选取一个元素为基准元素,然后设置两个指针,一个low指针指向左侧第一个位置,一个high指针指向右侧最后一个位置。
# 快排
# 选取一个基准值 小的往左移 大的往右移
# [34,32,12,51,75,25,84,1,8]
# [34 32 12 51 25 1 8] 75 [84]
# [34 32 12 25 1 8] 51 []
# [1 8] 12 [34 32 25]
# [] 1 [8] 12 [25] 32 [34]
def quick_sort(num_list: list):
if len(num_list) <= 1:
return num_list
left_list = []
right_list = []
mid = num_list[len(num_list) // 2] #选取基准元素
num_list.remove(mid)
for i in num_list:
if i > mid:
right_list.append(i)
else:
left_list.append(i)
return quick_sort(left_list) + [mid] + quick_sort(right_list) #过程中取出来的mid也是一个集合
if __name__ == '__main__':
print(quick_sort([34, 32, 12, 51, 75, 25, 84, 8, 1]))
#[1, 8, 12, 25, 32, 34, 51, 75, 84]
快速排序应用之汉诺塔:
# 汉诺塔问题
# 将所有的块当成一个整体 n 我们需要做的是 块划分为 n-1 和 1 然后去移动
"""
move(3,A,B,C) -> move(2,A,C,B) -> move(1,A,B,C) -> A --> C
move(1,A,C,B) -> A --> B
move(1,C,A,B) -> C --> B
move(1,A,B,C) -> A --> C
move(2,B,A,C) -> move(1,B,C,A) -> B --> A
move(1,B,A,C) -> B --> C
move(1,A,B,C) -> A --> C
"""
def move(n, a, b, c): #n是块数
if n == 1:
print(f"{a} -> {c}")
else:
move(n - 1, a, c, b)
move(1, a, b, c)
# print(f"{a} -> {c}")
move(n - 1, b, a, c)
if __name__ == '__main__':
move(3, "A", "B", "C")
面向对象
1.初识面向对象
面向对象编程OOP——Object Oriented Programming,是一种程序设计思想。
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行,为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度 而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递 在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。
面向对象的设计思想是从自然界中来的,因为在自然界中,类(Class)和实例(Instance)的概念是很自然的。
Class是一种抽象概念,而实例(Instance)则是一个个具体的Student 比如定义的Class——Student,是指学生这个概念, Bart Simpson和Lisa Simpson是两个具体的Student 。
所以,面向对象的设计思想是抽象出Class,根据Class创建Instance 面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。
面向对象主要有三大特点:封装、继承和多态。
面向对象中的一些专业术语:
类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
方法:类中定义的函数
类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据
方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写
局部变量:定义在方法中的变量,只作用于当前实例的类
实例变量:在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待 。
实例化:创建一个类的实例,类的具体对象 。
对象:通过类定义的数据结构实例,包括两个数据成员(类变量和实例变量)和方法。
2.类
类的定义格式:
面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同 。
由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,在创建实例的时候,就把name,score等属性绑上去:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数 self不需要传,Python解释器自己会把实例变量传进去。
面向对象编程的一个重要特点就是数据封装。在之前的Student类中,每个实例就拥有各自的name和score这些数据,在类中可以通过函数来访问这些数据,比如打印一个学生的成绩:
既然Student实例本身就拥有这些数据,要访问这些数据,就不需要通过外部函数访问,可直接在Student类的内部定义访问数据的函数,这样就把“数据”给封装起来了。这些封装数据的函数是和Student类本身是关联起来的,称之为类的方法。要定义一个方法,除了第一个参数是self外,其他和普通函数一样。要调用时只需要在实例变量上直接调用,除了self不用传递,其他参数正常传入:这样一来,从外部看Student类,就只需要知道,创建实例需要给出name和score,而如何打印,都是在Student类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节
封装的另一个好处是可以给Student类增加新的方法,比如get_grade:同样的,get_grade方法可以直接在实例变量上调用,不需要知道内部实现细节。
在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑 但是,从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的name、score属性:
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,只有内部可以访问,外部不能访问:
# 定义类的时候 开头大写
class Student(object):
# 数据
name = 'syh'
score = 61
# 私有变量 只有类里面可以调用
__secret = '无糖可口可乐'
# 定义私有变量 使用一个下滑线
# 约定俗成 一个下划线的就是私有变量 你不要随便动他
_secret = '无糖雷碧'
def set_secret(self, secret):
self.__secret = secret
def get_secret(self):
return self.__secret
# 私有方法
def __student_secret_method(self):
print("学生的私有方法 嘿嘿嘿")
# 用一个下划线开头用以表示私有方法
def _student_secret_method(self):
print("学生的私有方法 嘿嘿嘿")
# 初始化方法 、 构造函数
def __init__(self, name, score):
self.name = name
self.score = score
# 操作数据的函数
def print_score(self):
self.__student_secret_method()
print(f'I\'m {self.name} score is {self.score}')
def get_grade(self):
if self.score > 60:
print("good")
# def get_grade(self,score):
# print(score)
# if self.score > 60:
# print("good")
def print_sco(name, score):
print(f"{name},{score}")
if __name__ == '__main__':
syh = Student("syh", 61)
syh.print_score()
syh.score = 90
syh.print_score()
print(Student)
# 强行加一个属性 其他语言不要这么干 自己写python也不要这么干
syh.age = 18
print(syh.age)
# 定义的外部方法
print_sco(syh.name, syh.score)
# AttributeError: 'Student' object has no attribute '__secret'
# print(syh.__secret)
# 强行访问类中的私有变量
print(syh._Student__secret)
print(syh._secret)
syh.set_secret("香草可口可乐")
print(syh._Student__secret)
print(syh.get_grade(60))
3.继承和多态
在OOP程序设计中,当定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。
继承有什么好处?最大的好处是子类获得了父类的全部功能。
# 继承
class Aniaml:
def __init__(self, leg, ear, mouth):
self.leg = leg
self.ear = ear
self.mouth = mouth
pass
def run(self):
print("Animal is run")
def ear_method(self):
print(f"{self.ear} is good")
class Canidae: #动物之下的犬科
def __init__(self,nose):
self.nose = nose
def nose_good(self):
print("Canidae nose is good")
class Dog(Aniaml, Canidae): #犬科中的dog类
def __init__(self, leg, ear, mouth, nose):
# 传入父类的构造方法
super().__init__(leg, ear, mouth)
# super().__init__(nose)
# self.leg = leg
# self.ear = ear
# self.mouth = mouth
self.nose = nose
pass
def run(self):
print("dog is run and bark")
if __name__ == '__main__':
spike = Dog(4, 2, 1, 1)
spike.run()
spike.ear_method()
spike.nose_good()
当子类和父类都存run()方法时,可以看做子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样就获得了继承的另一个好处:多态。
# 多态 : 父类的引用指向子类的对象
class Animal():
def run(self):
print("Animal is running")
class Dog(Animal):
def run(self):
print("dog is running and barking")
pass
class Cat(Animal):
def run(self):
print("cat is running and arresting")
class Mouse(Animal):
def run(self):
print("mouse is running and digging")
pass
def animal_run_twice(animal:Animal):
animal.run()
animal.run()
if __name__ == '__main__':
spike = Dog()
tom = Cat()
jerry = Mouse()
jerry.run()
tom.run()
spike.run()
tyke = Animal()
animal_run_twice(tyke)
animal_run_twice(spike)
1.鸭子类型
鸭子类型是对Python中数据类型本质上是由属性和行为来定义的一种解读。
Python是一种动态语言,不像Java和C++这种强类型语言,Python里实际上没有严格的类型检查。
只要某个对象具有鸭子的方法,可以像鸭子那样走路和嘎嘎叫,那么它就可以被其它函数当做鸭子一样调用。
class Duck():
def __init__(self, name):
self.name = name
def swim(self):
print(f"a duck {self.name} is swimming")
def call(self):
print(f"a duck {self.name} is 桀桀桀")
class Goose(): #鹅也会像鸭子一样
def __init__(self,name):
self.name = name
def swim(self):
print(f"a goose {self.name} is swimming")
def call(self):
print(f"a goose {self.name} is 鹅鹅鹅")
def duck_show(duck:Duck):
duck.swim()
duck.call()
if __name__ == '__main__':
bianzuilun = Duck("bianzuilun")
mrping = Goose("Mr.ping")
duck_show(bianzuilun)
duck_show(mrping)
#结果
a duck bianzuilun is swimming
a duck bianzuilun is 桀桀桀
a goose Mr.ping is swimming
a goose Mr.ping is 鹅鹅鹅
由于Python是动态语言,没有严格类型检查,Goose这个类具有和Duck这个类相同的方法,Mr.ping这只鹅划起水来像只鸭子,叫起来也像一只鸭子,所以duck_show这个函数也可以对Mr.ping进行作用,故Python其数据类型属于鸭子类型.
2.猴子补丁
猴子补丁是对Python中模块和类可以在外部被动态修改这种特性的一个比喻。
在模块和类的外部对模块和类进行修改是一种非常耍赖的做法,会破坏代码的封装结构,这种事情大概只有淘气的猴子喜欢去做,因此形象地称之为猴子补丁。
class Dog():
def __init__(self,name):
self.name = name
def bark(self):
print(f"{self.name} is wangwangwang")
def eat(self): #在Dog类的外面定义了一个eat的方法
print(f"dog is eatting")
if __name__ == '__main__':
snoopy = Dog("snoopy")
snoopy.bark()
snoopy.leg = 4
print(snoopy.leg)
Dog.eat = eat
snoopy.eat()
#
snoopy is wangwangwang
4
dog is eatting
异常处理
在程序运行过程中,总会遇到各种各样的错误。有的错误是程序编写有问题造成的,比如本来应该输出整数结果输出了字符串,这种错误通常称之为bug,bug是必须修复的。 有的错误是用户输入造成的,比如让用户输入email地址,结果得到一个空字符串,这种错误可以通过检查用户输入来做相应的处理 还有一类错误是完全无法在程序运行过程中预测的,比如写入文件的时候,磁盘满了,写不进去了,或者从网络抓取数据,网络突然断掉了。这类错误也称为异常,在程序中通常是必须处理的,否则,程序会因为各种问题终止并退出。 Python内置了一套异常处理机制,来帮助我们进行错误处理。
Python中的错误可以分为两种:语法错误和异常
语法错误(Syntax errors) :代码编译时的错误,不符合Python语言规则的代码会停止编译并返回错误信息。
异常(Exceptions) :相较于语法错误,异常比较难发现,因为它只在代码运行时才会发生, 如类型错误、数值错误、索引错误和属性错误等。
pyton异常类层级区别:
Python中的主要错误:
常见的语法错误:缺少起始符号或结尾符号(括号、引号等),缩进错误,关键词拼写错误。
六种典型的异常:
除零错误(ZeroDivisionError):除数为0。
名称错误(NameError):变量使用前未进行申明或者初始化。
类型错误(TypeError):某些函数或者方法只适用于特定的数据类型,如果 对数据类型的操作不当,就会产生类型错误。
数值错误(ValueError):在输入类型正确的情况下,具体输入值错误。
索引错误(IndexError):超出序列长度的索引操作。
属性错误(AttributeError):方法或者属性不适用该对象 。
1.处理异常
高级语言通常都内置了一套错误处理机制,Python也不例外 在Python中可通过try…except…else…finally…机制捕获异常并进行处理。
![image.png](https://i-blog.csdnimg.cn/blog_migrate/808694fe6d4a5dfa7631d5243990ec5e.png)
# 异常处理
try:
# 放的是可能会出错需要 异常捕获的代码
int("3.0")
except TypeError as e:
# 上述代码出错执行以下代码
print(e)
print("代码出错1")
except ValueError as e:
print(e)
print("代码出错2")
else:
# 上述try代码执行正常 执行如下代码
print("dog eat")
finally:
# 不管代码出不出错都会执行下述代码
print("代码检测完毕")
在实际使用中,定义每一种错误太过于麻烦,因此我们常用:
except: 是 Python 中最简单的异常处理语句,它可以捕获并处理任何类型的异常。当程序执行过程中发生异常时,如果使用 except:,那么这个 except: 块会捕获该异常,而不管这个异常的类型是什么。
except Exception as e: 只会捕获 Exception 及其子类的异常。当程序执行过程中发生异常时,如果使用 except Exception as e:,那么这个 except Exception as e: 块只会捕获 Exception 及其子类的异常,其他类型的异常将不会被捕获。
2.自定义异常和抛出异常
在Python中可以通过创建一个新的异常类来拥有自己的异常。
自定义异常的原因 Python提供的内建异常不够用 可以预估某个错误的产生。
定义异常类 :异常类继承自 Exception 类,可以直接继承,或者间接继承。
抛出异常:Python 使用 raise 语句抛出一个指定的异常。
raise 需要指定了要被抛出的异常。它必须是一个异常的实例或者是异常的类(也就是 Exception 的子类)。
class WeightError(Exception):#定义一个体重异常类
pass
syh_weight = 110
if syh_weight < 120:
# 抛出异常
raise WeightError(" 体重太轻了")
print("hhhhhh")#无异常输出
#
raise WeightError(" 体重太轻了")
__main__.WeightError: 体重太轻了
文件读写
读写文件是最常见的IO操作。Python内置了读写文件的函数,用法和C是兼容的。
1.读文件
要以读文件的模式打开一个文件对象,使用Python内置的open()函数,传入文件名和标示符:
open() 函数常用形式是接收两个参数:文件名(file)和模式(mode)
参数说明:
- file: 必需,文件路径(相对或者绝对路径)
- mode: 可选,文件打开模式,默认为r
- encoding: 一般使用utf8编码
- errors: 报错级别
- newline: 区分换行符
- buffering: 设置缓冲
- closefd: 传入的file参数类型
![image.png](https://i-blog.csdnimg.cn/blog_migrate/5fb81e3f2b9c990de9290ebee23819c0.png)
#绝对路径
f=open("F:/No_30//test.py", 'r', encoding='utf-8')
#相对路径 . ..
f = open("./test.py", 'r', encoding='utf-8')
fp = open("../data/data.txt", "r", encoding='utf-8'
'r’标示符表示读,这样成功地打开了一个文件 如果文件不存在,open()函数就会抛出一个IOError的错误.
如果文件成功打开,调用read方法可以把内容读到内存,用一个str对象接收:
# read() 读取数据 不带参数 读取所有
# 读完之后在读 就没有东西了
# print(fp.read())
# read() 带参数 则读几个字符
# print(fp.read(1))
调用read()会一次性读取文件的全部内容,如果文件有10G,内存可能不够用,所以,要保险起见,可以反复调用read(size)方法,每次最多读取size个字节的内容
另外,调用readline()可以每次读取一行内容,调用readlines()一次读取所有内容并按行返回list。因此,要根据需要决定怎么调用 。
如果文件很小,read()一次性读取最方便;如果不能确定文件大小,反复调用read(size)比较保险;如果是配置文件,调用readlines()最方便,然后通过for…in循环遍历即可获取全部配置。
# readline() 读取一行
# readline(x) 读x个字符
# print(fp.readline(1))
# print(fp.readline(2))
# readable() 确定文件可不可读
# print(fp.readable())
# readlines() 每一行读进列表里
# readlines(x) 读取包含x个字符的所有行数
print(fp.readlines(7))
print(fp.readlines(1))
fp.close() #文件使用完毕后调用close()方法可以关闭文件
例子:
# 3、创建一个学生成绩文件txt,里面放着学生的基本信息和成绩,使用Python读取其中文件,按照成绩排序输出姓名。
def ques3():
fp = open("../data/data.txt", 'r', encoding='utf-8')
stu_name_sco = fp.readlines()
stu_dic = {i.strip().split(",")[0]: int(i.strip().split(",")[1]) for i in stu_name_sco}
print(stu_dic)
# sorted 排序方法
print(sorted(stu_dic.items(), key=lambda x: x[1]))
# print(sorted(stu_dic.keys()))
fp.close()
2.写文件
写文件和读文件是一样的,唯一区别是调用open()函数时,传入标识符’w’或者’wb’表示写文本文件或写二进制文件:
strs = 'xxx is 18,pretty good'
fp = open("./data.txt", 'a', encoding='utf-8') #追加写
fp.write(strs + "\n")
fp.close()
# 读取一个图片 然后写出去
# 读二进制 写二进制没有encoding 重要
# fp_r = open(r"C:\Users\wallhaven-6dg7ll.png", 'rb')
# png_content = fp_r.read()
# print(png_content)
# fp_r.close()
# fp_w = open("./xxx.png", 'wb')
# fp_w.write(png_content)
# fp_w.close()
当写文件时,操作系统不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。
3.使用with语句
由于文件读写时都有可能产生IOError,一旦出错,后面的f.close()就不会调用。所以,为了保证无论是否出错都能正确地关闭文件,可以使用try … finally来实现。但每次都这么写实在太繁琐,所以Python引入了with语句来自动调用close()方法。
这和try … finally是一样的,但代码更佳简洁,并且不必调用f.close()方法。
# 整个代码内只需要打开或者写一次 建议使用with open
# 如果全代码好几个地方需要写 建议使用 open
with open("./data.txt", 'r', encoding='utf-8') as fp:
print(fp.readlines())
正则表达式
正则表达式是一种用来匹配字符串的强有力的武器。它的设计思想是用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,就认为它“匹配”了,否则,该字符串就是不合法的。
Python中常见的元字符有:
要匹配变长的字符,在正则表达式中,用*表示任意个字符(包括0个),用+表示至少一个字符,用?表示0个或1个字符,用{n}表示n个字符,用{n,m}表示n~m个字符。
要做更精确地匹配,则还需编写更复杂的正则表达式:
在正则中-表示从什么到什么,[]表示范围。
[0-9a-zA-Z_]可以匹配一个数字、字母或者下划线
[0-9a-zA-Z_]+可以匹配至少由一个数字、字母或者下划线组成的字符串
[a-zA-Z\_][0-9a-zA-Z\_]*
可以匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串,也就是Python合法的命名规则
[a-zA-Z\_][0-9a-zA-Z\_]
{0, 19}更精确地限制了长度是1-20个字符(前面1个字符,后面最多19个字符)
A|B可以匹配A或B,所以(P|p)ython可以匹配’Python’或者’python’
表示行的开头,\d表示必须以数字开头
KaTeX parse error: Undefined control sequence: \d at position 8: 表示行的结束,\̲d̲表示必须以数字结束
Python提供re模块,包含所有正则表达式的功能。
由于Python的字符串本身也用\转义,所以要特别注意,推荐在Python中进行正则表达式匹配时,使用r前缀标记字符串.
re模块提供了一个match方法,可以判断正则表达式是否匹配.
match()方法判断是否匹配,如果匹配成功,返回一个Match对象,否则返回None。
import re
# 正则表达式
# 1、用一些字符去描述字符
# 2、如果直接给出字符就是精确匹配
a = 'wy'
print(re.match('wy', a))
# \d 匹配一个数字字符
print(re.match('\d\d\d', '012'))
# \w 匹配一个字母或者数字 和 _
print(re.match('\w\w\w', '01A'))
# . 可以匹配任意字符 除\n
print(re.match('...', 'w12'))
# \s 可以匹配任意空白字符 、 换页、 换行、 制表符
print(re.match('\s\s\s\s', ' \n\t\f'))
# 匹配个数符号 {num} 表示匹配这么多 少了返回空 多了返回num长
print(re.match('\d{11}', '0123413131231'))
print(re.match('\d{8,11}', '0123413131231'))
# 0551-1234567 010-1234567 匹配电话号码
print(re.match('\d{3,4}-\d{7}', '010-1234567'))#\用来转义
# * 匹配任意长度的字符
# + 匹配至少一个字符
# ? 匹配0或1个字符
# 匹配一个变量名
print(re.match('[a-zA-Z_]\w*', 'a_b'))
print(re.match('[Pp]ython', 'Python'))
s = 'ABC\\-001'
print(re.match(r"\w+\\-\d+", s))
s = "Hello 1234567 is a number. Regex String"
print(re.match("Hello (\d*).*", s))
match方法通常会结合if选择结构进行判断:
#1、编写程序实现下述功能,提示用户输入用户名,要求用户名以字母开头,长度不少于3位,只能包含字母、数字、下划线,
# 如果用户输入符合要求,则提示注册成功,否则提示用户名不符合要求,请重新输入,一直循环直到用户名符合要求为止。
def ques1():
while True:
name = input("请输入姓名:")
if re.match("[a-zA-Z]\w{2,}", name):
print(name)
break
if __name__ == '__main__':
ques1()
1.切分字符串
re模块也提供了split方法,可以按照指定的正则表达式进行字符串的切分:
a = "a,b|c#d,,e"
print(a.replace("|", ",").replace("#", ",").split(","))#字符串的方法
print(re.split(r"[,|#]+", a))
print(re.split("[,;\s]+", "a,b;; c d"))
#
['a', 'b', 'c', 'd', '', 'e']
['a', 'b', 'c', 'd', 'e']
['a', 'b', 'c', 'd']
2.分组
除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用()表示的就是要提取的分组(Group)。
如果正则表达式中定义了组,就可以在Match对象上用group()方法提取出子串来 注意:group(0)永远是与整个正则表达式相匹配的字符串,group(1)、group(2)…表示第1、2、……个子串。
# 分组 group
# groups 返回一个元组 记录所有括号获取的值
# group(num) 0 整个正则表达式相匹配的字符串 1 返回一个获取符合的字符串
# 340122200601014212
id_str = '340322200601014232' #身份证号
print(re.match("(\d{6})(\d{8})(.*)", id_str).groups())
print(re.match("(\d{6})(\d{8})(.*)", id_str).group(0))
print(re.match("(\d{6})(\d{8})(.*)", id_str).group(1))
print(re.match("(\d{6})(\d{8})(.*)", id_str).group(2))
#('340322', '20060101', '4232')
#340322200601014232
#340322
#20060101
来看一个经典应用:
匹配时分秒:
# 匹配时分秒
# 24小时制
time_str = '09:09:10'
print(re.match("([0-1][0-9]|2[0-3]):([0-5]\d):([0-5]\d)", time_str).group(1))
print(re.match("([0-1][0-9]|2[0-3]):([0-5]\d):([0-5]\d)", time_str).group(2))
print(re.match("([0-1][0-9]|2[0-3]):([0-5]\d):([0-5]\d)", time_str).group(3))
但是有些时候,用正则表达式也无法做到完全验证,比如识别日期: ‘^(0[1-9]|1[0-2]|[0-9])-(0[1-9]|1[0-9]|2[0-9]|3[0-1]|[0-9])$’ 对于’2-30’,'4-31’这样的非法日期,该正则无法识别,或者说写出来非常困难,这时就需要程序配合识别了。
3.编译
当在Python中使用正则表达式时,re模块内部会干两件事情: 编译正则表达式,如果正则表达式的字符串本身不合法,会报错 用编译后的正则表达式去匹配字符串 如果一个正则表达式要重复使用几千次,出于效率的考虑,可以使用compile方法预编译该正则表达式,接下来重复使用时就不需要编译这个步骤了,直接匹配:
# 预编译 遇到很多相同样式的匹配的时候使用
num0_pattern = re.compile("(\d+?)(0*)$")
print(num0_pattern.match("10230000").groups())
print(num0_pattern.match("10230"))
print(num0_pattern.match("102"))
print(num0_pattern.match("102300000000"))
#
('1023', '0000')
<re.Match object; span=(0, 5), match='10230'>
<re.Match object; span=(0, 3), match='102'>
<re.Match object; span=(0, 12), match='102300000000'>
4.贪婪匹配
正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符:
例如,匹配出数字后面的0:
# 贪婪匹配 匹配所有符合规则的字符串
print(re.match("(\d+)(0*)$", '10230000').groups())#以0结尾
#结果
('10230000', '')
由于\d+采用贪婪匹配,直接把后面的0全部匹配了,结果0*只能匹配空字符串。
所以我们必须改变这种模式:必须让\d+采用非贪婪匹配(也就是尽可能少匹配),才能把后面的0匹配出来,加个?就可以让\d+采用非贪婪匹配:
# ? 进入懒惰模式
print(re.match("(\d+?)(0*)$", '10230000').groups())
#结果
('1023', '0000')
5.一个例子
请编写一个正则表达式,用于验证密码格式是否符合要求: 只能是大小写字母或数字或英文标点符号,不能是空白字符 长度至少为10位,且必须至少包含一个大写字母、一个小写字母、一个符号:
def ques3():
# 匹配英文标点符号
print(re.match("[!-~]", "&*(#$%&"))
# print(re.match("(?=.*[A-Z])(?=.*[a-z])(?=.*[!-~])[0-9A-Za-z!-~]", "&*(#$%&"))
a = 'eqw231HJP^$#'
# 检测是否存在大写字母
if re.match(".*?[A-Z]+.*",a):
if re.match(".*?[a-z]+.*",a):
if re.match(".*?[!-~]+.*", a):
if re.match("[0-9A-Za-z!-~]{10,}", a):
print("密码规则正确")
if __name__ == '__main__':
ques3()