7.1 面向对象基础
面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)
优点和应用场景:
- 业务功能较多时,通过面向对象归类
- 数据封装(创建字典存储数据)
- 游戏示例:创建一些角色,并根据角色需要再创建任务
- 封装思想:将同一类的函数封装到同一个py文件中,以后方便使用
- 面向对象:将同一类的函数封装到同一个class中,以后方便使用
- 对象名:命名首字母大写
Note1(1)
- 函数式的应用场景 --> 各个函数之间是独立且无共用的数据
1. 基础概念
- 类:具有相同方法和属性的一类事物
- 对象、实例:一个拥有具体属性值和动作的具体个体
- 实例化:从一个类得到一个具体对象的过程
# 定义一个类,Account class Account: # 方法, 哪个对象调用方法,其就是self def login(self,name): print(123) return 666 def logout(self): pass # 调用类中的方法 x = Account() # 实例化(创建)Account类的对象,开辟一块内存 val = x.login('henry') # 使用对象调用class中的方法 print(val)
Note2(2)
- 应用场景:用于很多函数,需要对函数进行归类和划分(封装)
- self:哪个对象操作,self代表类的实例,而非类
2. 对象的封装
- 作用:存储一些值,将数据封装到对象,方便使用
- 属性调用:对象.属性名进行数据的调用
- 广义封装:类中成员
- 狭义封装:私有成员:
_类名__名字
:命名
class File: def read(self): with open(self.path, mode='r', encoding='utf-8') as f: data = f.read() def write(self, content): with open(self.path, mode='a', encoding='utf-8') as f: data = f.write() obj = File() # 创建对象,并使用 obj.path = 'test.txt' # 往obj对象中写入一个私有对象 obj.write(content) # 定义私有属性,私有属性在类外部无法直接进行访问 obj2 = File('info.txt') obj2.write(content)
class Person: # __init__初始化方法(构造方法),给对象内部做初始化 def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender def show(self): temp = 'i am %s, age:%s, gender:%s ' % (self.name, self.age, self.gender) print(temp) # 类(),会执行__init__ obj = Person('henry', 19, 'male') obj.show() obj2 = Person('echo', 19, 'female') obj2.show()
Note3(3)
- 函数和数据的封装
- 如果写代码时,函数较多,可以将函数归类,并放入同一类中。(函数的封装)
- 函数如果有一个反复使用的公共值,则可以封装到类中(数据的封装)
- 面向对象三大特性:封装、继承、多态
- 执行类中的方法时,需要通过self间接调用被封装的内容
2.1 查看对象的类
# 类有一个名为 __init__() 的构造方法,该方法在类实例化时会自动调用,一般通过object类进行格式化 # 类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self。
# self.__class__:查看实例所在的类 class Test: def prt(self): print(self) print(self.__class__) t = Test() t.prt()
2.2 类的方法
在类的内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self,且为第一个参数,self 代表的是类的实例。self 的名字并不是规定死的,也可以使用 this,但是最好还是按照约定是用 self。
类的私有方法**__private_method:两个下划线开头,声明该方法为私有方法,只能在类的内部调用 ,不能在类的外部调用。self.__private_methods**。
2.3 示例
# 循环让用户输入:用户名,密码,邮箱,输入完成后在打印 class Person(): def __init__(self, user, pwd, email): self.username = user self.password = pwd self.email = email def info(self): return temp = 'i am %s, pwd:%s, email:%s ' % (self.username, self.password, self.email,) USER_LIST = [] while 1: user = input('please input user name: ') pwd = input('please input user pwd: ') email = input('please input user email: ') p = Person(user, pwd, email) USER_LIST.append(p) for i in USER_LIST: data = i.info() print(i)
3. 继承
场景:多个类中,如果有公共的方法可以放到基类中,增加代码的重用性。
继承:可以对基类中的方法进行覆写
3.1 继承的查找方法
# 父类(基类) class Base: def f1(self): pass # 单继承,子类,Foo类继承Base类 (派生类) class Foo(Base): def f2(self): pass # 创建了一个子类对象 obj = Foo() # 执行对象.方法时,优先在自己类中找,没有则找其父类 obj.f2() obj.f1() # 创建了一个父类对象 obj = Base() obj.f1() obj.f2() # 会报错
继承关系中的查找方法:
- self 指的是哪个对象
- 当类是经典类时,多继承情况下,会按照深度优先方式查找
- 当类是新式类时,多继承情况下,会按照广度优先方式查找
3.2 经典类和新式类
- 新式类:继承object,super,多继承(广度优先c3),具有
__mro__
方法- super(新式类支持,遵循mro顺序)
- Python的每一个有父类的类都有一个与方法解析顺序相关的特殊属性:
__mro__
, 它是一个tuple
, 装着方法解析时的对象查找顺序: 越靠前的优先级越高。
- 经典类:py2不继承object,无super/mro , 深度优先
- 从字面上可以看出一个老一个新,新的必然包含了跟多的功能,也是之后推荐的写法,从写法上区分的话,如果当前类或者父类继承了object类,那么该类便是新式类,否则便是经典类。
class D(object): def bar(self): print 'D.bar' class C(D): def bar(self): print 'C.bar' class B(D): def bar(self): print 'B.bar' class A(B, C): def bar(self): print 'A.bar' a = A() # 执行bar方法时 # 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错 # 所以,查找顺序:A --> B --> C --> D # 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了 a.bar()
4. 多态(多种形态/类型)
多态:一个类变现出来的多种状态—>多个类表现出相似的状态。
Pyhon不支持Java和C#这一类强类型语言中多态的写法,但是原生多态,Python崇尚“鸭子类型”。list,tuple,python的多态是通过鸭子类型实现的
# 多态,鸭子模型 def func(arg): # 多种类型,很多事物 v = arg[-1] # 必须具有此方法,呱呱叫 print(v)
# 对于一个函数,python对参数类型不会限制,传入参数时可以是各种类型,在函数中如果有例如:arg.append方法,就会对传入类型进行限制。 # 这就是鸭子模型,类似于上述的函数,我们认为只要能呱呱叫的就是鸭子,只要有append方法,就是我们想要的类型
5. 类的专有方法
- **__init__ :** 初始化,在生成对象时调用
- **__del__ :** 析构函数,释放对象时使用
- **__repr__ :** 打印,转换
- **__setitem__ :** 按照索引赋值
- **__getitem__:** 按照索引获取值
- **__len__:** 获得长度
- **__cmp__:** 比较运算
- **__call__:** 函数调用
- **__add__:** 加运算
- **__sub__:** 减运算
- **__mul__:** 乘运算
- **__truediv__:** 除运算
- **__mod__:** 求余运算
- **__pow__:** 乘方
6. 运算符重载
Python同样支持运算符重载,我们可以对类的专有方法进行重载
class Vector: def __init__(self, a, b): self.a = a self.b = b def __str__(self): return 'Vector (%d, %d)' % (self.a, self.b) def __add__(self,other): return Vector(self.a + other.a, self.b + other.b) v1 = Vector(2,10) v2 = Vector(5,-2) print (v1 + v2)
7.2 类成员(6)
- 实例化对象时,对在对象中存储类对象指针,指向其类
1. 类变量(静态字段/属性)
- 写在类的下一级,和方法同级
- 访问:类.变量名/ 对象.变量名
- 继承关系中,自己类中没有的变量可以去基类中找
- 只能赋值、修改自己的变量
对象成员:实例变量(字段)
Note:属于谁的只允许谁去取,python允许对象去其类中取变量
2. 方法
2.1 绑定/普通方法
- 定义:必须有self参数
- 执行:先创建对象,由 对象.方法 调用
2.2 静态方法
- 定义:@staticmethod, 参数无限制
- 执行:类.静态方法名() / python对象也可以调用
class Foo: def __init__(self): self.name = 123 def func(self, a, b): print(self.name, a, b) # python内部装饰器 @staticmethod def f(): print(1,2) Foo.f() obj = Foo() obj.func(1, 2) obj.f()
2.3 类方法
- 定义:@classmethod, 必须有cls参数,当前类
- 执行:类.类方法() / python对象也可以调用
class Foo: def __init__(self): self.name = 123 def func(self, a, b): print(self.name, a, b) # python内部装饰器 @classmethod def f(cls, a, b): print(a, b) Foo.f(1, 2) obj.f(1, 2) # 不推荐
3. 属性
- 定义:@property 只能有一个参数self
- 执行:对象.属性名( 无括号)
class Foo: @property def func(self): print(123) print(666) obj = Foo() ret = obj.func print(ret)
# 示例:属性 class Page: def __init__(self, total_count, current_page, per_page = 10): self.total_count = total_count self.current_page = current_page self.per_page = per_page @proporty def start_index(self): return(self.current_page -1 ) * self.per_page @property def end_index(self): returno self.current_page * self.per_page_count USER_LIST = [] for i in range(321): USER_LIST.append('henry-%s' % (i,)) # 请实现分页 current_page = int(input('请输入要查看的页码:')) p = Page(321, current_page) data_list = USER_LIST[p.start_index:p.end_index] for i in data_list: print(i)
4. 成员修饰符
- 公有:所有位置都能访问
- 私有:__开头(只有自己才能访问)
class Foo: def __init__(self, name): self.__name = name def func(self): print(self.name) obj = Foo('alex') print(obj.__name) # 会报错 obj.func() # 可以访问
class Foo: __x = 1 @staticmethod def func(): print(Foo.__x) obj = Foo() print(Foo.__x) # 会报错 print(obj._Foo__x) # 强制访问私有成员
7.3 特殊方法
以双下划线开头的 **__foo** 代表类的私有成员,以双下划线开头和结尾的 **__foo__** 代表 Python 里特殊方法专用的标识,如
__init__()
代表类的构造函数。- 特殊方法/魔术方法/内置方法/双下方法
- 特殊成员(方法)__init__
- type / isinstance / issubclass / super
异常处理
- 类和对象的关系:对象是类的一个实例
- self:本质就是一个形式参数,对象调用方法时,python内部会将该对象传给这个参数
- 类/方法/对像都可以当作变量或嵌套到其他类中
class School(object): def __init__(self,title): self.title = title def rename(self): pass class Course(object): def __init__(self, name, school_obj): self.name = name self.school = school_obj def reset_price(self): pass class Classes(object): def __init__(self,cname, course_obj): self.cname = cname self.course = course_obj def sk(self): pass s1 = School('北京') c1 = Course('Python', s1) cl1 = Classes('全栈1期', c1)
1. 嵌套
- 函数:参数可以是任意类型
- dict:函数、类和对像都可以作为字典的key, 即都是可hash的
- 继承的查找关系
# 示例1 class StarkConfig(object): pass class AdminSite(object): def __init__(self): self.data_list = [] def register(self, arg): self.data_list.append(arg) site = AdminSite() obj = StarkConfig() site.regisetr(obj)
# 示例2 class StarkConfig(object): def __init__(self, name, age): self.name = name self.age = aeg class AdminSite(object): def __init__(self): self.data_list = [] self.sk = None def set_sk(self, arg=StarkConfig): self.sk =arg site = AdminSite() site.set_sk(StarkConfig) site.sk('henry', 19)
# 示例3 class StarkConfig(object): list_display = 'henry' def changelist(self): print(self.list_display) class UserConfig(StarkConfig): list_display = 'echo' class AdminSite(object): def __init__(self): self._register = {} def registry(self, key, arg=StarkConfig): self._register[key] = arg def run(self): for key, val in self._register.items(): obj = val() obj.changelist() site = AdminSite() site.registry(1) site.registry(2, StackConfig) site.registry(3, UserConfig) # 易错点 echo site.run()
2. 特殊成员
特殊成员:为了能够给快速实现某些方法而生。
2.1 __init__(初始化方法)
# 填充数据,一般称为初始化 class Foo: """ 此类的作用 """ def __init__(self): """ 初始化方法 """ pass
2.2 __new__(构造方法)
Note
- new方法是静态方法,在使用__new__方法时,构造的对象值为 new 方法的返回值
- 创建的是一块内存和指针
- 返回一个对象
# __new__ 创建一个空对象 # 通过 __init__ 初始化对像 class Foo(object): def __new__(cls, *args, **kwargs): # 在 __init__ 之前 return 'henry'/ object.__new__(cls) obj = Foo() print(obj)
2.3 __call__
# 对象() 会执行类中的 __call__ 方法 class Foo: def __init__(self): pass def __call__(self, *args, **kwargs): print('哈哈,你变成我了吧') Foo()() # 第三方模块。写一个网站,用户只要来访问,就自动找到第三个参数并执行 make_server('ip', port, Foo())
2.4 __getitem__ __setitem__ __delitem__
obj = dict() obj['k1'] = 123 class Foo(object): def __setitem__(self, key, values): print(key, value) def __getitem__(self, item): return item + 'uuu' def __delitem__(self, key): print(key) obj1 = Foo() obj1['k1'] = 123 # 内部会自动调用__setitem__方法 obj1['xxx'] # 内部会自动调用__getitem__方法 del obj1['ttt'] # 内部会自动调用__delitem__方法
2.5 __str__
# 只有在打印时,会自动调用此方法,并将返回值显示出来 # type 查看 class Foo: def __str__(self): print('变样是不是不认识我了') return 'henry' obj = Foo() print(obj)
2.6 __dict__
作用: 查看对象中有哪些变量
class Foo(object): def __init__(self, name, age, email): self.name = name self.age = age self.email = emial obj = Foo('henry', 19, '123@qq.com') val = obj.__dict__ # 去对象中找到所有变量并将其转换为字典 print(val)
2.7 __enter__(上下文管理)
作用:使用with语法时,需要
class Foo(object): def __enter__(self): self.x = open('a.txt', mode='a', encoding='utf-8') return self.x def __exit__(self, exe_type, exc_val, exc_tb): self.x.close() with Foo() as f: # 需要 __enter__ 和 __exit__ 方法 f.write('henry') f.write('echo')
2.8 __add__ 两个对像相加
class Foo(object): def __init__(self, v): self.v = v def __add__(self, other): return self.v + other.v obj1 = Foo() obj2 = Foo() val = obj1 + obj2 # obj1触发,把obj1传给self
2.9 __iter__
# 可迭代对象 class Foo: def __iter__(self): return iter([1, 2, 3, 4]) obj = Foo() # 示例2 class Foo: def __iter__(self): yield 1 yield 2 ... obj = Foo()
2.10 __repr__
- 当对象处于一个list中时,调用该方法
class Studnet: def __init__(self, name): self.name = name # 面向用户 def __str__(self): return self.name # 内部程序 def __repr__(self): return '<{}>'.format(self.name) class Classes: def __init__(self): self.students = [] s1 = Studnet('henry') s2 = Studnet('echo') # henry echo,优先调用str方法, print(s1,s2) c = Classes() c.students.append(s1) c.students.append(s2) # [<henry>, <echo>],调用__repr__方法 print(c.students) # henry echo,调用__str__方法 for i in c.students: print(i)
2.11 其他
# 类的 cls.__dict__ # 打印出 cls 类的所有属性和方法,结果为一个字典 cls.__bases__ # 类的基类 cls.__doc__ # 类的docstring cls.__name__ # 类的名字 cls.__module__ # 类所在模块,如果是主文件,就是__main__ # 对象的 obj.__class__ # 类的类型<class '__main__.类名'> obj.__module__ # 实例类型所在模块 obj.__dict__ # 对象的字典,存储所有实例成员信息
3. 内置函数
3.1 type(对象)
class Foo(object): pass obj = Foo() print('obj是Foo的对象,开心吧') if type(obj) == Foo else print('哪凉快呆哪去')
3.2 issubclass(子类,基类)
# 可以多级继承 class Base(object): pass class Bar(Base): pass class Foo(Bar): pass print(issubclass(Foo, Base))
3.3 isinstance(obj, Foo)
# 判断某个对象是否时 某个类 或 基类 的实例(对象) class Base(object): pass class Foo(Base): pass obj = Foo() print(isinstance(obj, Foo)) print(isinstance(obj, Base))
4. super()
# super().func(),根据 self所属类的继承关系进行查找,默认找到第一个就停止 class Bar(object): def func(self): print('bar.func') return 123 class Base(Bar): def func(self): super().func() print('bar.func') return 123 class Foo(Base): def func(self): v = super().func() print('foo.func', v) obj = Foo() obj.func()
7.4 接口类和抽象类(约束)&反射
1. 扩展
# 会打印 hello # 类里的成员会加载,代码会执行 # 函数只有在调用时执行 class Foo(object): print('hello') def func(self): pass
# 类的嵌套 class Foo(object): x = 1 def func(self): pass class Meta(object): y = 123 print('hello') def show(self): print(y.self)
2. 可迭代对象
- 表象:可被for循环的对象
- 作用:组合搜索
- 可迭代对象:在类中实现**__iter__方法并返回迭代器/生成器**
# 可迭代对象示例1 class Foo: def __iter__(self): return iter([1, 2, 3, 4]) obj = Foo() # 示例2 class Foo: def __iter__(self): yield 1 yield 2 ...'' obj = Foo()
3. 抽象类/接口类(约束 源码)
# python的约束,易错点 # 约束子类中必须要有send方法,如果没有则会抛出:NotImplementedError class Interface(object): def send(self): raise NotImplementedError() class Foo(Interface): def send(self): pass class Base(Interface): def func(arg): arg.send(arg)
# 应用场景示例 class BaseMassage(object): def send(self): raise NotImplementedError('子类中必须有send方法') class Msg(BaseMassage): def send(self): print('发送短信') class Email(BaseMassage): def send(self): print('发送邮件') class Wechat(BaseMassage): def send(self): print('发送微信') class DingDing(BaseMassage): def send(self): pass obj = Email() obj.send()
4. 反射
反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。
反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)
- getattr('对象', 字符串):根据字符串的形式,去某个对象中获取其成员。
- hasattr('对象', 字符串):根据字符串的形式,去某个对象中判断是否有该成员。
- setattr('对象', '变量',值):根据字符串的形式,去某个对象中设置成员。
- delatttr('对象', '变量'):根据字符串的形式,去某个对象中删除成员。
# getattr示例 class Foo(object): def __init__(self, name): self.name = name obj = Foo('alex') obj.name v1 = getattr(obj, 'name') # setattr示例 obj.name = 'eric' setattr(obj, 'name', 'eric')
- getattr:反射当前文件内容
# 反射当前文件内容 import sys getattr(sys.modules[__name__], 'ab') # 通过对象获取、示例变量、绑定方法 # 通过类来获取类变量、类方法、静态方法 # 通过模块名获取模块中的任意变量(普通变量、函数、类) # 通过本文件反射任意变量
# 应用示例 class Foo(object): def login(self): pass def regiseter(self): pass obj = Foo() func_name = input('please input method name: ') # 获取方法 getattr(obj, func_name)()
# setattr 示例 class Foo(object): pass obj = Foo() setattr(obj, 'k1', 123) print(obj.k1)
# delattr 示例 class Foo(object): pass obj = Foo() obj.k1 = 999 delattr(obj, 'k1') print(obj.k1)
Note(2)
- python中一切皆对象(py文件,包,类,对象),可以通过getattr获取
- 通过字符串操作内部成员都可以通过反射的机制实现
import x v = x.NUM # 等价于 v = getattr(x, 'NUM') print(v) v = getattr(x, 'func') v() v = getattr(x, 'Foo') val = v() val.x
示例:
# 浏览器两类行为 # way1: 输入地址+回车 get.... # way2: 表单(输入框+按键) post.... # 浏览器都会有get,post,dispatch方法 class View(object): def get(self): pass def Post(self): pass def Dispatch(self): # 请求第一步来这,在进行分发 pass
# 推荐使用性能较好 class Foo(object): def post(self): pass # 方式1 if hasattr(obj, 'get'): getattr(obj, 'get') # 方式2:推荐使用 v = getattr(obj, 'get', None) print(v)
5. setarrt和getatrr
应用场景
- django中间件:
- 注册中间件用的是字符串,django1.10之前(MIDDLEWAER_CLASSES),之后MIDDLEWAER
- 基于反射实现,process_requests...(5个)
- 注册中间件用的是字符串,django1.10之前(MIDDLEWAER_CLASSES),之后MIDDLEWAER
- flask 上下文管理:LocalProxy对象
getattr(obj, 'args')() # 高级用法 flask 上下文管理:LocalProxy对象
7.5 单例&项目结构
1. 单例模式
1.1 单例
场景:数据库连接和数据库连接池(数据一致时)
设计模式:23种设计模式
class Foo(object): pass # 每实例化一次,就创建一个新对象,内存地址 不一样 obj1 = Foo() obj2 = Foo()
# 单例(Singleton)模式,无论是实例化多少次,都用第一次创建的那个对象,内存地址一样 class Singleton(object): instance = None def __new__(cls, *args, **kwargs): if not cls.instance: cls.instance = object.__new__(cls) return cls.instance obj1 = Singleton() # 内存地址一致 obj2 = Singleton()
1.2 标准
# 需要加锁,多线程,并发
class FileHelper(object): instance = None def __init__(self, path): self.file_object = open(path, mode='r', encoding='utf-8') def __new__(cls, *args, **kwargs): if not cls.instance: cls.instance = object.__new__(cls) return cls.instance obj1 = FileHelper('x') # 内存地址一致 obj2 = FileHelper('x')
2. 模块导入
# 导入模块,只是保留模块内存 # 思考角度:函数名不能重复、内存溢出 from jd import n1 # 多次导入,模块只会加载一次,即使模块中包含其他模块 import jd import jd print(456)
# 多次导入,模块只会加载一次,即使模块中包含其他模块 import importlib import jd # 手动加载,会覆盖第一次导入 importlib.reload(jd) print(456)
- 通过模块导入特性,也可以实现单例模式
# jd.py class Foo(object): pass obj = Foo()
# app.py import jd # 加载jd.py,加载最后会实例化一个Foo对象并赋值给obj print(jd.obj)
3. 项目开发规范
- bin:start
- config:配置文件settings
- src:业务逻辑
- db:数据文件
- lib:扩展模块
- log:日志文件
3.1 脚本
import os import re import datetime import xlrd import requests
3.2 单可执行文件
# app(程序入口)/src(业务相关)/lib(公共的类库)/db(文件)/config(配置) app.py 越简单越好,少于10行
3.3 多可执行文件
# app(程序入口)/src(业务相关)/lib(公共的类库)/db(文件)/config(配置) # bin(多个可执行文件例如:student.py,teacher.py,admin.py) # log (存储日志文件) # seetings(BASE_PATH,LOG_FILE_NAME...) path = sys.path.os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(path)
7.6 MethodType和FunctionType
from types import MethodType, FunctionType def fun(): pass print(isinstance(func, FunctionType)) # True print(isinstance(list.append(), MethodType)) # True class Foo: def fun(self): pass obj = Foo() obj.fun() # 方法 Foo.fun(123) # 函数 # 通过对象调用是方法,通过类调用时函数
番外篇之正则
1. 基本概念
- re模块本身只是用来操作正则表达式的和正则本身无关
- 正则表达式:是一种匹配字符串的规则
- 为什么要有正则:应用场景
- 匹配字符串
- 表单验证:11位,全数字,1开头,第二个数 3-9,绑定银行卡
- 爬虫:从网页源码中获取链接,重要数据
2. 规则
2.1 元字符
- 是哪个一字符就匹配字符串中的哪一个字符
- 字符组(3)
- [ad] ,匹配a/d,单字符匹配
- [0-9], [a-z], [A-Z] (范围是从小到大),遵循ASCII码
- [a-zA-Z], [0-9x]
- 转义字符(7 )
- [0-9] 等价于 \d (\转义符,转义d使得其匹配0-9之间的数)
- \w:(word,数字,大小写字母,下划线)
- \s:(space, 空格,换行,制表符) (\t(table) \n(next))
- \D \W \S(对以上结果取反)
- \b:匹配\w和\W之间,即匹配单词边界匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。
- 特殊符号的含义(4)
- . 除了换行符之外的任意内容
- [\d] [0-9] \d 没有区别。 [\d\D] 匹配所有
- [^abc]:非字符组,[abc] 取反
- ^:表示一个字符的开始。 $:表示一个字符的结束 (^abc$)
- | 和()eg. abc|edf。 abc|ab
# 若果规则有重叠,需要长的在前面 www.(baidu|google).com # () 表示分组,给一部分正则规定为一组,
2.2 量词(6)
1[3-9]\d{9} # 量词前面一个重复次数,9次 1[3-9]\d{9,} # 量词前面一个重复次数,9次以上 1[3-9]\d{n,m} # 量词前面一个重复次数,n-m次 ? # ? 匹配到0次或1次,没匹配上也算一次,匹配上算2次 #(可有可无,只能有一个) + # + 匹配1次或多次 * # * 匹配0次或多次
# 匹配任意小数,保留两位 \d+\.\d{2} # 匹配任意整数或小数 \d+\.?\d* # 有bug \d+(\.\d+)? # 分组实现
2.3 贪婪匹配/惰性匹配
\d{7-12} # 默认是贪婪匹配,尽量多匹配 # 回溯算法 # 非贪婪匹配,惰性匹配,总是匹配符合条件范围内尽量小的字符串 \d{2,3}? # 匹配两位数 \d+?3 # 尽量多取,遇到3结束 元字符 量词 ?x # 按照元字符规则在量词范围内匹配,一旦遇到x停止 .*?x # 常用,先找x找到匹配结束
# 身份证号匹配(正则表达式,断言) [1-9](\d{16}[\dx]|\d{14}) [1-9]\d{14}(\d{2}[\dx]) ^([1-9]\d{16}[0-9x]|[1-9]\d{14})$
. 是任意字符 * 是取 0 至 无限长度 ? 是非贪婪模式。 .*?x # 就是取前面任意长度的字符,直到一个x出现
2.4 示例
# 匹配邮箱 \w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14} # url ^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+
3. re模块
3.1 compile()
编译正则表达式模式,返回一个对象的模式。(可以把那些常用的正则表达式编译成正则表达式对象,这样可以提高一点效率。)
格式:re.compile(pattern,flags=0),pattern: 编译时用的表达式字符串。flags 编译标志位,用于修改正则表达式的匹配方式,如:是否区分大小写,多行匹配等。常用的flags有:
标志 | 含义 |
---|---|
re.S(DOTALL) | 使匹配包括换行在内的所有字符 |
re.I(IGNORECASE) | 使匹配对大小写不敏感 |
re.L(LOCALE) | 做本地化识别(locale-aware)匹配,法语等 |
re.M (MULTILINE) | 多行匹配,影响^和$ |
re.X (VERBOSE) | 该标志通过给予更灵活的格式以便将正则表达式写得更易于理解 |
re.U | 根据Unicode字符集解析字符,这个标志影响\w,\W,\b,\B |
import re tt = "Tina is a good girl, she is cool, clever, and so on..." rr = re.compile(r'\w*oo\w*') print(rr.findall(tt)) # 查找所有包含'oo'的单词 执行结果如下: ['good', 'cool']
3.2 re.match()
格式:re.match(pattern, string, flags=0)
在search前的正则前加了一个:^
想要完全匹配,可以在表达式末尾加上边界匹配符$,没有匹配到,则返回 None
# 从字符串开头匹配,匹配上则返回一个match对像,有group()方法 import re ret = re.match('\d', '8alex83') print(ret)
3.3 search()
格式:re.search(pattern, string, flags=0)
- re.search只要找到第一个匹配然后返回,如果字符串没有匹配,则返回None。
print(re.search('\dcom','www.4comrunoob.5com').group()) # 执行结果如下:4com
注:match和search一旦匹配成功,就是一个match object对象,而match object对象有以下方法:
- start() 返回匹配开始的位置
- end() 返回匹配结束的位置
- span() 返回一个元组包含匹配 (开始,结束) 的位置
- group() 返回re整体匹配的字符串,可以一次输入多个组号,对应组号匹配的字符串。
a. group()返回re整体匹配的字符串,
b. group (n,m) 返回组号为n,m所匹配的字符串,如果组号不存在,则返回indexError异常
c.groups()groups() 方法返回一个包含正则表达式中所有小组字符串的元组,从 1 到所含的小组号,通常groups()不需要参数,返回一个元组,元组中的元就是正则表达式中定义的组。
import re a = "123abc456" print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(0)) #123abc456,返回整体 print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(1)) #123 print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(2)) #abc print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(3)) #456 ###group(1) 列出第一个括号匹配部分,group(2) 列出第二个括号匹配部分,group(3) 列出第三个括号匹配部分。###
3.4 findall()
- 格式:re.findall(pattern, string, flags=0)
- 可以获取字符串中所有匹配的字符串,返回一个列表,没有匹配则为空。
p = re.compile(r'\d+') print(p.findall('o1n2m3k4')) 执行结果如下: ['1', '2', '3', '4']
import re tt = "Tina is a good girl, she is cool, clever, and so on..." rr = re.compile(r'\w*oo\w*') print(rr.findall(tt)) print(re.findall(r'(\w)*oo(\w)',tt)) # ()表示子表达式 执行结果如下: ['good', 'cool'] [('g', 'd'), ('c', 'l')]
3.5 finditer()
格式:re.finditer(pattern, string, flags=0)
- 搜索string,返回一个顺序访问每一个匹配结果(Match对象)的迭代器。找到 RE 匹配的所有子串,并把它们作为一个迭代器返回。
# 匹配到结果为 迭代器,每一项都是match对象,通过group取值 import re ret = re.finditer('\d', 'safh123ghakjdsfg234'*2000000) for i in ret: print(i.group())
3.6 split()
格式:re.split(pattern, string[, maxsplit],maxsplit用于指定最大分割次数,不指定将全部分割。
- 按照能够匹配的子串将string分割后返回列表。
- 可以使用re.split来分割字符串,如:re.split(r'\s+', text);将字符串按空格分割成一个单词列表。
import re ret = re.split('\d+', 'henry18') print(ret) # 保留分组中内容 ret = re.split('(\d+)', 'henry18') print(ret)
3.7 sub()/ subn()
格式:re.sub(pattern, repl, string, count=0)
格式:subn(pattern, repl, string, count=0, flags=0)
- 不返回/返回替换次数
import re text = "JGood is a handsome boy, he is cool, clever, and so on..." print(re.sub(r'\s+', lambda m:'['+m.group(0)+']', text,0)) # flags=0默认参数 执行结果如下: JGood[ ]is[ ]a[ ]handsome[ ]boy,[ ]he[ ]is[ ]cool,[ ]clever,[ ]and[ ]so[ ]on... # 替换 n 次 ret = re.sub('\d', 'G', 'henry18',n) print(ret) # 返回替换次数(tuple类型) ret = re.subn('\d', 'G', 'henry18') print(ret) # 返回值为tuple类型
4. 分组
4.1 特殊分组用法
语法 | 含义 | 示例 | |
---|---|---|---|
(?P |
分组,除了原有的编号外再指定一个额外的别名 | (?P |
abcabc |
(?P=name) | 引用别名为 |
(?P |
1abc15abc5 |
<number> | 引用编号为 |
(\d)abc\1 | 1abc15abc5 |
(?<=….) | 以…开头,并不包括开头 | ||
(?<!….) | 不以…结尾,并不包括开头 |
Note(3)
- [^] 带有特殊意义的元字符到字符组中大部分会取消它特殊意义
- [()+*.]:取消特殊含义,恢复原本意义
- [-]:第一个或最后表示横杠,中间位置表示范围
4.2. group()
- 括号中默认为0,即取第0个分组
s = '<h1>wahaha</h1>' ret = re.search('(\w+)>(.*?)</\w+>', s) print(ret.group()) print(ret.group(1)) print(ret.group(2))
4.3 分组命名
- (?P
正则表达式) - name:不需要加引号,本身就是字符串
ret = re.search('<(?P<tag>\w+)>(?P<content>.*?)</\w+>', s) print(ret.group('tag')) print(ret.group('content'))
4.4 引用分组
- (?P=name)
s = '<h1>wahaha</h1>' ret = re.search('(?P<tag>\w+)>.*?</(?P=tag)>', s) print(ret.group())
s = '<h1>wahaha</h1>' # \1 在python中有特殊含义 ret = re.search(r'(\w+)>.*?</\1>', s) print(ret.group())
4.5 取消分组优先
- (?:)
# findall 遇到正则中的分组 优先 显示分组中的内容 import re ret = re.findall('\d(\d)', 'henry18') print(ret) # 取消分组优先(?:正则表达式) ret = re.findall('\d+(?:\.\d+)?', '1.234+2') print(ret)
4.6 split,保留分割符
- ()
# 保留分组中内容 ret = re.split('(\d+)', 'henry18') print(ret)
5. 练习
# 示例1:匹配单个数字,findall方法会有屏蔽所有其他匹配项,只显示分组中内容 import re ret = re.findall(r'\d+\.\d+|(\d)', '2+23*3.42/3.2') print(ret) while True: if '' not in ret:break ret.remove('') print(ret)
# 示例2:匹配以...开头的数据,不包括开头 import re m = re.findall('(?<=>)\w+', '\<a>wahaha\</a>\<b>banana\</b>\<h1>qqxing\</h1>') for i in m: print(i) # 匹配不以...开头的数据,不包括结尾 m = re.findall('(?<!>)\w+', '\<a>wahaha\</a>\<b>banana\</b>\<h1>qqxing\</h1>') print(m)
| :或只负责把两个表达式分开,如果是整个表达式中只对一部分内容进行或,需要分组
():限定一组正则的量词约束 (\d\w)?
# 示例3:以a开头,由至少一个字母组成的字 ^a[a-zA-Z]+ ^a[a-zA-Z]*
# 以1开头,中间3-5个数字,如果中间位置超过5个数字,则整个字符串不匹配 ^1\d{3,5}$
# 示例4:匹配用户输入的身份证号 import re content = input('用户输入:') ret = re.match('[1-9]\d{14}(\d{2}[\dx])?$', content)
# 示例5:第一个乘除法 import re ret = re.search('\d+(\.\d+)?[\*]-?\d+(\.\d+)?', s)