Python 学习之路(下)

一、多态【了解】

一种事物的多种体现形式,函数的重写其实就是多态的一种体现

在Python中,多态指的是父类的引用指向子类的对象

代码演示:

#父类
class Animal(object):
    pass

#子类
class Dog(Animal):
    pass

class Cat(Animal):
    pass

#定义变量
a = []   #a是list类型
b = Animal()  #b是Animal类型
c = Cat()  #c是Cat类型

#isinstance():判断一个对象是否属于某种类型【系统还是自定义的类型】
print(isinstance(a,list))
print(isinstance(b,Animal))
print(isinstance(c,Cat))

print(isinstance(c,Animal))  #True
print(isinstance(b,Dog))   #False

#结论:子类对象可以是父类类型,但是,父类的对象不能是子类类型

总结:

​ 简化代码,提高代码的可读性,可维护性

二、获取对象信息

type() isintance() dir()

代码演示:

#1.type() :判断一个对象所属的类型
num = 10
print(type(num))
print(type("hello"))

class Check(object):
    pass
c = Check()
print(type(c))

#使用==判断type返回的结果
print(type(12) == type(57))  #True
print(type(12) == type("57"))  #False

#使用type返回的结果和数据类型直接判断
print(type(12) == int)

#2.isintance(): 判断一个对象是否属于某种指定的数据类型
#自定义的类中
class Dog(object):
    pass

d = Dog()
print(isinstance(d,Dog))
print(isinstance([1,2,4],list))

#特殊用法:可以判断一个对象是否属于多种数据类型中的某一种
print(isinstance([1,2,4],(tuple,list)))

#3.dir()  :列出指定对象中所包含的所有的内容【成员变量,成员方法】
dict = {}
print(dir(dict))

print(dir("abc"))

print(dir(d))

三、类中特殊的属性和方法

1.实例属性和类属性
1.1实例属性和类属性的区别【面试题】

a.定义的位置不同,类属性时直接定义在类中,实例属性定义在构造函数中

b.访问的方式不同,类属性使用类名直接访问,实例属性使用对象访问

c.在内存中出现的时机不同,类属性随着类的出现而出现,实例属性随着对象的出现而出现

d.优先级不同,实例属性的优先级高于类属性

代码演示:

class Person(object):
    #1.定义位置
    #类属性:直接定义在类中
    name = "abc"
    age = 0

    def __init__(self,name):
        #实例属性:定义在构造函数中
        self.name = name


#2.访问方式
print(Person.name)  #类属性:类名.属性 或者 对象.属性

p = Person("hello")
print(p.name)   #实例属性:对象.属性

#3.优先级不同:实例属性的优先级高于类属性
print(p.name)   #hello

#4.不同对象的类属性在内存中是不是同一块空间?----->不是
p1 = Person("小白")
p2 = Person("小红")
print(p1.age)
print(p2.age)
p1.age = 33
print(p1.age)
print(p2.age)
print(id(p1.age))
print(id(p2.age))
"""
0
0
33
0
1420404832
1420403776
"""

#注意:尽量避免类属性和实例属性的重名

#删除属性【类属性,实例属性】
del p1.age
1.2动态添加属性和方法

代码演示:

from  types import MethodType


class Person(object):
    #__slots__ = ("name","age")
    pass


#1.动态添加属性
per = Person()
str = "fjsgh"
per.name = str

#2.动态添加方法
def say(self):
    print("fhsj")
"""
per.test = say
per.test(per)
"""

#弊端:违背了普通函数定义
#解决方案:MethodType类,存在于types模块下

#类似于偏函数
#参数:函数名,对象
#作用:在现有函数的基础上生成了一个对象【新的函数】,赋值给成员变量,则认为给对象添加了一个成员方法
per.test = MethodType(say,per)
per.test()
2.类方法和静态方法

类方法:使用@classmethod装饰器修饰的方法,被称为类方法,可以通过类名调用,也可以通过对象调用,但是一般情况下使用类名调用

静态方法:使用@staticmethod装饰器修饰的方法,被称为静态方法,可以通过类名调用,也可以通过对象调用,但是一般情况下使用类名调用

代码演示:

class Test(object):
    #1.类属性
    age = 100

    def __init__(self,name):
        #2.实例属性
        self.name = name

    #3.成员方法,通过对象调用
    #必须有一个参数,这个参数一般情况下为self,self代表是当前对象
    def func(self):
        print("func")

    #4.类方法
    """
    a.必须有一个参数,这个参数一般情况下为cls,cls代表的是当前类
    b.类方法是属于整个类的,并不是属于某个具体的对象,在类方法中禁止出现self
    c.在类方法的内部,可以直接通过cls调用当前类中的属性和方法
    d.在类方法的内部,可以通过cls创建对象
    """
    @classmethod
    def test(cls):
        print("类方法")
        print(cls)   #<class 'methodDemo01.Test'>
        print(cls.age)

        #6
        #注意:cls完全当做当前类使用
        c = cls("hello")
        c.func()

    #7.静态方法
    @staticmethod
    def show():
        print("静态方法")

t = Test("hjfsh")
t.func()

#5,.调用类方法
Test.test()   #类名.类方法的名称()
t.test()       #对象.类方法的名称()

#7。调用静态方法
Test.show()
t.show()

总结:实例方法【成员方法】、类方法以及静态方法之间的区别

a.语法上

​ 实例方法:第一个参数一般为self,在调用的时候不需要传参,代表的是当前对象【实例】

​ 静态方法:没有特殊要求

​ 类方法:第一个参数必须为cls,代表的是当前类

b.在调用上

​ 实例方法:只能对象

​ 静态方法:对象 或者 类

​ 类方法:对象 或者 类

c.在继承上【相同点】

​ 实例方法、静态方法、类方法:当子类中出现和父类中重名的函数的时候,子类对象调用的是子类中的方法【重写】

代码演示:

class SuperClass(object):
    @staticmethod
    def show():
        print("父类中的静态方法")

    @classmethod
    def check(cls):
        print("父类中的类方法")

class SubClass(SuperClass):
    pass

s = SubClass()
s.show()
s.check()

注意:注意区分三种函数的书写形式,在使用,没有绝对的区分

3.类常用属性
__name__
	通过类名访问,获取类名字符串
	不能通过对象访问,否则报错
	
__dict__
	通过类名访问,获取指定类的信息【类方法,静态方法,成员方法】,返回的是一个字典
	通过对象访问,获取的该对象的信息【所有的属性和值】,,返回的是一个字典
	
__bases__
	通过类名访问,查看指定类的所有的父类【基类】

代码演示:

class Animal(object):
    def __init__(self,arg):
        super(Animal, self).__init__()
        self.arg = arg


class Tiger(Animal):
    age = 100
    height = 200

    def __init__(self,name):
        #super(Tiger, self).__init__(name)
        self.name = name

    def haha(self):
        print("haha")

    @classmethod
    def test(cls):
        print("cls")

    @staticmethod
    def show():
        print("show")


if __name__ == "__main__":

    #1.__name__
    print(Tiger.__name__)  #Tiger

    t = Tiger("")
    #print(t.__name__)  #AttributeError: 'Tiger' object has no attribute '__name__'

    #2.__dict__
    print(Tiger.__dict__)  #类属性,所有的方法
    print(t.__dict__)   #实例属性

    #3.__bases__,获取指定类的所有的父类,返回的是一个元组
    print(Tiger.__bases__)

四、运算符重载【了解】

运算符重载其实就是函数重写

代码演示:

print(1 + 1)
print("1" + "1")
#print("1" + 1)
#不同的数据类型进行加法运算得到的是不同的解释

#思考问题:两个对象相加?
class Person(object):
    def __init__(self,num):
        self.num = num

    def __str__(self):
        return "num=" + str(self.num)

    def __add__(self, other):
        #两个对象相加得到的结果仍然为一个对象
        return Person(self.num + other.num)   #Peson(30)


p1 = Person(10)
p2 = Person(20)

print(p1)  #10
print(p2)  #20

print(p1 + p2)  #30

#p1 + p2----->p1.__add__(p2),

五、单例设计模式【扩展】

1.概念

什么是设计模式

​ 经过已经总结好的解决问题的方案

​ 23种设计模式,比较常用的是单例设计模式,工厂设计模式,代理模式,装饰模式

什么是单例设计模式

​ 单个实例【对象】

​ 在程序运行的过程中,确保某一个类只能有一个实例【对象】,不管在哪个模块中获取对象,获取到的都是同一个对象

​ 单例设计模式的核心:一个类有且仅有一个实例,并且这个实例需要应用在整个工程中

2.应用场景

实际应用:数据库连接池操作-----》应用程序中多处需要连接到数据库------》只需要创建一个连接池即可,避免资源的浪费

3.实现
3.1模块

Python的模块就是天然的单例设计模式

模块的工作原理:

​ import xxx,模块被第一次导入的时候,会生成一个.pyc文件,当第二次导入的时候,会直接加载.pyc文件,将不会再去执行模块源代码

3.2使用new
__new__():实例从无到有的过程【对象的创建过程】

代码演示:

class Singleton(object):
    #类属性
    instance = None

    #类方法
    @classmethod
    def __new__(cls, *args, **kwargs):
        #如果instance的值不为None,说明已经被实例化了,则直接返回;如果为NOne,则需要被实例化
        if not cls.instance:
            cls.instance = super().__new__(cls)

        return cls.instance

class MyClass(Singleton):
    pass

#当创建对象的时候自动被调用
one = MyClass()
two = MyClass()

print(id(one))
print(id(two))

print(one is two)
3.3装饰器

代码演示:

#单例类:将装饰器作用于一个类上
def singleton(cls):
    #类属性
    instance = {}

    #成员方法
    def getSingleton(*args, **kwargs):
        #思路:如果cls在字典中,则直接返回;如果不存在,则cls作为key,对象作为value,添加到字典中
        if cls not in instance:
            instance[cls] = cls(*args, **kwargs)
        return  instance[cls]

    return getSingleton

@singleton
class Test(object):
    pass

t1 = Test()
t2 = Test()

print(id(t1) == id(t2))
print(t1 is t2)
3.4使用在类中

代码演示:

#单例类
class Foo(object):
    #1.声明一个变量【类属性】
    instance = None

    #2.向外界提供一个公开的方法,用于返回当前类唯一的对象
    #方法命名格式:defaultInstance,currentInstance ,getInstance
    @classmethod
    def getInstance(cls):
        if cls.instance:
            return cls.instance
        else:
            #实例化
            cls.instance = cls()
            return  cls.instance

obj1 = Foo.getInstance()
obj2 = Foo.getInstance()

print(id(obj1) == id(obj2))
print(obj1 is obj2)

五、错误和异常

1.概念

两种容易辨认的错误

​ 语法错误:一些关于语法的错误【缩进】

​ 异常:代码完全正确,但是,程序运行之后,会报出 的错误

exception/error

代码演示:

list1 = [23,54,6,6]
print(list1[2])
print(list1[3])
print(list1[4])  

print("over")

"""
6
6
Traceback (most recent call last):
  File "C:/Users/Administrator/Desktop/SZ-Python/Day15Code/textDemo01.py", line 4, in <module>
    print(list1[4])
IndexError: list index out of range
"""

异常特点:当程序在执行的过程中遇到异常,程序将会终止在出现异常的代码处,代码不会继续向下执行

解决问题:越过异常,保证后面的代码继续执行【实质:将异常暂时屏蔽起来,目的是为了让后面的代码的执行不受影响】

2.常见的异常

NameError:变量未被定义

TypeError:类型错误

IndexError:索引异常

keyError:

ValueError:

AttributeError:属性异常

ImportError:导入模块的时候路径异常

SyntaxError:代码不能编译

UnboundLocalError:试图访问一个还未被设置的局部变量

3.异常处理方式【掌握】

捕获异常

抛出异常

3.1捕获异常
try-except-else

语法:

​ try:

​ 可能存在异常的代码

​ except 错误表示码 as 变量:

​ 语句1

​ except 错误表示码 as 变量:

​ 语句2

​ 。。。

​ else:

​ 语句n

说明:

​ a.try-except-else的用法类似于if-elif-else

​ b.else可有可无,根据具体的需求决定

​ c.try后面的代码块被称为监测区域【检测其中的代码是否存在异常】

​ d.工作原理:首先执行try中的语句,如果try中的语句没有异常,则直接跳过所有的except语句,执行else;如果try中的语句有异常,则去except分支中进行匹配错误码,如果匹配到了,则执行except后面的语句;如果没有except匹配,则异常仍然没有被拦截【屏蔽】

代码演示:

#一、try-except-else的使用

#1.except带有异常类型
try:
    print(10 / 0)
except ZeroDivisionError as e:
    print("被除数不能为0",e)

print("~~~~")
"""
总结:
a.try-except屏蔽了异常,保证后面的代码可以正常执行
b.except ZeroDivisionError as e相当于声明了一个ZeroDivisionError类型的变量【对象】,变量e中携带了错误的信息
"""

#2.try后面的except语句可以有多个
class Person(object):
    __slots__ = ("name")
try:
    p = Person()
    p.age = 19

    print(10 / 0)
except AttributeError as e:
    print("属性异常",e)
except ZeroDivisionError as e:
    print("被除数不能为0",e)

print("over")

"""
总结:
a.一个try语句后面可以有多个except分支
b.不管try中的代码有多少个异常,except语句都只会被执行其中的一个,哪个异常处于try语句的前面,则先先执行对应的except语句
c.后面的异常不会报错【未被执行到】
"""

#3.except语句的后面可以不跟异常类型
try:
    print(10 / 0)
except:
    print("被除数不能为0")


#4.一个except语句的后面可以跟多种异常的类型
#注意:不同的异常类型使用元组表示
try:
    print(10 / 0)
except (ZeroDivisionError,AttributeError):
    print("出现了异常")


#5.else分支
try:
    print(10 / 4)
except ZeroDivisionError as e:
    print("出现了异常",e)
else:
    print("hello")

"""
总结:
a.如果try中的代码出现了 异常,则直接去匹配except,else分支不会被执行
b.如果try中的代码没有出现异常,则try中的代码正常执行,except不会被执行,else分支才会被执行
"""

#6.try中不仅可以直接处理异常,还可以处理一个函数中的异常
def show():
    x = 1 / 0

try:
    show()
except:
    print("出现了异常")

#7.直接使用BaseException代替所有的异常
try:
    y = 10 / 0
except BaseException as e:
    print(e)

"""
总结:在Python中,所有的异常其实都是类,他们都有一个共同的父类BaseException,可以使用BaseException将所有异常“一网打尽”
"""
try-except-finally

语法:

​ try:

​ 可能存在异常的代码

​ except 错误表示码 as 变量:

​ 语句1

​ except 错误表示码 as 变量:

​ 语句2

​ 。。。

​ finally:

​ 语句n

说明:不管try中的语句是否存在异常,不管异常是否匹配到了except语句,finally语句都会被执行

作用:表示定义清理行为,表示无论什么情况下都需要进行的操作

代码演示:

#二、try-except-finally的使用

#1.
try:
    print(10 / 5)
except ZeroDivisionError as e:
    print(e)

finally:
    print("finally被执行")


#2.特殊情况
#注意:当在try或者except中出现return语句时,finally语句仍然会被执行
def show():
    try:
        print(10 / 0)
        return
    except ZeroDivisionError as e:
        print(e)

    finally:
        print("finally被执行~~~~")

show()
3.2抛出异常

raise抛出一个指定的异常对象

语法:raise 异常对象 或者 raise

说明:异常对象通过错误表示码创建,一般来说错误表示码越准确越好

代码演示:

#raise的使用主要体现在自定义异常中

#1.raise表示直接抛出一个异常对象【异常是肯定存在的】
#创建对象的时候,参数表示对异常信息的描述
try:
    raise NameError("hjafhfja")
except NameError as e:
    print(e)

print("over")

"""
总结:
通过raise抛出的异常,最终还是需要通过try-except处理
"""

#2.如果通过raise抛出的异常在try中不想被处理,则可以通过raise直接向上抛出
try:
    raise NameError("hjafhfja")
except NameError as e:
    print(e)
    raise
4.assert断言

对某个问题做一个预测,如果预测成功,则获取结果;如果预测失败,则打印预测的信息

代码演示:

def func(num,divNum):

    #语法:assert表达式,当出现异常时的信息描述
    #assert关键字的作用:预测表达式是否成立,如果成立,则执行后面的代码;如果不成立,则将异常的描述信息打印出来
    assert (divNum != 0),"被除数不能为0"

    return  num / divNum

print(func(10,20))
print(func(10,0))
5.自定义异常

实现思路:

a.定义一个类,继承自Exception类

b.书写构造函数,属性保存异常信息【调用父类的构造函数】

c.重写__str__函数,打印异常的信息

d.定义一个成员函数,用来处理自己的异常

代码演示:

class MyException(Exception):
    def __init__(self,msg):
        super(MyException,self).__init__()
        self.msg = msg

    def __str__(self):
        return self.msg

    def handle(self):
        print("出现了异常")

try:
     raise MyException("自己异常的类型")
except MyException as e:
     print(e)
     e.handle()

六、文件读写

1.概念

在Python中,通过打开文件生成一个文件对象【文件描述符】操作磁盘上的文件,操作主要有文件读写

2.普通文件的读写

普通文件包含:txt文件,图片,视频,音频等

2.1读文件

操作步骤:

​ a.打开文件:open()

​ b.读取文件内容:read()

​ c.关闭文件:close()

说明:最后一定不要忘了文件关闭,避免系统资源的浪费【因为一个文件对象会占用系统资源】

代码演示:

#一、打开文件
"""
open(path,flag[,encoding,errors])
path:指定文件的路径【绝对路径和相对路径】
flag:打开文件的方式
    r:只读、
    rb:read binary,以二进制的方式打开,只读【图片,视频,音频等】
    r+:读写

    w:只能写入
    wb:以二进制的方式打开,只能写入【图片,视频,音频等】
    w+:读写

    a:append,如果一个文件不为空,当写入的时候不会覆盖掉原来的内容
encoding:编码格式:gbk,utf-8
errors:错误处理
"""
path = r"C:\Users\Administrator\Desktop\SZ-Python\Day15\笔记\致橡树.txt"
#调用open函数,得到了文件对象
f = open(path,"r",encoding="gbk")

"""
注意:
a.以r的方式打开文件时,encoding是不是必须出现
    如果文件格式为gbk,可以不加encoding="gbk"
    如果文件格式为utf-8,必须添加encoding="utf-8"
b。如果打开的文件是图片,音频或者视频等,打开方式采用rb,但是,此时,不能添加encoding="xxx"
"""

#二、读取文件内容
#1.读取全部内容   ***********
#str = f.read()
#print(str)

#2.读取指定的字符数
#注意:如果每一行的结尾有个"\n",也被识别成字符
"""
str1 = f.read(2)
print(str1)
str1 = f.read(2)
print(str1)
str1 = f.read(2)
print(str1)


#3.读取整行,不管该行有多少个字符    *********
str2 = f.readline()
print(str2)
str2 = f.readline()
print(str2)
"""

#4.读取一行中的指定的字符
#str3 = f.readline(3)
#print(str3)

#5.读取全部的内容,返回的结果为一个列表,每一行数据为一个元素
#注意:如果指明参数,则表示读取指定个数的字符
str4 = f.readlines()
print(str4)

#三、关闭文件
f.close()

其他写法:

#1.读取文件的简写形式
#with open()  as 变量

#好处:可以自动关闭文件,避免忘记关闭文件导致的资源浪费
path = "致橡树.txt"
with open(path,"r",encoding="gbk") as f:
    result = f.read()
    print(result)

#2.
try:
    f1 = open(path,"r",encoding="gbk")
    print(f1.read())
except FileNotFoundError as e:
    print("文件路径错误",e)
except LookupError as e:
    print("未知的编码格式",e)
except UnicodeDecodeError as e:
    print("读取文件解码错误",e)
finally:
    if f1:
        f1.close()

读取图片等二进制文件:

#1.
f = open("dog.jpg","rb")

result = f.read()
print(result)

f.close()

#2
with open("dog.jpg","rb") as f1:
    f1.read()

#注意:读取的是二进制文件,读取到的内容为\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00\xff\xdb\x0
2.2写文件

操作步骤:

​ a.打开文件:open()

​ b.写入数据:write()

​ c.刷新管道【内部缓冲区】:flush()

​ d.关闭文件:close()

代码演示:

path = "file1.txt"

#1.打开文件
#注意:写入文件的时候,文件可以不存在,当open的时候会自动创建文件
#读取文件的时候,文件必须先存在,才能open
f = open(path,"w",encoding="utf-8")

#2.写入数据
#注意:将数据写入文件的时候,默认是没有换行的,如果向换行,则可以手动添加\n
f.write("Python高薪就业,走上人生巅峰")

#3.刷新数据缓冲区
#作用:加速数据的流动,保证缓冲区的流畅
f.flush()

#4.关闭文件
f.close()

#简写形式
with open(path,"w",encoding="utf-8") as f1:
    f1.write("hello")
    f.flush()
3.编码和解码

编码:encode,字符串类型转换为字节类型

解码:decode,字节类型转换为字符串类型

注意:编码和解码的格式必须保持一致

代码演示:

path = "file2.txt"

#编码:字符串----》字节
with open(path,"wb") as f1:
    str = "today is a good day 今天是个好天气"
    f1.write(str.encode("utf-8"))

#解码:字节----->字符串
with open(path,"rb") as f2:
    data = f2.read()
    print(data)
    print(type(data))

    newData = data.decode("utf-8")
    print(newData)
    print(type(newData))
4.csv文件的读写

csv:逗号分隔值【Comma Separated Values】

一种文件格式,.csv,本质是一个纯文本文件,可以作为不同程序之间数据交互的格式

打开方式:记事本,excel

4.1读文件

代码演示:

#C:\Users\Administrator\Desktop\SZ-Python\Day15\笔记\text.csv
import  csv


#方式一:三部曲
def readCsv1(path):
    #1.打开文件
    csvFile = open(path,"r")

    #2.将文件对象封装成可迭代对象
    reader= csv.reader(csvFile)

    #3.读取文件内容
    #遍历出来的结果为列表
    for item in reader:
        print(item)

    #4.关闭文件
    csvFile.close()

readCsv1(r"C:\Users\Administrator\Desktop\SZ-Python\Day15\笔记\text.csv")

#方式二:简写
def readCsv2(path):
    with open(path,"r") as f:
        reader = csv.reader(f)
        for item in reader:
            print(item)

readCsv2(r"C:\Users\Administrator\Desktop\SZ-Python\Day15\笔记\text.csv")
4.2写文件

代码演示:

import  csv

#1.从列表写入数据
def writeCsv1(path):
    infoList = [['username', 'password', 'age', 'address'],['zhangsan', 'abc123', '17', 'china'],['lisi', 'aaabbb', '10', 'england']]

    #1.打开文件
    #注意:如果不设置newline,每一行会自动有一个空行
    csvFile = open(path,"w",newline="")

    #2.将文件对象封装成一个可迭代对象
    writer = csv.writer(csvFile)

    #3.写入数据
    for i in range(len(infoList)):
        writer.writerow(infoList[i])

    #4.关闭文件
    csvFile.close()

writeCsv1("file3.csv")

#2.从字典写入文件
def writeCsv2(path):
    dic = {"张三":123,"李四":456,"王麻子":789}
    csvFile = open(path, "w", newline="")
    writer = csv.writer(csvFile)

    for key in dic:
        writer.writerow([key,dic[key]])

    csvFile.close()

#3.简写形式
def writeCsv3(path):
    infoList = [['username', 'password', 'age', 'address'], ['zhangsan', 'abc123', '17', 'china'],
                ['lisi', 'aaabbb', '10', 'england']]
    with open(path, "w", newline="") as f:
        writer = csv.writer(f)

        for rowData in infoList:
            writer.writerow(rowData)

正则表达式 Regular Expression

七、正则表达式

1.引入案例

代码演示:

import  re     #regular  Expession   
#需求:判断一个qq号是否是合法的
"""
分析:
1.全数字
2.第一位数字不能为0
3.位数:5~11
"""
def checkQQ(str):
    #不管str是否合法,假设合法
    result = True

    #寻找条件推翻假设
    try:
        #判断是否是全数字
        num = int(str)

        #判断位数
        if len(str) >= 5 and len(str) <= 11:

            #判断开头是否为0
            if str[0] == "0":
                result = False

        else:
            result = False
    except ValueError as e:
        result = False

    return  result


print(checkQQ("6725675785678657"))

#使用正则表达式实现上面的需求
result = re.match(r"[1-9]\d{4,10}","6725675786574657")
print(result)
2.概述

正则表达式【Regular Expression】,简写为regex,RE,使用单个字符串来描述一系列具有特殊格式的字符串

功能:

​ a.搜索

​ b.替换

​ c.匹配

使用情景:

​ 爬虫

​ 验证手机号,验证邮箱,密码【用户名】

3.使用规则
3.1匹配单个数字或者字符

代码演示:

import  re
"""
----------匹配单个字符与数字---------
.                匹配除换行符以外的任意字符
[0123456789]     []是字符集合,表示匹配方括号中所包含的任意一个字符
[good]           匹配good中任意一个字符
[a-z]            匹配任意小写字母
[A-Z]            匹配任意大写字母
[0-9]            匹配任意数字,类似[0123456789]
[0-9a-zA-Z]      匹配任意的数字和字母
[0-9a-zA-Z_]     匹配任意的数字、字母和下划线
[^good]          匹配除了good这几个字母以外的所有字符,中括号里的^称为脱字符,表示不匹配集合中的字符
[^0-9]           匹配所有的非数字字符
\d               匹配数字,效果同[0-9]
\D               匹配非数字字符,效果同[^0-9]
\w               匹配数字,字母和下划线,效果同[0-9a-zA-Z_]
\W               匹配非数字,字母和下划线,效果同[^0-9a-zA-Z_]
\s               匹配任意的空白符(空格,回车,换行,制表,换页),效果同[ \r\n\t\f]
\S               匹配任意的非空白符,效果同[^ \f\n\r\t]
"""
#[]  :只匹配其中的一位
# - :表示一个区间
#1.
#1.1编译正则表达式返回对象
pattern = re.compile(r"[abcd]")   #正则表达式  [a-d]
#1.2使用正则表达式匹配字符串,成功返回对象,并携带匹配之后额结果,如果匹配失败则返回None
ret = pattern.match("dhello")    #需要被匹配的字符串
print(ret)
print(ret.group())

#2.
pattern = re.compile(r"[s-z]")
ret = pattern.match("xhello")
print(ret)
print(ret.group())

#3
pattern = re.compile(r"[0-9]")
ret = pattern.match("5hello")
print(ret)
print(ret.group())

#4
pattern = re.compile(r"[0-9a-zA-Z]")
ret = pattern.match("8hello")
print(ret)
print(ret.group())

#5  ^:脱字符【】否定的含义
pattern = re.compile(r"[^0-9]")
ret = pattern.match("chello")
print(ret)
print(ret.group())

#6 \d:只匹配数字,等同于[0-9]
pattern = re.compile(r"\d")
ret = pattern.match("4")
print(ret)
print(ret.group())

#7.   \w
pattern = re.compile(r"\w")
ret = pattern.match("7")
print(ret)
print(ret.group())

#8   \s
pattern = re.compile(r"\s")
ret = pattern.match("\t")
print(ret)
print(ret.group())

#9.   .:匹配不到换行符【\n】
pattern = re.compile(r".")
ret = pattern.match("\r")
print(ret)
print(ret.group())
3.2匹配边界字符

代码演示:

import  re
"""
--------------锚字符(边界字符)-------------

^     行首匹配,和在[]里的^不是一个意思   startswith
$     行尾匹配                          endswith

\A    匹配字符串开始,它和^的区别是,\A只匹配整个字符串的开头,即使在re.M模式下也不会匹配它行的行首
\Z    匹配字符串结束,它和$的区别是,\Z只匹配整个字符串的结束,即使在re.M模式下也不会匹配它行的行尾

\b    匹配一个单词的边界,也就是值单词和空格间的位置   bounds
\B    匹配非单词边界

"""
#search()
print(re.search(r"^to","today is a good day"))
print(re.search(r"day$","today is a good day"))

#findall()
#re.M
print(re.findall(r"\Ato","today is a good day\ntoday is a good day",re.M))
print(re.findall(r"^to","today is a good day\ntoday is a good day",re.M))
#总结:\A只匹配第一行的行首,^匹配每一行的行首

#\b匹配边界【开始和结尾】,\B匹配的是非边界【中间】
print(re.search(r"er\b","never"))   #er
print(re.search(r"er\b","nerve"))   #None
print(re.search(r"er\B","never"))    #None
print(re.search(r"er\B","nerve"))   #er
3.3匹配多个字符

代码演示:

import  re
"""
-------------------匹配多个字符------------------------

说明:下方的x、y、z均为假设的普通字符,n、m(非负整数),不是正则表达式的元字符
(xyz)    匹配小括号内的xyz(作为一个整体去匹配)
x?       匹配0个或者1个x
x*       匹配0个或者任意多个x(.* 表示匹配0个或者任意多个字符(换行符除外))
x+       匹配至少一个x
x{n}     匹配确定的n个x(n是一个非负整数)
x{n,}    匹配至少n个x
x{n,m}   匹配至少n个最多m个x。注意:n <= m
x|y      |表示或,匹配的是x或y
"""

#()当做一个整体去匹配,返回的结果为一个列表
print(re.findall(r"(ab)","aaacccaabbbbb"))

print(re.findall(r"a?","aaacccaabbbbb"))
print(re.findall(r"a*","aaacccaabbbbb"))
print(re.findall(r"a+","aaacccaabbbbb"))
"""
['a', 'a', 'a', '', '', '', 'a', 'a', '', '', '', '', '', '']
['aaa', '', '', '', 'aa', '', '', '', '', '', '']
['aaa', 'aa']
"""
#恰好出现n次
print(re.findall(r"a{2}","aaacccaabbbbb"))
#至少出现n次
print(re.findall(r"a{2,}","aaacccaabbbbb"))
#{m,n}:至少出现m次,至多出现n次
print(re.findall(r"a{2,5}","aaacccaabbbbb"))
#表示或
print(re.findall(r"a|b","aaacccaabbbbb"))   #[ab]
3.4匹配分组

代码演示:

import  re

#匹配分组
#|   :或
#()   :整体
#search:会在字符串中从左向左进行查找,如果找到第一个符合条件的,则停止查找
#正则1|正则2:只要正则1或者正则2中的一个满足,则直接按照这个条件查找
pattern = re.compile("\d+|[a-z]+")
ret = pattern.search("123-d223344aa$$aa")   #abc123-d223344aa$$aa
print(ret.group())

pattern = re.compile("([a-z]\d)+\w+")
ret = pattern.search("abc123-d223344aa$$aa")   #abc123-d223344aa$$aa
print(ret.group())
3.5子模式

代码演示:

import  re

#子模式
#()
#如果在正则表达式中出现\1  \2等标识符的时候,那么\1代表从左向右的第一个()中的内容。。。被称为子模式【简化正则表达式】
pattern = re.compile(r"<([a-z]+)><(\w+)>\w+</\2></\1>")
ret = pattern.search("<div><span>hello</span></div>")
print(ret.group())
#子模式访问
print(ret.group(1))
print(ret.group(2))
3.6贪婪和非贪婪

代码演示:

import re

#贪婪和非贪婪【匹配一位还是匹配多位的区别】
#+   *  :多次匹配【贪婪匹配】
#在+或者*后面添加?则改为非贪婪匹配
result1 = re.findall(r"a(\d+)","a23333333b")
print(result1)   #['23333333']

result1 = re.findall(r"a(\d+?)","a23333333b")
print(result1)   #['2']

#特殊情况;如果一个正则表达式的前后都有限定条件的时候,那么则不存在贪婪模式,?将不起作用
result1 = re.findall(r"a(\d+)b","a23333333b")
print(result1)
result1 = re.findall(r"a(\d+?)b","a23333333b")
print(result1)
3.7模式修正

代码演示:

import re

#模式修正
"""
re.I:忽略大小写模式【ignorecase】
re.M:视为多行模式【more】
re.S:视为单行模式【single】
"""
pattern = re.compile(r"^love",re.M)
string = """
alove you
love her
love he
"""
result1 = pattern.search(string)
print(result1.group())

pattern = re.compile(r"[a-z]+",re.I)
result1 = pattern.search("LLLLLLlove")
print(result1.group())
4.re模块中常用功能函数

代码演示:

import re

#re模块中常用的函数

#1.compile()
str1 = "today is a good day"
str2 = r"\w*"
pattern1 = re.compile(str2)
print(pattern1.findall(str1))

#2.match()
result = re.match(r"[1-9]\d{4,10}","6725675786574657")
print(result)

#3.search()
#注意:只要匹配到一个符合条件的子字符串,则直接返回,后面的内容不参与搜索
print(re.search(r"\dcom","www.4comnghughs").group())

#4.findall()
#注意;返回的结果为列表
#finditer(): 返回的结果为可迭代器
iter = re.finditer(r"\d+","12 fjhaehgj 66 fhaj  ")
print(iter)

for i in iter:
    print(i)

    #获取值
    print(i.group())
    #获取下标
    print(i.span())


#5.split() :返回一个列表
s1 = "1one2two3445545three454four56977878five"
print(re.split(r"\d+",s1))
print(re.split(r"[0-9]{1,}",s1))
print(re.split(r"[^a-z]+",s1))

s2 = "zhangsan lilei      lisi    Han   Jack"
print(re.split(r" +",s2))

s3 = "zhangsan@@@@lilei##########lisi%%%Han&&&&&&&&&&&&&Jack"
print(re.split(r"[^a-zA-Z]+",s3))

#6.sub() 替换,返回的是替换之后的字符串
string1 = "today is a good day today is a good da"
#用-将空格替换
#参数:旧的字符换【正则表达式】  新的字符串   原字符串
print(re.sub(r"\s","-",string1))

#subn()  替换,返回一个元组(新的字符串,替换的次数)
print(re.subn(r"\s","-",string1))

八、网络编程

1.网络编程基础
1.1概念

计算机网络:把分布在不同区域的计算机【设备】与专门的一些外部设备通过通信线路相关联,形成一个网络系统,从而使得计算机之间可以共享数据

网络编程:同一个网络中不同的机器之间的通信

1.2计算机之间需要通信的必要条件

ip地址,端口,网络协议

1.>ip地址

​ 互联网协议地址【Internet Protocol Address】,是联网设备和互联网之间的唯一标识,在同一个网段中,ip地址是唯一的

​ ip地址是数字型,是一个32位的整数,

​ 举例:10.36.131.32:32位的整数,分成4个8位的二进制,将二进制转换为0~255之间的十进制整数

​ 分类:

​ a.形式分类

​ ipv4:分为4段

​ ipv6:分为6段

​ b.功能分类

​ A类:保留给政府机构,1.0.0.1~126.255.255.255

​ B类:分配中型企业,128.。。。。~191.。。

​ C类:分配个人,192…~223

​ D类:组播,224~239

​ E类:实验,240~255

​ 127.0.0.1:回送地址,一般指的是本机的ip,localhost,一般用来进行测试

​ 总结:ip地址可以唯一确定网络上的一个通信实体,但是一个通信实体上可能有很多的应用程序,可以同时提供网络服务,此时还需要借助于端口进行区分

2>端口

​ 数据的发送和接收都需要通过端口,端口号用于唯一标识通信实体上进行网络通信的程序

​ 注意:同一台机器上的不同的应用程序不能占用同一个端口,端口的范围:0~65535

​ 作用:ip地址结合端口号,就可以唯一的确定一个网络中唯一一台计算机上的一个应用程序

​ 分类:

​ a.公认端口:0~1023

​ b.注册端口:1025~49151

​ c.动态端口或者私有端口:1024~65535

​ 常用端口:

​ mysql: 3306

​ oracle:1521

​ tomcat:8080

​ qq:4000

3>网络协议

​ http: 被动式协议

​ tcp

​ udp

​ tcp/ip:互联网协议

2.TCP编程

Transmission Control Protocol,传输控制协议,是一个传输层的通信协议

客户端/服务端:套接字【socket】,程序通常通过套接字向网络发出请求或者应答网络请求,使得两台设备之间进行通信

理解;打开了一个网络连接,必须知道ip地址,端口号,协议

​ 特点:

​ a.安全的【确保接收方完全正确的接收数据】

​ b.面向连接【数据传输之前必须要建立连接】

​ c.传输的效率较低【面向连接需要耗时】

​ d.一旦连接建立,双方可以按照指定的格式发送数据【大小没有限制】

使用经典三次握手建立连接

​ a.客户端向服务端发起请求

​ b.服务端收到请求之后,会给客户端一个响应

​ c.客户端收到服务端的响应之后,给服务端回复一个确认信息

总结:使用tcp实现数据的发送和接收需要有发送方和接收方,但是两个通信实体之间没有明确的客户端或者服务端之分,在两个通信实体在建立连接之前,必须有一个通信实体先做出主动姿态,主动发起请求

2.1 socket通信流程

代码演示:

server:

#服务端流程描述
import  socket

#1.创建服务端的socket对象
serverSocket = socket.socket()

#2.为socket绑定端口和ip地址
"""
bind(元组),将端口号和ip地址创建元组,然后传参
(host,port)
"""
#查看ip地址:在终端输入ipconfig命令
ip_port = ("10.36.131.32",6666)
serverSocket.bind(ip_port)

#3.服务端监听请求,随时准备接受客户端发来的连接
"""
listen(backlog)
backlog:在拒绝连接之前,可以挂起的最大连接数量
注意:不能无限大
"""
serverSocket.listen(5)

print("server waiting~~~~~")

#4.服务端接收到客户端的请求,被动打开进行连接
#accept();在连接的时候,会处于阻塞状态
#返回值:conn,address,conn表示连接到的套接字对象,address表示连接到的客户端的地址
conn,addr = serverSocket.accept()

#5.服务端接收消息
"""
recv(size)
可以一次性接收到多大的数据
"""
client_data = conn.recv(1024)
print(str(client_data,"utf-8"))

#6.服务端关闭
serverSocket.close()

client:

import  socket

#1.创建socket对象
clientSocket = socket.socket()

#2.发起连接请求
#connect(元组)
#(host,port)ip地址和端口号需要和服务端中绑定的ip地址以及端口号保持一致
ip_port = ("10.36.131.32",6666)
clientSocket.connect(ip_port)

#3.发送数据
#sendall(string) ,字节类型的字符串【编码的过程】
clientSocket.sendall(bytes("hello你好啊",encoding="utf-8"))

#4.关闭客户端
clientSocket.close()
2.2客户端和服务端的数据交互

代码演示:

server:

import  socket

server = socket.socket()

server.bind(("10.36.131.32",6666))

server.listen(5)

conn,address = server.accept()

print("连接成功")

while True:
    clientData = conn.recv(1024)
    result = clientData.decode("utf-8")
    print("客户端对服务端说:",result)

    if result == "bye" or result == "再见":
        break

    sendData = input("请输入要给回复客户端的数据:")
    conn.send(sendData.encode("utf-8"))

server.close()

client:

import  socket

client = socket.socket()

client.connect(("10.36.131.32",6666))

while True:
    sendData = input("请输入要发送给服务端的数据:")
    client.send(sendData.encode("utf-8"))

    serverData = client.recv(1024)
    result = serverData.decode("utf-8")
    print("服务端回复:",result)

    if result == "bye":
        break

client.close()
3.UDP编程

User Datagram Protocol,用户数据包协议

特点:

​ a.不安全的

​ b.无连接

​ c.效率高,速度快

​ d.对数据的大小是有限制的,每个被传输的数据包的大小不超过64k

九、发邮件和发短信【扩展】

1.发短信

互亿无线

# 发送短信
# APIID:C80604386
# APIKEY:16874f2ed0a2d0bb225747370f9aedc4

代码演示:

import requests

# 用户名 查看用户名请登录用户中心->验证码、通知短信->帐户及签名设置->APIID
account  = "C80604386"
# 密码 查看密码请登录用户中心->验证码、通知短信->帐户及签名设置->APIKEY
password = "16874f2ed0a2d0bb225747370f9aedc4"
mobile = "18566218480"
text = "您的验证码是:121254。请不要把验证码泄露给其他人。"

url = 'http://106.ihuyi.com/webservice/sms.php?method=Submit'
data = {'account': account, 'password' : password, 'content': text, 'mobile':mobile,'format':'json'}

res = requests.post(url, data=data)
print(res, res.text)

2.发邮件

代码演示:

#发送纯文本
#发邮件的模块
import  smtplib
#邮件标题
from  email.header import Header
#邮件文本
from  email.mime.text import MIMEText

"""
user:用户名
pwd:授权码
sender:发送方
receiver:接收方
content:邮件的正文
title:邮件的标题
"""
def sendMail(user,pwd,sender,receiver,content,title):
    mail_host = "smtp.163.com"   #163的SMTP服务器

    #第一部分:准备工作
    #1.将邮件的信息打包成一个对象
    message = MIMEText(content,"plain","utf-8")   #内容,格式,编码
    #2.设置邮件的发送者
    message["From"] = sender
    #3.设置邮件的接收方
    #message["To"] = receiver
    #join():通过字符串调用,参数为一个列表
    message["To"] = ",".join(receiver)
    #4.设置邮件的标题
    message["Subject"] = title

    #第二部分:发送邮件
    #1.启用服务器发送邮件
    #参数:服务器,端口号
    smtpObj = smtplib.SMTP_SSL(mail_host,465)
    #2.登录邮箱进行验证
    #参数:用户名,授权码
    smtpObj.login(user,pwd)
    #3.发送邮件
    #参数:发送方,接收方,邮件信息
    smtpObj.sendmail(sender,receiver,message.as_string())

    print("mail send successful!")

if __name__ == "__main__":
    mail_user = "18501970795@163.com"
    mail_pwd = "yang0122"

    mail_sender = "18501970795@163.com"
    mail_receiver = ["1490980468@qq.com"]

    email_content = "人生苦短,我用Python"
    email_title = "Python"

    sendMail(mail_user,mail_pwd,mail_sender,mail_receiver,email_content,email_title)

十、多线程

在介绍Python中的线程之前,先明确一个问题,Python中的多线程是假的多线程!
为什么这么说,我们先明确一个概念,全局解释器锁(GIL)

什么是GIL
Python代码的执行由Python虚拟机(解释器)来控制,同时只有一个线程在执行。对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同时只有一个线程在运行。
为什么要GIL
为了线程间数据的一致性和状态同步的完整性
GIL的影响
只有一个线程在运行,无法使用多核(多个CPU)。

在多线程环境中,Python虚拟机按照以下方式执行。
	1.设置GIL。
	2.切换到一个线程去执行。
	3.运行。
	4.把线程设置为睡眠状态。
	5.解锁GIL。
	6.再次重复以上步骤。

比方我有一个4核的CPU,那么这样一来,在单位时间内每个核只能跑一个线程,然后时间片轮转切换。
但是Python不一样,它不管你有几个核,单位时间多个核只能跑一个线程,然后时间片轮转。
执行一段时间后让出,多线程在Python中只能交替执性,10核也只能用到1个核

# 使用线程
from threading import Thread
def loop():
    while True:
        print("亲爱的,我错了,我能吃饭了吗?")

if __name__ == '__main__':
    for i in range(3):
        t = Thread(target=loop)
        t.start()
    while True:
        pass

# 而如果我们变成进程呢?cpu --100%
from multiprocessing import Process
def loop():
    while True:
        print("亲爱的,我错了,我能吃饭了吗?")

if __name__ == '__main__':
    for i in range(3):
        t = Process(target=loop)
        t.start()
    while True:
        pass
多线程怎么使用多核
1、重写python编译器(官方cpython)如使用:PyPy解释器
2、调用C语言的链接库
3. 不用线程,改用进程
cpu密集型(计算密集型)、I/O密集型
计算密集型任务由于主要消耗CPU资源,代码运行效率至关重要,C语言编写

IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成,99%的时间花费在IO上,脚本语言是首选,C语言最差。

计算密集型: 建议使用多进程(多进程可以用到多核)
IO密集型: Input output(一般是比较耗时的操作), 建议使用多线程

创建多线程

def doSth(arg):
    # 拿到当前线程的名称和线程号id
    threadName = threading.current_thread().getName()
    tid = threading.current_thread().ident
    for i in range(5):
        print("%s *%d @%s,tid=%d" % (arg, i, threadName, tid))
        time.sleep(2)
1、使用_thread.start_new_thread开辟子线程
def simpleThread():
    # 创建子线程,执行doSth
    # 用这种方式创建的线程为【守护线程】(主线程死去“护卫”也随“主公”而去)
    _thread.start_new_thread(doSth, ("拍森",))

    mainThreadName = threading.current_thread().getName()
    print(threading.current_thread())
    
    # 5秒的时间以内,能看到主线程和子线程在并发打印
    for i in range(5):
        print("劳资是主线程@%s" % (mainThreadName))
        time.sleep(1)

    # 阻塞主线程,以使【守护线程】能够执行完毕
    while True:
        pass
2、 通过创建threading.Thread对象实现子线程
def threadingThread():
    # 默认不是【守护线程】
    t = threading.Thread(target=doSth, args=("大王派我来巡山",)) # args=(,) 必须是元组
    # t.setDaemon(True)  # 设置为守护线程
    t.start()  # 启动线程,调用run()方法
    
3、通过继承threading.Thread类,进而创建对象实现子线程
class MyThread(threading.Thread):
    def __init__(self, name, task, subtask):
        super().__init__()

        self.name = name  # 覆盖了父类的name
        self.task = task  # MyThread自己的属性
        self.subtask = subtask  # MyThread自己的属性

    # 覆写父类的run方法,
    # run方法以内为【要跑在子线程内的业务逻辑】(thread.start()会触发的业务逻辑)
    def run(self):
        for i in range(5):
            print("【%s】并【%s】 *%d @%s" % (self.task, self.subtask, i, threading.current_thread().getName()))
            time.sleep(2)

def classThread():
    mt = MyThread("小分队I", "巡山", "扫黄")
    mt.start()  #  启动线程

4、几个重要的API
def importantAPI():
    print(threading.currentThread())  # 返回当前的线程变量
    # 创建五条子线程
    t1 = threading.Thread(target=doSth, args=("巡山",))
    t2 = threading.Thread(target=doSth, args=("巡水",))
    t3 = threading.Thread(target=doSth, args=("巡鸟",))

    t1.start()  # 开启线程
    t2.start()
    t3.start()

    print(t1.isAlive())  # 返回线程是否活动的
    print(t2.isDaemon())  # 是否是守护线程
    print(t3.getName())  # 返回线程名
    t3.setName("巡鸟")  # 设置线程名
    print(t3.getName())
    print(t3.ident)  # 返回线程号

    # 返回一个包含正在运行的线程的list
    tlist = threading.enumerate()
    print("当前活动线程:", tlist)

    # 返回正在运行的线程数量(在数值上等于len(tlist))
    count = threading.active_count()
    print("当前活动线程有%d条" % (count))

线程冲突
'''
【线程冲突】示例:
	多个线程并发访问同一个变量而互相干扰
'''
import threading
import time
money = 0

# CPU分配的时间片不足以完成一百万次加法运算,
# 因此结果还没有被保存到内存中就被其它线程所打断
def addMoney():
    global money
    for i in range(1000000):
        money += 1
    print(money)


# 创建线程锁
lock = threading.Lock()

def addMoneyWithLock():
    # print("addMoneyWithLock")
    time.sleep(1)
    global money
    # print(lock.acquire())
    # if lock.acquire():
    #     for i in range(1000000):
    #         money += 1
    # lock.release()
    # 独占线程锁
    with lock:  # 阻塞直到拿到线程锁

        # -----下面的代码只有拿到lock对象才能执行-----
        for i in range(1000000):
            money += 1
        # 释放线程锁,以使其它线程能够拿到并执行逻辑
        # ----------------锁已被释放-----------------

    print(money)

# 5条线程同时访问money变量,导致结果不正确
def conflictDemo():
    for i in range(5):
        t = threading.Thread(target=addMoney)
        t.start()

# 通过线程同步(依次执行)解决线程冲突
def handleConflictBySync():
    for i in range(5):
        t = threading.Thread(target=addMoney)
        t.start()
        t.join()  # 一直阻塞到t运行完毕

# 通过依次独占线程锁解决线程冲突
def handleConflictByLock():
    # 并发5条线程
    for i in range(5):
        t = threading.Thread(target=addMoneyWithLock)
        t.start()

if __name__ == '__main__':
    # conflictDemo()
    # handleConflictBySync()
    handleConflictByLock()

    pass
死锁
死锁:是指一个资源被多次调用,而多次调用方都未能释放该资源就会造成一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。

互相锁住对方线程需要的资源,造成死锁局面
线程安全
互斥锁
互斥锁
    状态:锁定/非锁定
    # 创建锁
        lock = threading.Lock()
    # 锁定
        lock.acquire()
    # 释放
        lock.release()
递归锁
递归锁,重用锁,用于解决死锁的问题,可重复锁

# 递归锁
rlock = threading.RLOCK()

信号量Semaphore调度线程:控制最大并发量
'''
使用Semaphore调度线程:控制最大并发量
'''
import threading
import time

# 允许最大并发量3
sem = threading.Semaphore(3)

def doSth(arg):
    with sem:
        tname = threading.current_thread().getName()
        print("%s正在执行【%s】" % (tname, arg))
        time.sleep(1)
        print("-----%s执行完毕!-----\n" % (tname))
        time.sleep(0.1)

if __name__ == '__main__':

    # 开启10条线程
    for i in range(10):
        threading.Thread(target=doSth, args=("巡山",), name="小分队%d" % (i)).start()
    

协程

协程,又称微线程,纤程。英文名Coroutine。
首先我们得知道协程是啥?协程其实可以认为是比线程更小的执行单元。为啥说他是一个执行单元,因为他自带CPU上下文。这样只要在合适的时机,我们可以把一个协程切换到另一个协程,只要这个过程中保存或恢复CPU上下文那么程序还是可以运行的。

通俗的理解:在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定。
协程和线程差异
协程的特点在于是一个线程执行, 那和多线程比,协程有何优势?
	1.最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
	2.第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

因为协程是一个线程执行,那怎么利用多核CPU呢?
	最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
	
协程的缺点: 它不能同时将CPU的多个核用上,只能使用一个核

Python对协程的支持是通过generator实现的。
在generator中,我们不但可以通过for循环来迭代,还可以不断调用next()函数获取由yield语句返回的下一个值。

# 协程一个简单实现
def C():
    while True:
        print("=====C=====")
        yield
        time.sleep(0.5)

def D(c):
    while True:
        print("=====D=====")
        next(c)
        time.sleep(0.5)

if __name__ == "__main__":
    c = C()
    D(c)

使用协程

1.使用greenlet + switch实现协程调度
'''
	使用greenlet + switch实现协程调度
'''
from greenlet import greenlet
import time


def func1():
    print("开门走进卫生间")
    time.sleep(3)
    gr2.switch()  # 把CPU执行权交给gr2

    print("飞流直下三千尺")
    time.sleep(3)
    gr2.switch()
    pass


def func2():
    print("一看拖把放旁边")
    time.sleep(3)
    gr1.switch()

    print("疑是银河落九天")
    pass


if __name__ == '__main__':
    gr1 = greenlet(func1)
    gr2 = greenlet(func2)
    gr1.switch()  # 把CPU执行权先给gr1
    pass

2.使用gevent +sleep自动将CPU执行权分配给当前未睡眠的协程
'''
	使用gevent + sleep自动将CPU执行权分配给当前未睡眠的协程
'''
import gevent

def func1():
    gevent.sleep(1)
    print("大梦谁先觉")

    gevent.sleep(13)
    print("1:over")
    pass

def func2():
    gevent.sleep(3)
    print("平生我自知")

    gevent.sleep(9)
    print("2:over")
    pass

def func3():
    gevent.sleep(5)
    print("草堂春睡足")

    gevent.sleep(5)
    print("3:over")
    pass

def func4():
    gevent.sleep(7)
    print("窗外日迟迟")

    gevent.sleep(1)
    print("4:over")
    pass

def simpleGevent():
    gr1 = gevent.spawn(func1)
    gr2 = gevent.spawn(func2)
    gr3 = gevent.spawn(func3)
    gr4 = gevent.spawn(func4)
    gevent.joinall([
        gr1, gr2, gr3, gr4
    ])

if __name__ == '__main__':
    simpleGevent()
    
3.通过monkey调度
'''
	使用gevent + monkey.patch_all()自动调度网络IO协程
'''
import gevent
from gevent import monkey
monkey.patch_all()  # 将【标准库-阻塞IO实现】替换为【gevent-非阻塞IO实现】

import requests
import time

def getPageText(url, order=0):
    print("No%d:%s请求开始..." % (order, url))
    resp = requests.get(url)  # 发起网络请求,返回需要时间——阻塞IO

    html = resp.text
    print("No%d:%s成功返回:长度为%d" % (order, url, len(html)))
    pass

if __name__ == '__main__':
    start = time.time()
    time.clock()
    gevent.joinall([
        gevent.spawn(getPageText, "http://www.sina.com", order=1),
        gevent.spawn(getPageText, "http://www.qq.com", order=2),
        gevent.spawn(getPageText, "http://www.baidu.com", order=3),
        gevent.spawn(getPageText, "http://www.163.com", order=4),
        gevent.spawn(getPageText, "http://www.4399.com", order=5),
        gevent.spawn(getPageText, "http://www.sohu.com", order=6),
        gevent.spawn(getPageText, "http://www.youku.com", order=7),
        gevent.spawn(getPageText, "http://www.iqiyi.com", order=8),
    ])

    end = time.time()
    print("over,耗时%d秒" % (end - start))
    print(time.clock())
    

十一. 进程

进程的概念
python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。

进程的概念:
	 进程是程序的一次执行过程, 正在进行的一个过程或者说一个任务,而负责执行任务的则是CPU.
	
进程的生命周期:
	当操作系统要完成某个任务时,它会创建一个进程。当进程完成任务之后,系统就会撤销这个进程,收回它所占用的资源。从创建到撤销的时间段就是进程的生命期

进程之间存在并发性:
	在一个系统中,同时会存在多个进程。他们轮流占用CPU和各种资源

并行与并发的区别:
	无论是并行还是并发,在用户看来都是同时运行的,不管是进程还是线程,都只是一个任务而已, 
真正干活的是CPU,CPU来做这些任务,而一个cpu(单核)同一时刻只能执行一个任务。 
并行:多个任务同时运行,只有具备多个cpu才能实现并行,含有几个cpu,也就意味着在同一时刻可以执行几个任务。 
并发:是伪并行,即看起来是同时运行的,实际上是单个CPU在多道程序之间来回的进行切换。

同步与异步的概念:
	同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去。 
	异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进行处理,这样可以提高执行的效率。 
	比如:打电话的过程就是同步通信,发短信时就是异步通信。

多线程和多进程的关系:
	对于计算密集型应用,应该使用多进程;
	对于IO密集型应用,应该使用多线程。线程的创建比进程的创建开销小的多。

创建进程
使用multiprocessing.Process
import multiprocessing
import time

def func(arg):
    pname = multiprocessing.current_process().name
    pid = multiprocessing.current_process().pid
    print("当前进程ID=%d,name=%s" % (pid, pname))

    for i in range(5):
        print(arg)
        time.sleep(1)

if __name__ == "__main__":
    p = multiprocessing.Process(target=func, args=("hello",))
    # p.daemon = True  # 设为【守护进程】(随主进程的结束而结束)
    p.start()

    while True:
        print("子进程是否活着?", p.is_alive())
        time.sleep(1)
    print("main over")

通过继承Process实现自定义进程
import multiprocessing
import os

# 通过继承Process实现自定义进程
class MyProcess(multiprocessing.Process):
    def __init__(self, name, url):
        super().__init__()
        self.name = name
        self.url = url  # 自定义属性

    # 重写run
    def run(self):
        pid = os.getpid()
        ppid = os.getppid()
        pname = multiprocessing.current_process().name
        print("当前进程name:", pname)
        print("当前进程id:", pid)
        print("当前进程的父进程id:", ppid)

if __name__ == '__main__':

    # 创建3个进程
    MyProcess("小分队1", "").start()
    MyProcess("小分队2", "").start()
    MyProcess("小分队3", "").start()
    print("主进程ID:", multiprocessing.current_process().pid)

    # CPU核数
    coreCount = multiprocessing.cpu_count()
    print("我的CPU是%d核的" % coreCount)

    # 获取当前活动的进程列表
    print(multiprocessing.active_children())
同步异步和进程锁
import multiprocessing
import random
import time

def fn():
    name = multiprocessing.current_process().name
    print("开始执行进程:", name)
    time.sleep(random.randint(1, 4))
    print("执行结束:", name)

# 多进程
# 异步执行进程
def processAsync():
    p1 = multiprocessing.Process(target=fn, name="小分队1")
    p2 = multiprocessing.Process(target=fn, name="小分队2")
    p1.start()
    p2.start()

# 同步执行
def processSync():
    p1 = multiprocessing.Process(target=fn, name="小分队1")
    p2 = multiprocessing.Process(target=fn, name="小分队2")
    p1.start()
    p1.join()
    p2.start()
    p2.join()

# 加锁
def processLock():
    # 进程锁
    lock = multiprocessing.Lock()
    p1 = multiprocessing.Process(target=fn2, name="小分队1", args=(lock,))
    p2 = multiprocessing.Process(target=fn2, name="小分队2", args=(lock,))
    p1.start()
    p2.start()

def fn2(lock):
    name = multiprocessing.current_process().name
    print("开始执行进程:", name)

    # 加锁
    # 方式一
    # if lock.acquire():
    #     print("正在工作...")
    #     time.sleep(random.randint(1, 4))
    #     lock.release()

    # 方式二
    with lock:
        print("%s:正在工作..." % name)
        time.sleep(random.randint(1, 4))

    print("%s:执行结束:"% name)


if __name__ == '__main__':
    # processAsync() # 异步执行
    # processSync()  # 同步执行
    processLock()  # 加进程锁

使用Semaphore控制进程的最大并发
import multiprocessing
import time

def fn(sem):
    with sem:
        name = multiprocessing.current_process().name
        print("子线程开始:", name)
        time.sleep(3)
        print("子线程结束:", name)

if __name__ == '__main__':
    sem = multiprocessing.Semaphore(3)
    for i in range(8):
        multiprocessing.Process(target=fn, name="小分队%d"%i, args=(sem, )).start()

十二. 高阶函数

sorted(key=lambda x:x[‘age’])

reversed()

1.map()

代码演示:

"""
map(function,iterable)
function:函数
iterable:可迭代对象
作用:将传入的函数依次作用于可迭代对象中的每一个元素,并把结果【Iterator】返回
"""
#需求1:给一个已知列表中的元素求平方
def square(x):
    return x ** 2
list1 = [1,2,3,4,5]
result1 = map(square,list1)
#注意:map是一个类,表示一种数据类型,集合或者序列,使用类似于list,tuple,set
print(result1)   #<map object at 0x000001EE25431DA0>
print(type(result1))   #<class 'map'>
print(list(result1))  #[1, 4, 9, 16, 25]

result2 = map(lambda x:x ** 2,list1)
print(list(result2))

#str = 10

#需求2:将整型元素的列表转换为字符串元素的列表
#举例:[1,2,3,4]------>["1","2","3","4"]
#str(1) ---- >字符串1
#注意:在使用系统函数之前,最好不要出现同名的变量
result3 = map(str,[1,2,3,4])
print(list(result3))


#需求3:已知两个整型列表,将两个列表中相同位置的元素相加,得到一个新的列表
def add(x,y):
    return  x  + y
l1 = [1,2,3,4,5]
l2 = [6,7,8,9,10]

result4 = map(add,l1,l2)
print(list(result4))
2.reduce()

代码演示:

from  functools  import  reduce

"""
reduce(function,Iterable)  :通过函数对参数列表中的元素进行累积
function:函数
Iterable:可迭代对象,一般使用列表
工作原理:用传给reduce的function先作用于list中第一个和第二个元素,用得到的结果和list中第三个元素计算。。。
reduce(add,[a,b,c,d])
add(add(add(a,b),c),d)---->递归
"""

#需求1;求一个已知列表中元素的和
list1 = [1,2,3,4,5]
def add(x,y):
    return x + y
result1 = reduce(add,list1)
print(result1)

result2 = reduce(lambda x,y:x + y,list1)
print(result2)

#需求2:将列表[1,3,5,7,9]变换成整数13579
"""
分析:
13 = 1 * 10 + 3
135 = 13 * 10 + 5
1357 = 135 * 10 + 7
13579 = 1357 * 10 + 9
"""
list2 = [1,3,5,7,9]
def fn(x,y):
    return x * 10 + y

result3 = reduce(fn,list2)
print(result3)

#需求3:
#结合map函数,实现一个将str转换为int的函数   int()

#思路:传进来一个字符串,返回一个对应的整数
def strToInt(s):
    digits = {"0":0,"1":1,"2":2,"3":3,"4":4}
    return digits[s]

#"23401"------>23401
r0 = map(strToInt,"23401")
print(list(r0))   #[2, 3, 4, 0, 1]

r1 = reduce(fn,map(strToInt,"23401"))
print(r1)   #23401
print(type(r1))   #<class 'int'>
3.filter()

代码演示:

"""
filter(function,序列)
作用:通过指定的条件过滤列表中的元素
工作原理:将传入的函数依次作用于列表中的每一个元素,根据返回的是True还是False决定元素是否需要保留
"""

#需求1:将列表中的偶数筛选出来
list1 = [1,2,3,4,5,6,7,8,9]
#作用:定义筛选的规则
def func(num):
    if num % 2 == 0:
        return  True
    return  False

result1  = filter(func,list1)
print(result1)
print(list(result1))  #[2, 4, 6, 8]
4.sorted()

代码演示:

#1.普通排序
#默认为升序排序,得到了的一个新的列表
list1 = [4,5,23,3,5,7]
result1 = sorted(list1)
print(list1)
print(result1)  #r[3, 4, 5, 5, 7, 23]

#2.按照绝对值进行排序
#默认为升序排序,排序的依据是所有元素的绝对值的大小
list2 = [4,5,-23,3,-5,7]
result2 = sorted(list2,key=abs)
print(result2)  #[3, 4, 5, -5, 7, -23]

#3.降序升序
list3 = [4,5,23,3,5,7]
result3 = sorted(list3,reverse=True)
print(result3)

#4.字符也可以实现排序
list4 = ["f","a","k","z"]
result4 = sorted(list4)
print(result4)

#5.自定义排序规则
#默认为升序排序
def myFunc(str):
    return len(str)
list5 = ["gsg","a","34535","efgg","562875678257fhjawhgj"]
result5 = sorted(list5,key=myFunc)
print(result5)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不能say的秘密

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值