廖雪峰Python电子书总结

函数

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)
原文地址:https://www.cnblogs.com/it-q/p/8885721.html