函数
1.注意:函数的默认参数必须指向不可变对象
未修改前:
def add_end(L=[]): L.append('END') return L
存在的问题:如果连续调用多次,会出现多个 'END' 对象
原因解释:
Python函数在定义的时候,默认参数L就被计算出来了,即 [] ,因为默认参数L指向了可变对象[],每次调用的时候,如果改变了L的内容,下次调用的时候,L指向的内容也发生了改变,不再是函数定义时候的 [] 了。
修改后:
def add_end(L=None): if L is None: L = [] L.append('END') return L
这样无论调用多少次都不会出问题。
2.关键字参数
func(xx,xxx,**kw)
def person(name,age,**kw): print('name:',name,'age:',age,'other:',kw) extra = {'city': 'BeiJing','job': 'Python'} person('XM',23,**extra) # 输出 # name: XM age: 23 other: {'city': 'BeiJing', 'job': 'Python'}
注意:**extra表示把extra这个dict的所有key-value用关键字参数传递给函数的**kw参数,kw将获得一个dict,注意获得dict是extra的一份拷贝,修改kw不会对extra有任何影响。
高级特性模块
1.迭代器
可以被next()函数调用并不断返回下一个值的对象成为迭代器Iterator,可以使用isinstance()判断一个对象是否是Iterator对象
>>> from collections import Iterator >>> isinstance((x * x for x in range(10)),Iterator) True >>> isinstance([],Iterator) False >>> isinstance({},Iterator) False
生成器都是Iterator对象,但是list、dict、str、虽然是Iterable,却不是Iterator。
把list、dict、str等Iterable变成Iterator,可以使用iter()函数
>>> isinstance(iter([]),Iterator) True >>> isinstance(iter({}),Iterator) True
函数式编程
fitler用法
把一个序列中的空字符串删掉
# 1.把一个序列中的空字符串删掉 def not_empty(s): return s and s.strip() li = ['A','',None,'B',' ','C'] li = list(filter(not_empty,li)) print(li)
闭包
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量
# 返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量 def count(): fs = [] for i in range(1,4): def f(): return i * i fs.append(f) return fs f1,f2,f3 = count() print(f1()) # 输出9 print(f2()) # 输出9 print(f3()) # 输出9
# 原因就在于返回的函数引用了变量i,但它并非立刻执行,等到3个函数都返回时,他们所引用的变量i,已经变成了3
# 因此最终结果都为9
# 如果一定要引用循环变量怎么办?方法就是在创建一个函数,用该函数的参数绑定循环变量当前的值,无论循环变量后期如何变化 # 已绑定到函数参数的值不变 def count(): def f(j): def g(): return j * j return g fs = [] for i in range(1,4): fs.append(f(i)) return fs f1,f2,f3 = count() print(f1()) # 输出1 print(f2()) # 输出4 print(f3()) # 输出9
装饰器
# 定义装饰器 def log(func): def wrapper(*args,**kwargs): print('call %s()' % func.__name__) func(*args,**kwargs) return wrapper @log def now(): print('2018-04-25') # 调用now函数 now() # 输出 # call now() # 2018-04-25
解释:把@log放到now()函数定义处,相当于执行了语句
now = log(now)
由于log()是一个decorator,返回一个函数,所以原来的now()函数仍然存在,只是现在同名的now()指向了新的函数,于是调用now()将执行新的函数,即在log()函数中返回的wrapper()函数
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数
def log(txt): def decorator(func): def warpper(*args,**kwargs): print('%s %s' % (txt,func.__name__)) return func(*args,**kwargs) return warpper return decorator @log('execute') def now(): print('2018-04-25') # 调用now()函数 now() print('%s' % now.__name__) # 输出 # execute now # 2018-04-25 # wrapper
解释:和两层嵌套decorator相比,3层嵌套的效果是这样的:
now = log('execute')(now)
我们来剖析上面的语句,首先执行log('execute')返回的是decorator()函数,在调用返回的函数参数是now()函数,返回值最终是wrapper()函数
最后一步:因为我们讲的是函数也是对象,他有__name__等属性,但是你去看经过decorator()函数装饰之后的函数,他们的__name__已经从原来的的'now'变成'wrapper'了
所以需要将原始函数的__name__等属性复制到wrapper()函数中,否则又一些以来函数签名的代码执行要出错。
不需要编写wrapper.__name__ = func.__name__这样的代码,Python中内置了functools.wraps就是干这事的,所以一个完整的decorator的写法如下:
from functools import wraps def log(func): @wraps(func) def wrapper(*args,**kwargs): print('call %s' % func.__name__) return func(*args,**kwargs) return wrapper @log def now(): print('2018-04-25') # 调用函数 now() print(now.__name__) # 输出 # call now # 2018-04-25 # now def log(txt): def decorator(func): @wraps(func) def wrapper(*args,**kwargs): print('%s %s' % (txt,func.__name__)) return func(*args,**kwargs) return wrapper return decorator @log('execute') def now(): print('2018-04-25') # 调用函数 now() print(now.__name__) # 输出 # execute now # 2018-04-25 # now
偏函数
通过functools.partial可以帮助我们创建一个偏函数
from functools import partial
int2 = partial(int,base=2) ret = int2('100') print(ret) # 输出 # 2 max2 = partial(max,10) ret = max2(2,3,4) print(ret) # 输出 # 10
简单总结functools.partial的作用,就是把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新的函数会更简单
面向对象编程
需要注意的是,在Python中,变量名类似__xxx__的,也就是双下划线开头,双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以不能用__name__、__score__这样的变量名
双下划线开头的实例变量是不是就不能被外部访问了?其实不是的,不能只能访问__name是因为Python解释器对外吧__name变量改成了_Student__name,所以可以通过_Student__name来访问__name变量:
bart._Student__name
但是强烈不推荐这么做,因为不同版本的Python解释器可能会把__name改成不同的变量名
如何判断一个对象是否是函数怎么办?可以使用types模块中定义的常量:
import types def Animals(): pass a = Animals() print(type(Animals) == types.FunctionType)
如何给实例绑定一个方法?
class Student(): pass def set_age(self,age): # 定义一个函数作为实例方法 self.age = age s = Student() from types import MethodType s.set_age = MethodType(set_age,s) # 给实例绑定一个方法 s.set_age(25) # 调用实例方法 print(s.age) # 测试结果
为了给所有实例都绑定方法,可以给class绑定方法:
Student.set_age = MethodType(set_age,Student) s1 = Student() s1.set_age(12) print(s1.age) s2 = Student() s2.set_age(23) print(s2.age)
__slots__的使用
如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加name和age属性。
为了达到目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:
class Student: __slots__ = ('name','age') # 用tuple定义绑定的属性名称 s = Student() s.name = 'XM' s.age = 34 s.score = 98 # 绑定属性score 报错 # AttributeError: 'Student' object has no attribute 'score'
除非在子类中也定义__slots__,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__
class GraduateStudent(Student): __slots__ = ('score') # 除非子类也定义__slots__,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__ pass g = GraduateStudent() g.score = 97 g.name = 'DN'
@property的使用
class Student:
@property def score(self): return self._score @score.setter def score(self,value): if not isinstance(value,int): raise ValueError('score must be int') if value < 0 or value >100: raise ValueError('score must be 0~100') self._score = value s = Student() s.score = 90 print(s.score)
注意到这个神奇的@property,我们在对实例属性操作的时候,就知道该属性很可能不是直接暴漏的,而是通过getter和setter方法实现的。还可以设置只读属性,不定义setter方法就是只读属性
__str__和__repr__的区别?
在不使用print的时候,直接显示变量的调用的不是__str__(),而是__repr__(),两者的区别是__str__()返回用看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说,__repr__()是为了调试服务的。
偷懒的写法是
class Student: def __init__(self,name): self.name = name def __str__(self): print('Student object(name = %s)' % self.name) __repr__ = __str__
__iter__
如果一个类想被用于for...in循环,类似list或tuple那样,就是必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断的调用迭代对象的__next__()方法,拿到循环的下一个值,直到StopIteration错误时推出循环。
# 以斐波那契数列为例,写一个Fib类
class Fib: def __init__(self): self.a,self.b = 0,1 def __iter__(self): return self def __next__(self): self.a,self.b = self.b,self.a + self.b # 计算下一个值 if self.a > 100: raise StopIteration() return self.a for n in Fib(): print(n)
__getitem__/__setitem__/__delitem__的使用方法
Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,如果要表现的和list那样按照下标取元素,需要实现__getitem__()方法
class Fib: def __init__(self): self.a,self.b = 0,1 def __iter__(self): return self def __next__(self): self.a,self.b = self.b,self.a + self.b # 计算下一个值 if self.a > 100: raise StopIteration() return self.a def __getitem__(self, item): a,b = 1,1 for x in range(item): a,b = b,a+b return a for n in Fib(): print(n) print(Fib()[5]) # 8 print(Fib()[10]) # 89
但是list有个神奇的切片方法:
list(range(100))[5:10]
对于Fib却报错,原因是__getitem__()传入的参数可能是一个int,也可以是一个切片对象slice,所以要做判断:
class Fib: def __getitem__(self, item): if isinstance(item,int): a,b = 1,1 for i in range(item): a,b = b,a+b return a if isinstance(item,slice): start,stop = item.start,item.stop if start is None: start = 0 a,b = 1,1 L = [] for x in range(stop): if x >= start: L.append(a) a,b = b,a+b return L print(Fib()[0,5])
与之对应的是__setitem__()方法,把对象视作list或dict来对集合赋值,最后,还有一个__delitem__()方法,用于删除某个元素
__getattr__/__setattr__/__delattr__
正常情况下我们调用属性或方法不存在时会报错,比如定义Student类:
class Student: def __init__(self): self.name = 'Michel' s = Student() print(s.name) print(s.age) # Michel # Traceback (most recent call last): # File "/Users/qianhaichao/Desktop/Python练习/练习项目/LF-Project/Python练习/廖雪峰Python/面向对象编程.py", line 169, in <module> # print(s.age) # AttributeError: 'Student' object has no attribute 'age'
调用name属性,没问题,但是调用不存在的age属性,就有问题了,而且很明显的告诉我们没有找到age这个attribute
要避免这个错误,除了可以加上一个age属性外,Python还有另一个机制,那就是写一个__getattr__()方法,动态的返回一个属性,修改如下:
class Student: def __init__(self): self.name = 'Michel' def __getattr__(self, item): if item == 'age': return 99 s = Student() print(s.name) # Michel print(s.age) # 99
返回函数也是完全可以的
class Student: def __getattr__(self, item): if item == 'age': return lambda: 25 s = Student() print(s.age()) #只是调用方式变了
此外,注意到任意调用如s.abc都会返回None,不会报错,这是因为我们定义了__getattr__默认返回的就是None,要让class只响应特定的几个属性,我们就按照约定,抛出AttributeError的错误:
class Student: def __getattr__(self, item): if item == 'age': return lambda: 25 raise AttributeError('Student object has no attribute %s' % item)
这实际上可以把一个类的所有属性和方法调用全部动态化处理了,不需要做任何特殊手段。
这种完全动态调用的特性有什么实际作用呢?作用就是,可以针对完全动态的情况作调用。
现在很多网站都搞RESTAPI,比如新浪微博、豆瓣啥的,调用API的URL类似:
http://api.server/user/friends
http://api.server/user/timline/list
如果要写SDK,给每个URL对应的API都写一个方法,那得累死,而且API一旦改动,SDK也要改动。
利用完全动态的__getattr__,我们可以写一个链式调用:
class Chain: def __init__(self,path=''): self.path = path def __getattr__(self, attr): return Chain('%s/%s' % (self.path,attr)) def __str__(self): return self.path __repr__ = __str__ url = Chain().status.user.timeline.list print(url) # /status/user/timeline/list
这样,无论API怎么变,SDK都可以根据URL完全动态的调用,而且不随API的增加而改变
__call__的使用
任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用
class Student: def __init__(self,name): self.name = name def __call__(self, *args, **kwargs): print('My name is %s' % self.name) s = Student('XM') s() # My name is XM
__call__()还可以定义参数,对实例进行直接调用就好比对一个函数进行调用,所以你完全可以把对象看成函数,把函数看成对象,因为这两者之间本来就没啥根本的区别。
怎么判断一个变量是对象还是函数呢?其实更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable对象,比如函数和我们上面定义的带有__call__()的累实例
class Student: def __init__(self,name): self.name = name def __call__(self, *args, **kwargs): print('My name is %s' % self.name) print(callable(Student('XM'))) # True print(callable(max)) # True print(callable([1,2,3])) # False print(callable('str')) # False
使用枚举类
Python提供了Enum类来实现这个功能:
from enum import Enum Month = Enum('Month',('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')) for name,member in Month.__members__.items(): print('name:',name,'member:',member,'value:',member.value) print(Month.Jan.value)
value属性则是自动赋给成员的int常量,默认是从1开始计数的。
如果要使用更精确的控制枚举类型,可以从Enum派生出自定义类:
from enum import Enum,unique
from enum import Enum,unique class Weekday(Enum): Sun = 0 # Sun的value被设定为0 Mon = 1 Tue = 2 Wed = 3 Thu = 4 Fri = 5 Sat = 6 # @unique可以帮助我们检查保证没有重复值 day1 = Weekday.Mon print(day1 == Weekday.Mon)
元类的使用(暂且先不研究)
单元测试
我们来编写一个Dict类,这个类的行为和dict一致,但是可以通过属性来访问,用起来就像下面这样:
d = Dict(a=1,b=2) print(d['a']) # 1 print(d.a) # 1
Dict.py代码如下:
class MyDict(dict): def __init__(self,**kw): super().__init__(**kw) def __getattr__(self, key): try: return self[key] except KeyError: raise AttributeError('MyDict object has no %s attribute' % key) def __setattr__(self, key, value): self[key] = value
为了编写单于测试,我们需要引入Python自带的unittest模块,编写TestDict.py如下:
from Dict import MyDict import unittest class TestDict(unittest.TestCase): def test_init(self): d = MyDict(a=1,b='test') self.assertEqual(d.a,1) self.assertEqual(d.b,'test') self.assertTrue(isinstance(d,dict)) def test_key(self): d = MyDict() d['key'] = 'value' self.assertEqual(d['key'],'value') def test_attr(self): d = MyDict() d.key = 'value' self.assertTrue('key' in d) self.assertEqual(d['key'],'value') def test_keyerror(self): d = MyDict() with self.assertRaises(KeyError): value = d['empty'] def test_attrerror(self): d = MyDict() with self.assertRaises(AttributeError): value = d.empty if __name__ == '__main__': unittest.main()
setUp与tearDown
可以编写单元测试中编写两个特殊的setUp()和tearDown()方法,这两个方法会分别在没调用一个测试方法的前后分别被执行。
setUp()和tearDown()方法有什么用呢?设想你的测试需要启动一个数据库,这时,就可以在setup()方法中连接数据库,在tearDown()方法中关闭数据库,这样不必在每个测试方法中重复相同的代码:
IO编程
multiprocess模块提供一个一个Process类来代表进程对象,下面的例子演示启动一个子进程并等待其结束:
Process
from multiprocessing import Process import os def run_proc(name): print('Run child process %s (%s)' % (name,os.getpid())) if __name__ == '__main__': print('Parent process %s' % os.getpid()) p = Process(target=run_proc,args=('test',)) print('Child prcess will start') # 启动子线程 p.start() # 等待子线程结束后在继续 p.join() print('Process End')
执行结果如下:
Parent process 27352
Child prcess will start
Run child process test (27353)
Process End
创建子进程时,只需要传入一个执行函数和函数参数,创建一个Process实例,用start()方法启动,这样创建进程比fork()还要简单,join()方法可以等待子线程结束后在继续往下运行,通常用于进程间的同步。
Pool
from multiprocessing import Pool import os,time,random def long_time_task(name): print('Run task %s(%s)' % (name,os.getpid())) start = time.time() time.sleep(random.random() * 3) end = time.time() print('Task %s run %.02f seconds' % (name,(end-start))) if __name__ == '__main__': print('Parent process %s' % os.getpid()) p = Pool(4) for i in range(5): p.apply_async(long_time_task,args=(i,)) print('waitting all subprocesses done') p.close() p.join() print('All subprocesses done.') # 输出结果 # Parent process 27468 # waitting all subprocesses done # Run task 0(27469) # Run task 1(27470) # Run task 2(27471) # Run task 3(27472) # Task 3 run 1.03 seconds # Run task 4(27472) # Task 2 run 1.27 seconds # Task 4 run 0.56 seconds # Task 0 run 2.53 seconds # Task 1 run 2.96 seconds # All subprocesses done.
代码解读:
对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须调用close(),调用close()之后就不能继续添加新的Process了。
多线程
Python的标准库提供了两个模块:_thread和threading,_thread是低级模块,threading是高级模块,对_thread进行了封装,绝大多数情况下,我们只需要使用threading这个高级模块、
import threading import time def loop(): print('Thread %s is running...' % threading.current_thread().name) n = 0 while n < 5: n = n + 1 print('Thread %s >>> %s' % (threading.current_thread().name,n)) time.sleep(1) print('Thread %s end' % threading.current_thread().name) print('Thread %s is running' % threading.current_thread().name) t = threading.Thread(target=loop,name='LoopThread') t.start() t.join() print('Thread %s end' % threading.current_thread().name) # 输出结果 # Thread MainThread is running # Thread LoopThread is running... # Thread LoopThread >>> 1 # Thread LoopThread >>> 2 # Thread LoopThread >>> 3 # Thread LoopThread >>> 4 # Thread LoopThread >>> 5 # Thread LoopThread end # Thread MainThread end
由于任何进程都有一个默认的线程,我们把这个线程称为主线程,主线程又可以启动新的线程,Python的threading模块有一个current_thread()函数,他永远返回当前线程的实例,主线程实例的名字叫MainThread,子线程的名字在创建是指定,我们用LoopThread命名子线程,名字仅仅在打印时用来显示。、
正则表达式
*表示任意一个字符(包括0个),+表示至少一个字符,?表示0个或者1个字符,用{n}表示n个字符
常用内建模块
datetime
1.获取系统当前时间
from datetime import datetime
now = datetime.now() print(now) print(type(now))
2.获取指定日期和时间
dt = datetime(2018,5,3,17,9) print(dt)
3.datetime转换为timestamp
ts = dt.timestamp() print(ts) ts = now.timestamp() print(ts)
4.timestamp转换为datetime
ts = 1525338540 dt = datetime.fromtimestamp(ts) print(dt)
注意到timestamp是一个浮点数,他没有时区的概念,而datetime是有时区的,上述转换是在tiemstamp和本地时间做转换的。
5.timestamp也可以直接被转换为UTC标准时区的时间
dt = datetime.utcfromtimestamp(ts) print(dt)
6.str转换为datetime
dt = datetime.strptime('2018-05-03 17:30','%Y-%m-%d %H:%M') print(dt)
7.datetime转换为str
st = datetime.strftime(datetime.now(),'%Y-%m-%d %H:%M') print(st)
8.datetime的加减
from datetime import datetime,timedelta now = datetime.now() dt = now + timedelta(hours=10) print(dt) dt = now + timedelta(days=2,hours=3) print(dt)
9.时区转换
拿到UTC时间,并强制设置时区为UTC+0:00
utc_dt = datetime.utcnow().replace(tzinfo=timezone.utc) print(utc_dt)
astimezone()将时区转换为北京时区
bj_dt = utc_dt.astimezone(timezone(timedelta(hours=8))) print(bj_dt)
小结:
datetime表示的时间需要时区信息才能确定一个特定的时间,否则只能视为本地时间
如果要存储datetime,最佳方法是将其转换为timestamp在存储,因为timestamp的值与时区完全无关
collections
1.namedtuple namedtuple('名称',[属性list])
from collections import namedtuple
Point = namedtuple('Point',['x','y']) p = Point(1,2) print(p.x) print(p.y)
可以根据属性名来访问tuple,使用起来十分方便
2.deque
使用list存储数据的时候,按索引访问元素是很快的,但是插入和删除元素就很慢了,因为list是线性储存,数据量大的时候,插入和删除效率很低。
deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈。
from collections import deque q = deque(['a','b','c','d']) q.append('x') q.appendleft('1') print(q)
deque除了实现list的append()和pop()外,还支持appendleft()和popleft(),这样可以非常高效的往头部添加和删除元素。
3.OrderedDict
使用dict时,key是无序的,在对dict做迭代时,我们无法确定key的顺序,如果要保证key的顺序,可以使用OrderDict:
from collections import OrderedDict d = dict([('a',1),('b',2),('c',3)]) print(d) d = OrderedDict([('a',1),('b',2),('c',3)]) print(d)
注意:OrderedDict的key会按照插入的顺序排列,不是可以本身排序
OrderedDict可以实现一个FIFO(先进先出)的dict,当容量超出限制时,会删除最早添加的key
4.Counter
统计字符出现的个数:
from collections import Counter c = Counter() for ch in 'programming': c[ch] = c[ch] + 1 print(c)
5.Struct
pass
6.hashlib
import hashlib md5 = hashlib.md5() md5.update('how to use md5 in python hashlib'.encode('utf-8')) ret = md5.hexdigest() print(ret)
如果数据量很大,可以分块多次调用update(),最后计算的结果是一样的。
7.itertools
首先看看itertools提供的几个无限迭代器
cycle()传入一个队列,无限循环下去
cs = itertools.cycle('ABC') for c in cs: print(c)
repeat()负责把一个元素无限循环下去
ns = itertools.repeat('A',3) for n in ns: print(n)
takewhile()根据条件取出有限序列
ns = itertools.count(1) tw = itertools.takewhile(lambda x: x < 10,ns) for t in tw: print(t)
chain()把一组有序队列串联起来,拼成一个更大的队列
ch = itertools.chain('ABC','XYZ') for c in ch: print(c) print(ch) print(type(ch))
groupby()把序列中相邻且重复的序列跳出来放在一起
gb = itertools.groupby('AABBBCCCADDDBBEEEFF') for key,group in gb: print(key,list(group))
HTMLParser
抓取html的商品名称
from html.parser import HTMLParser html_str = ''' <h3 class="tb-main-title" data-title="【金冠现货/全色/顶配版】Xiaomi/小米 小米note移动联通4G手机"> 【金冠现货/全色/顶配版】Xiaomi/小米 小米note移动联通4G手机 </h3> <p class="tb-subtitle"> 【购机即送布丁套+高清贴膜+线控耳机+剪卡器+电影支架等等,套餐更多豪礼更优惠】 【购机即送布丁套+高清贴膜+线控耳机+剪卡器+电影支架等等,套餐更多豪礼更优惠】 【金冠信誉+顺丰包邮+全国联保---多重保障】 </p> <div id="J_TEditItem" class="tb-editor-menu"></div> </div> <h3 class="tb-main-title" data-title="【现货增强/标准】MIUI/小米 红米手机2红米2移动联通电信4G双卡"> 【现货增强/标准】MIUI/小米 红米手机2红米2移动联通电信4G双卡 </h3> <p class="tb-subtitle"> [红米手机2代颜色版本较多,请亲们阅读购买说明按需选购---感谢光临] 【金皇冠信誉小米手机集市销量第一】【购买套餐送高清钢化膜+线控通话耳机+ 剪卡器(含还原卡托)+ 防辐射贴+专用高清贴膜+ 擦机布+ 耳机绕线器+手机电影支架+ 一年延保服务+ 默认享受顺丰包邮 ! </p> <div id="J_TEditItem" class="tb-editor-menu"></div> </div> ''' # 定义一个MyHTMLParser继承自HTMLParser class MyHTMLParser(HTMLParser): re = [] # 放置结果 flag = 0 # 标志是否是我们想要的标签 def handle_starttag(self, tag, attrs): if tag == 'h3': for attr in attrs: if attr[0] == 'class' and attr[1] == 'tb-main-title': self.flag = 1 break else: pass def handle_data(self, data): if self.flag: self.re.append(data.strip()) self.flag = 0 # 重置标记位 else: pass myparser = MyHTMLParser() myparser.feed(html_str) print(myparser.re)