【Python教程】进阶篇

目录

2.1函数

2.1.1函数基础

2.1.1.1函数的定义和调用

2.1.1.2函数的作用

2.1.1.3函数的参数

2.1.1.4函数的返回值

2.1.1.5函数的作用域-LEGB

2.1.2闭包

2.1.3装饰器

2.1.4迭代器和生成器

2.1.4.1可迭代对象

2.1.4.2迭代器

2.1.4.3生成器

2.1.4.4三者之间的异同

2.1.5四种函数

2.1.5.1递归函数

2.1.5.2匿名函数

2.1.5.3普通函数

2.1.5.4高阶函数

2.1.6列表推导式

2.1.6.1三目运算符

2.1.6.2for循环

2.1.6.3for循环和if的嵌套

2.1.6.4for循环和if..else的嵌套

2.1.6.5双层for循环

2.1.6.6问题汇总

2.2类

2.2.1面向对象基础

2.2.1.1面向对象思想

2.2.1.2类和对象

2.2.1.3属性和方法

2.2.1.4类属性和对象属性

2.2.1.5检索原则

2.2.1.6创建对象经历了什么

2.2.2面向对象方法

2.2.2.1普通方法(对象方法)

2.2.2.2类方法

2.2.2.3静态方法

2.2.2.4魔术方法

2.2.2.5几种方法的区别和联系

2.2.2.6注意点

2.2.4面向对象三大特性

2.2.4.1封装(私有化)

2.2.4.2继承

2.2.4.3多继承

2.2.4.4多态

2.2.5类装饰器

2.3异常处理

2.3.1异常基础

2.3.1.1异常基本格式

2.3.1.2异常的类型

2.3.1.3异常注意点

2.3.2自定义异常

2.3.2.1异常的传递

2.3.2.2自定义异常

2.4模块

2.4.1模块基础

2.4.2循环导入问题

2.5多任务

2.5.1进程

2.5.1.1进程基础

2.5.1.2进程之间的通信

2.5.1.3进程池

2.5.1.4自定义进程

2.5.2线程

2.5.2.1线程基础

2.5.2.2自定义线程

2.5.2.3线程共享全局变量

2.5.2.4GIL锁

2.5.2.5线程之间通信以及信号量

2.5.3协程

2.5.3.1生成器实现协程

2.5.3.2greenlet实现

3.5.3.3gevent实现真正的协程

2.5.4多任务基础

2.5.1.1并行和并发

2.5.1.2阻塞和非阻塞

2.5.1.3进程线程和协程

2.5.1.4程序和进程的区别

2.5.1.5同步和异步


2.1函数

2.1.1函数基础

2.1.1.1函数的定义和调用

def 函数名([形参,...]):
    函数体
函数调用:
    函数名([实参])

2.1.1.2函数的作用

  • 减少代码冗余,将一些重复的代码封装在函数中

2.1.1.3函数的参数

函数参数

  • 定义函数时形式参数的个数和调用时实参的个数一致
  • 可变类型参数传递的是地址,不可变类型参数传递的是值
  • 如果传入函数内部的是不可变类型的参数,那么函数内部对参数的操作不会影响函数外部的变量,如果传入函数内部的是可变类型的参数,即传入的参数是地址,那么函数内部对参数的操作会对函数外部的变量造成影响.
num=10
list1=[]
def fun(num,li):
    num+=1
    li.append(10)
    li.append(11)
    print(num)
    print(li)
fun(num,list1)
print(num)
print(list1)

参数类型

  • 普通参数:必填参数(参数列表开头)
  • 默认值参数:def func(a,b=10)
  • 关键字参数:调用的时候通过关键字的方式指明
  • 可变参数
    # 默认值参数
    def func1(a=None):
        print(a)
    func1()
    
    # a是关键字参数
    def func2(b,a=10):
        print(a,b)
    func2(b=9)
    
    # 可变参数
    def func3(*args,**kwargs):
        print(args,kwargs)
    func3(1,2,3,a=5,b=6)

     

参数的拆包和装包

一般情况下,拆包的顺序应该是:必传参数,关键字参数,可变参数,而且关键字参数一般都会在末尾,必传参数一般都会在开头

# 拆包
list1=[6,8,2]
n1,n2,n3=list1  # 都会先做拆包,然后做后面变量的赋值
print(n1,n2,n3)
# 装包
list2=[1,2,3,4,5]
x,*y,z=list2  # 一个变量名前面加一个*号表示多值参数,
print(x,y,z)

2.1.1.4函数的返回值

如果函数没有返回值,默认为None;如果函数有返回值,那么就要在函数体中添加return关键字

return关键字的作用

  • 返回return之后的值,结束方法
  • return后面可以返回多个值

结束相关的关键字

  • continux:跳过当前一次的循环
  • break:结束最近一层的循环
  • return 结束函数
  • quit:退出程序

2.1.1.5函数的作用域-LEGB

搜索变量的规则

  • 局部变量local
  • 嵌套变量弄local
  • 全局变量glocal
  • 内置模块builtins中找
  • 没有就报错
a=1
b=2
c=3
def func1():
    a=10
    b=20
    c=30
    def func2():
        c=300  # 局部变量
        print(c)  
        nonlocal a  # 嵌套变量
        print(a)  
        global b  # 全局变量
        print(b)
    return func2
f=func1()
print(f())

2.1.2闭包

闭包的条件

  • 在一个函数中定义另一个函数
  • 内层函数使用外层函数的变量
  • 返回值是内层函数
def func1():
    def func2(a):
        print(a)
        return a+1
    return func2
f=func1()
print(f(3))

获取当前函数被应用的数量

import sys
print(sys.getrefcount(func1))

闭包的优点:

  • 延迟了函数中变量的回收,在外部函数结束的时候,就会将函数的所有资源释放

2.1.3装饰器

简单的装饰器

这里我们就拿show和decorator举个例子.装饰器@decorator和show=decorator(show)有相同的功能,都表示将show函数通过装饰器decorator进行装饰

Python中的函数其实就是一个变量,函数名就是变量名,函数名中存放的是函数的地址.首先将需要装饰的函数传入装饰器decorator,然后通过被装饰的函数进行接收,返回出来的是内部函数的引用,在调用内部函数.

def decorator(func):
    print('装饰开始')
    def wrapper(s):
        result=func(s)  # 调用要被装饰的函数,并获取被装饰的函数返回的值
        print('进行装饰',result)  # 对值进行打印
        print('装饰结束')
    return wrapper
@decorator
def show(x):  # 需要被装饰的函数
    return x+1
show(2)  # 这里的参数是被装饰的函数需要的参数

一个装饰器装饰多个函数

def decorator(func):
    def wrapper(s):
        result=func(s)  # 调用要被装饰的函数,并获取被装饰的函数返回的值
        print(result)  # 对值进行打印
    return wrapper
@decorator
def show1(x):  # 需要被装饰的函数
    return x
@decorator
def show2(x):
    return x
show1('装饰函数show1')  # 这里的参数是被装饰的函数需要的参数
show2('装饰函数show2')

一个函数被多个装饰器装饰

距离原函数越近越先被装饰

def decorator1(func):
    print('-----1-----')
    def wrapper(*args):
        func(args)
        print('-----1-----')
    return wrapper
def decorator2(func):
    print('-----2-----')
    def wrapper(args):
        func(args)
        print('-----2-----')
    return wrapper
@decorator1
@decorator2
def test1(x):
    print(x,'需要被装饰')
test1('Marry')

-----2-----
-----1-----
('Marry',) 需要被装饰
-----2-----
-----1-----

带参数的装饰器

带参数的装饰器需要三层函数才能实现

def decorator(number):
    print('装饰调用',number)  # 来自装饰器的参数,传递参数,调用装饰器
    def decorator1(func):  # func = show
        print('开始装饰')
        def wrapper(*args, **kwargs):  # 用来接收调用show传过来的参数
            result=func(*args, **kwargs)  # 调用需要装饰的函数
            print(result)  # 对获取到的需要装饰的函数的内容进行打印
            return '装饰成功'
        return wrapper
    return decorator1  # 将真正的装饰器返回
@decorator(number=10)
def show(name):
    print('需要装饰的函数')
    return name
result=show('Marry')
print(result)

执行顺序

  • decorator(number=10)
  • print('装饰调用',number)
  • return decorator1  # 将真正的装饰器返回
  • @decorator
  • print('开始装饰')
  • return wrapper
  • show('Marry')
  • def wrapper(*args, **kwargs)
  • result=func(*args, **kwargs)==def show(name)
  • return '装饰成功'
  • print(result)

装饰器小结

  • 装饰器就是不改变原函数的内容,通过装饰为原函数增加功能,说白了,就是传递函数的引用,首先执行距离被装饰函数最近的装饰器,完了之后返回函数引用,然后将函数引用传递给上一层的装饰器,一次类推.就是通过引用,不断的将函数包裹起来,然后一层一层的拆开.

2.1.4迭代器和生成器

2.1.4.1可迭代对象

  • 具有__iter__()魔术方法的对象就是可迭代对象
  • iter()实质上是调用了可迭代对象中的__iter__()方法
  • 当我们已经迭代完成最后一个数据,如果继续使用next()函数就会抛出异常StopIteration异常.
class MyList(object):
    """自定义的一个可迭代对象"""
    def __init__(self):
        self.items = []


    def add(self, val):
        self.items.append(val)


    def __iter__(self):
        myiterator = MyIterator(self)
        return myiterator

2.1.4.2迭代器

  • 实现了__next__()和__iter__()方法,在我们使用next()函数的时候实质上使用的是__next__()方法,迭代器就是通过next()函数的调用返回下一个数据值的
class MyIterator(object):
    """自定义的供上面可迭代对象使用的一个迭代器"""
    def __init__(self, mylist):
        self.mylist = mylist
        # current用来记录当前访问到的位置
        self.current = 0

    def __next__(self):
        if self.current < len(self.mylist.items):
            item = self.mylist.items[self.current]
            self.current += 1
            return item
        else:
            raise StopIteration

    def __iter__(self):
        return self

2.1.4.3生成器

  • 之所以没有元组推导式是因为元组推导式推出来的就是生产器.
  • 为了减少内存空间的占用,我们有了生产器,用的时候产生,不用的时候就不产生,使用()和yield

一个简答的生成器

s=(x for x in range(2))
print(next(s))
print(next(s))
print(next(s,-1))

使用函数实现生成器

yield:暂停,退出返回yield后面的值

注意点:

  • 定义函数+关键字yield
  • 调用函数得到的是一个生成器对象:g=func(),g就是一个生成器
  • 结合next(g):只要遇到yield就会将其后面的值进行返回并且暂停下一次再调用next的时候就会从暂停的位置进行执行

函数

  • __next__()同系统next(g),g.__next__(),next(g),以上都是得到下一个元素的方法
  • send()每次调用的时候需要向生成器传值,注意第一次必须send(None),以后可以给send(value)

小结

  • 产生方式
  • 结合哪些函数可以得到元素:next(),__next__(),send(5)
  • 从那暂停,从哪开始
  • 应用:线程,进程,协程

实例

def func():
    sum1=0
    for i in range(5):
        x=yield i
        sum1+=(x+i)
        print(x)
        print('sum1',sum1)
        print('i',i)
g=func()  # 创建生成器对象
next(g)  # 启动生成器 g.__next__()
g.send(2)  # 为生成器传值
g.send(3)  # 生成器中没有要接收返回的值的时候可以使用next,如果有接收的值,我们要用send()函数,包含了next()的功能
g.close()  # 关闭生成器

多个生成器切换任务

def study():
    for i in range(5):
        print("studing......")
        yield


def listening():
    for i in range(5):
        print("listening......")
        yield

def weichar():
    for i in range(5):
        print("weixin......")
        yield

g1=study()
g2=listening()
g3=weichar()

while True:
    try:
        g1.__next__()
        g2.__next__()
        g3.__next__()
    except StopIteration:
        quit()

生成器就是当前的值为下一个值计算的条件

2.1.4.4三者之间的异同

  • 可迭代对象:可以通过for循环遍历(Iterable)
  • 迭代器:可以被next()函数调用并不断返回下一个值的对象(Iterator)
  • 生成器:本次的计算的结果值是下一个计算的条件(generator)
  • 三者都可以通过isinstance(object,generator)进行判断
from collections import Iterator,Iterable,Generator
# Iterbale 可迭代的
# Iterator 迭代器
# Generator 生成器
print(isinstance(r,Iterable))  # True
print(isinstance(r,Iterator))  # False
print(isinstance(r,Generator)) # False

2.1.5四种函数

2.1.5.1递归函数

在每次进行的结构和自己相同的情况下,可以使用递归函数,也就是自己调用自己,虽然比较简单,但是效率很低
原则:必须要有出口,不断向出口靠近

def fun1(n):
    if n>1:
        return n*fun1(n-1)
    else:
        return n
print(fun1(5))

2.1.5.2匿名函数

  • 特点:函数体非常简单,使用次数较少
  • 作用:简化函数定义,使用方便
  • 定义格式:lambda 参数 : 返回值
a=lambda x,y:x+y
print(a(1,2))

2.1.5.3普通函数

2.1.5.4高阶函数

就是把函数当做参数传递

sorted()函数

  • 排序
list3 = [('tom', 21), ('lucy', 18), ('jack', 22), ('lily', 19), ('jerry', 24)]
list4 = sorted(list3, key=lambda x: x[1], reverse=True)
print(list4)

import operator
list1 = [('tom', 21), ('lucy', 18), ('jack', 22), ('lily', 19), ('jerry', 24)]
list2=sorted(list1,key=operator.itemgetter(1),reverse=True)
print(list2)

map(func,iterable)函数

  • 就是将原列表通过某个规则转成新的列表,返回值是map对象,需要我们转成元组或者列表,返回值是一个对象
names=['guofubin','chaijinhu','cheweigang','guochengxi']
map1=map(lambda x:x.capitalize(),names)
print(map1)
print(list(map1))

filter(function,iterable)函数

  • 返回值是一个对象,需要对返回值进行相应的转换(list(filter_object)),function的返回值必须是bool类型,对满足函数的内容进行过滤
a=[1,2,3,4,5,6,7,8]
filter_obj=filter(lambda i:i%2==0,a)
print(list(filter_obj))

reduce(function,iterable,initial)函数

  • 返回值是一个数,对可迭代对象在initial的基础上进行function操作,函数将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果。
from functools import reduce
list2 = [1, 2, 3, 4]
result = reduce(lambda x, y: x + y, list2, 0)  # (运算规则,运算对象,运算初始值)
print(result)

偏函数

partial()

from functools import partial,wraps
int1 = partial(int, base=8)
print(int1('123'))
print(int1('16'))
print(int1('25'))

wraps():消除装饰器带来的一些副作用

def decorator1(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        func(*args, **kwargs)  # func =  house
        print('铺地板')
        print('刷漆')
    return wrapper
@decorator1
def house():
    print('------》毛坯房')
print(house.__name__)
print(house.__doc__)  # document
house()

因为加上装饰器之后文档的名字变了,文档注释也变了,为了消除装饰器的这种副作用.我们在装饰器内部函数的前面加上@wraps(func)即可

2.1.6列表推导式

2.1.6.1三目运算符

a=1 if 1>2 else 2
print(a)

2.1.6.2for循环

a=[i*2 for i in range(10)]
print(a)

2.1.6.3for循环和if的嵌套

a=[i*3 for i in range(10) if i%2==0]
print(a)

2.1.6.4for循环和if..else的嵌套

a=[i+1 if i%2==0 else i for i in range(10)]
print(a)

2.1.6.5双层for循环

a=["{}*{}={}".format(i,j,i*j)  for i in range(10) for j in range(i,10)]
print(a)

2.1.6.6问题汇总

问题一:

def fun(a = []):
    a.append('a')
    print(a)
    print(locals())
fun()
fun()
print(globals())

# 结果
['a']
['a','a']

问题二:

print(__name__)  # 表示当前模块,如果是当前模块,打印结果就是__main__

if __name__ == '__main__':
    pass

问题三:

li=[1,2,3,4,5]
print(id(li))
print(id(li[0]))

列表地址是指该列表的存储地址,而列表的第一个元素的地址指的是元素存储数据的地址,这两个是不一样的.

问题四:

  • from 文件名 import 类名
  • import 文件名

问题五:

Python中一切皆对象

def fun():
    pass
a=0
list1=[]
print('a:',isinstance(a,object))
print('fun:',isinstance(fun,object))
print('list1:',isinstance(list1,object))

问题六

x=3 or 5
print(x)  结果是3


x=False and 5  # x=0 and 5
print(x)  结果False

x=True+5
print(x)  # 结果是6

2.2类

2.2.1面向对象基础

2.2.1.1面向对象思想

是相对于面向过程而言,就是说面向对象是将功能通过对象来实现,将功能封装进对象之中,让对象实现具体的细节这种方法将数据作为第一位,方法或者算法作为其次,是对数据的一种优化,操作起来更加方便,过程简化.面向对象的三大特征:封装\继承和多态

  • 封装性
  • 继承性:
  • 多态性:

2.2.1.2类和对象

  • 对象是类的实例化,类是对象特征的提取和封装
  • 先定义一个类,通过类创建一个对象,对象=类名(),类就像一个模型,对象是通过类这个模型创建出来的

2.2.1.3属性和方法

  • 属性是对象的特征
  • 方法是对象的动作

2.2.1.4类属性和对象属性

类属性:在类空间中,类属性既可以被类访问又可以被对象访问

class 类名:
    属性=值

对象属性:在对象空间中,对象属性只能被当前对象访问

class 类名:
    def __init__(self):
        super(self).__init__()
        属性=值

方式一:动态创建

  • 对象=类名()
  • 对象.属性名=值
  • 缺点:系统的魔术方法(无需我们调用,某种条件下自动触发)

方式二:依赖__init__()创建

  • __init__()在创建对象的时候自动触发
  • 在类的__init__()中使用:self.name=value

2.2.1.5检索原则

  • 从对象自身检索
  • 去对象模板(类)中检索
  • 类属性在创建对象的时候不会在对象中实例化,之所以可以通过(对象名.属性名)获取到,那是因为在对象中没有找到的时候会在类模板中进行查找

2.2.1.6创建对象经历了什么

  • Student类模型构建一个对象空间(通过__new__来得到这个空间的)
  • 初始化这个对象(通过__init__()这个魔术方法创建的)
  • 执行魔术方法(self就是第二部创建的内存空间)
class User(object):
    def __init__(self):
        super().__init__()
        print('init')
    def __new__(cls, *args, **kwargs):
        print('new')
        return super().__new__(cls)
    def __del__(self):
        print('del')
user=User()

# 结果
new
init
del

2.2.2面向对象方法

2.2.2.1普通方法(对象方法)

创建方式

class Dog:
    def eating(self):
        print('吃放')
dog=Dog()
dog.eating()

调用方式:对象名.方法名(参数)

访问方式:self.方法名(参数)

注意点:在调用的过程中会默认传递self,表示该对象自己

2.2.2.2类方法

创建方式

class Dog:
    @classmethod
    def eating(cls):
        print('吃放')
dog=Dog()
dog.eating()
Dog.eating()

调用方式

  • 类名.属性名
  • 对象名.属性名

类中的访问方式

  • cls.属性名(方法名(参数))

2.2.2.3静态方法

创建方式

import time
class Tool:
    @staticmethod
    def get_time():
        print(time.time())
tool=Tool()
tool.get_time()
Tool.get_time()

调用方式

  • 类名.方法名
  • 对象名.方法名

注意点

  • 调用不依赖于对象self,也不依赖于类cls

2.2.2.4魔术方法

魔术方法简介

  • 是类的特殊方法和普通方法不同的是魔术方法由系统自动调用

实例化一个对象经历了什么

  • 通过__new__()申请空间
  • 调用__init__()初始化对象
  • 最后调用__del__()销毁对象

常见的魔术方法

  • __new__():创建对象的时候创建,可以通过object.__new__()开辟空间,返回值是创建的对象空间
  • __init__():对象空间创建之后对对象进行初始化
  • __del__():对象销毁的时候执行,也就是没有引用指向的时候执行
  • __call__():对象本来是不能当函数调用的,如果有了此魔术方法就可以当函数调用
  • __str__()和__repr__():共同点都是将对象转成字符串输出,__str__()是通过print函数触发的,__repr__()是通过reps()函数触发的.
  • __len__():返回值是长度,调用len()函数的时候执行.
class User(object):
    def __init__(self,name):
        print('init')
    def __new__(cls, *args, **kwargs):
        print('new')
        return super().__new__(cls)
    def __del__(self):
        print('del')
    def __call__(self, *args, **kwargs):
        print('call')
    def __repr__(self):
        return repr(self)
    def __len__(self):
        return None
    def __str__(self):
        return 'str'
user=User('Marry')
user()
print(user)

运算相关的魔术方法

对象是不能比较大小的,但是同重写这些运算相关的方法就可以

  • __gt__()大于
  • __ge__():大于等于
  • __lt__():小于
  • __le__():小于等于
  • __eq__():等于
  • __ne__():不等于
  • __int__():将对象转成整型
  • __add__():对象进行加法操作
class Cat:
    def __init__(self, nickname, age):
        self.nickcname = nickname
        self.age = age
    def __gt__(self, other):
        return self.age > other.age
    def __lt__(self, other):
        return self.age < other.age
    def __eq__(self, other):
        print('--->')
        return self.age == other.age
    def __add__(self, other):
        return self.age + other.age
    def __str__(self):
        return str(self.age)
    def __int__(self):
        return self.age
c1 = Cat('花花', 2)
c2 = Cat('小猫1', 1)
c3 = Cat('小猫2', 4)
print(c1 > c2)
print(c1 < c2)
print(c1 == c2)
result = c1 + c2
print(result)
s=c1+c2+int(c3)
print(s)

属性相关的魔术方法

  • __getattr__():
  1. 触发时机:获取不存在对象的成员时触发
  2. 参数:一个是接收当前对象的self,一个是获取成员名称的字符串
  3. 返回值:必须有值
  4. 作用:为访问不存在的属性设置值
  5. 注意点:getattribute无论何时都会在getattr之前触发,触发了getattribute就不会在触发getattr了
  • __setattr():
  1. 触发时机:设置对象成员值的时候触发
  2. 参数:一个当前对象self,一个是要设置的成员名称字符串,一个是要设置的值
  3. 返回值:无过程操作
  4. 作用:接管设置操作,可以在设置前进行判断验证等行为
  5. 注意点:在当前方法中无法使用成员=值的方式直接设置成员,否则会无限递归,必须借助object的设置方法来完成
  6. object.__setattr__(参数1,参数2,参数3)
class Person:
    def __init__(self):
        self.name = '12'
    def __getattr__(self, item):
        if item == 'age':
            return 20
        elif item == 'gender':
            return '男'
        else:
            return '不存在此属性{}'.format(item)
    def __setattr__(self, key, value):
        print(key, value)
        # 如果这样使用就会反复调用这个方法,进入死循环,因为这个方法就是通过为属性赋值触发的,递归操作
        # self.key = value
        if key == 'phone' and value.startswith('139'):
            object.__setattr__(self, key, value)
p = Person()  # 设置对象成员值的时候触发了__setattr__函数
print(p.name)
print(p.phone)  # 因为属性phone并不存在于对象,这样会触发__getattr__函数
print(p.__dict__)  # 获取p对象的自身属性,并以字典的形式返回

2.2.2.5几种方法的区别和联系

  • 对象方法描述的是当前对象所独有的方法
  • 类方法(@classmethod):描述的是这一类具有的特性,举个例子:所有的狗具有"旺旺"的叫声,这就是狗这一类具有的特性,但是不同的狗对狗来说叫声也是不一样的,因此具体的狗的叫声,就是具体某个狗对象的特征.
  • 静态方法(@staticmethod):类中的函数,不需要实例,主要用来存放逻辑性的代码,逻辑上属于该类,但是本身与类没有任何关系(静态方法中不会涉及到类中的属性和方法操作).可以理解为:静态方法是独立单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护.,例如:获取当前时间的函数,连接数据库的函数等
  • 对象方法和类方法:对象方法只能是该对象独有的方法
  • 类方法与静态方法的区别:类方法描述的是这一类事务独有的动作.静态方法表示与当前类没有直接的联系,只不过为了代码的规范化在逻辑上属于该类罢了

2.2.2.6注意点

  • 类方法能否被对象调用? 能访问
  • 类方法方法中能否访问对象属性?不能
  • 对象能否调用静态方法?能
  • 类方法能否调用静态方法?不能
  • 静态方法能否调用类方法?能
  • 类方法和静态方法能否调用普通方法?都不能

2.2.4面向对象三大特性

2.2.4.1封装(私有化)

理解封装

  • 表面上:属性和方法封装在类中,
  • 实质上:将属性和方法私有化,并定义set和get方法,外界通过set和get访问属性,对属性是一种权限的约束

封装的作用

  • 通过set方法限制赋值,通过get限制取值

私有化

  • 设置私有方法可以让我们通过get_属性名(self)和set_属性名(self,参数)来访问内部属性,这样更加安全,也可以通过get或者set方法进行访问的限制
class Person:
    # 限制属性作用,只有在该列表中的属性才能别其他方法和属性访问,如果不在该列表中即使是对象方法也是不能访问的,
    # 这样做可以防止在函数外动态创建属性
    __slots__ = ['__name', '__age', '__flag']

    def __init__(self, name, age):
        self.__name = name
        self.__age = age
        self.__flag = True

    def get_name(self):
        if self.__flag:
            return self.__name
        else:
            return '没有权限查看用户名'

    def get_age(self):
        return self.__age

    def set_name(self, name):
        if len(name) >= 6:
            self.__name = name
        else:
            print('名字必须要大于等于6位')

    def set_age(self, age):
        if 125 > age > 0:
            self.__age = age
        else:
            print('年龄赋值失败,必须在0~125范围内')

p = Person('lucy', 20)
print(p.get_name())
print(p.get_age())
# p.name = 'abc'
# print(p.name)
p.set_name('steven')
print(p.get_name())

p.set_name('tom')
print(p.get_name())

p.set_age(10)
print(p.get_age())

在类的最前面加上这个__slots__ = ['__name', '__age', '__flag'],是为了防止外部随意动态的创建属性.

如果想既要用的时候像原来一样通过对象名.属性名访问对象内部的属性,也想起到限制作用,这是就要用到装饰器@property,为了让用户体验到通过对象名.属性名这种方式中的属性名是真正的属性名,我们会将get和set方法的名字也改成属性的名字,其实在底层,体验者通过上述方式点出来的是方法的名字

在原来的get方法上添加一个property装饰器,函数的名字最好增加的简要,让使用者在调用或者访问的时候更加的简洁好用(也就是让使用者认为是原来的属性名),结合私有属性一起操作.装饰set方法:

class Person:
    __slots__ = ['__name']
    def __init__(self):
        self.__name='gfb'

    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self,name):
        self.__name=name
p=Person()

使用:

对象=类名(参数)
    对象.属性名=值 ----set方法
    print(对象.属性名)---get方法

2.2.4.2继承

特点:

  • 让程序更加的简洁,提高代码的复用性,提高了程序的可读性.
  • 继承了父类的非私有属性和方法(父类,基类,超类),
  • 如果子类定义了一个相同的属性,先找子类属性,再找父类,如果父类与子类有相同的方法,则认为子类重写了此方法(重写--覆盖--override)

在子类中初始化父类中的方法的时候使用,父类名.__init__(self,参数),这个放在子类__init__()中的位置不同,结果也不同,这个不像java中的一样,只能放置在构造方法的开始位置

class Animal:
    type1 = '动物'
    def __init__(self):
        self.name = '花花'
        print('----->Animal')
    def run(self):
        print('正在奔跑....')
    def eat(self):
        print('喜欢吃...')
    def __str__(self):
        return '当前类型:{}'.format(Animal.type1)
class Dog(Animal):
    type1 = '狗'
    def __init__(self, name, color):
        # 调用父类的__init__方法
        Animal.__init__(self)
        self.name = name
        self.color = color
    def see_home(self):
        print('看家高手....')
    def eat(self):
        print('喜欢啃骨头...')
d = Dog('大黄', '黄色')
d.run()
d.eat()
d.see_home()
print(d.name)
print(d)

测试题

class Father:
    def __init__(self):
        self.__a = 10
        self.b = 8

    def show(self):
        r = self.__a + self.b
        print('运算结果是:', r)

class Son(Father):
    def __init__(self):
        Father.__init__(self)
        self.__a = 100

s = Son()
s.show()

结果是18,首先执行Father.__init__(self)这个,调用父类中的初始化方法__init__(),因为父类中的__a是私有化属性,因此,并没有将__a传递给子类,然后执行self.__a=100,但是在调用show()方法的时候,因为Son中没有show方法,因此他会去调用父类中的show()方法,但是因为子类中的__a是私有属性,并不能传递给父类,而在调用show()方法的时候是在父类中调用的,因此使用的__a是父类中的属性,因此得出来的结果也就是8+10=18,如果说我们将父类中的__a删除掉,它就会报错,这也这好验证了一点,__私有属性和私有方法只能在本类中使用

重写

  • 父类的方法无法满足子类的需求的时候,则定义一个和父类名相同的方法,每次对象调用,优先调用子类,在重写父类的方法的时候,子类和父类方法的参数列表和方法名要一致,
  • 重写之后在子类中如何访问父类方法?  父类名.父类方法名(参数),super(子类名,self).父类方法名(参数)

2.2.4.3多继承

查找顺序

  • 经典类:广度优先(从左到右的原则)
  • 新式类:C3算法形式-深度优先-MRO原则

可以通过类名.__mor__或者(类名.mro()函数)查看搜索顺序.

多继承的实现

class 类名(父类1,父类2,父类3):
    pass

菱形继承

  • 深度遍历顺序

2.2.4.4多态

多态就是一个父类的名可以接受一个子类对象,比如我们创建了一个动物类,也创建了一个狗类,狗类继承了动物类,我们创建了一个动物类对象,因为狗类是动物类的子类,我们在接收狗对象的时候可以将这个狗对象存放在创建的动物类变量中.下面举个例子:

宠物商店:

  •   issubclass(Cat,(Pet,))   ---> issubclass(类名,(父类1,父类2,。。))
  •   isinstance(pet,Pet)
class PetShop:
    def __init__(self, name):
        self.name = name
        self.pets = set()

    # 动作
    def save_pet(self, pet):
        # print(isinstance(pet, Pet))
        if isinstance(pet, Pet):
            self.pets.add(pet)
            print('添加成功!')
        else:
            print('不是宠物不收留!')

    def sale_pet(self, pet):
        if isinstance(pet, Pet):
            self.pets.discard(pet)
            print('宠物减少')
        else:
            print('不是宠物不收留!')

    # 查找宠物
    def search_pet(self, pname):
        for pet in self.pets:
            if pname == pet.pname:
                print('宠物在商店里')
                break
        else:
            print('不存在此宠物!')

    # 显示所有的宠物
    def all_pets(self):
        print('宠物商店所有的宠物信息如下:')
        for pet in self.pets:
            print(pet)

class Pet:
    type = '宠物'

注意点

  • Python中的多态是假的,因为是弱数据类型
  • _setattr__():赋值的时候触发,使用的时候要通过父类方法进行赋值object.__setattr__(参数1,参数2)

2.2.5类装饰器

参考文章

2.3异常处理

2.3.1异常基础

2.3.1.1异常基本格式

try:
    可能出现异常的代码
except Exception as ex:
    如果出现异常,执行的代码
else:
    没有遇到异常时执行的代码
finally:
    无论有没有异常都会执行,return也阻止不了它的执行

2.3.1.2异常的类型

  • 语法异常
  • 运行时异常:ValueError,TypeError,ZeroDivisionError

2.3.1.3异常注意点

  • Exception是异常类型的父类,Exception通常是要放在最后用来接收未知类型的.
  • 如果代码中有返回值和finally,则肯定会执行finally里面的代码部分,return也无法阻挡,在开发的时候,数据库连接,网络连接,使用finally最后释放资源.

2.3.2自定义异常

2.3.2.1异常的传递

import random
def func(list1):
    sum = 0
    for a in list1:
        print(a)
        sum += a  # raise
def get_random():
    try:
        list1 = []
        for i in range(5):
            ran = random.randint(1, 10)
            list1.append(ran)
        list1.append('9')
        func(list1)
    except Exception as err:
        print('+++++++++++++++++>', err)
if __name__ == '__main__':
    try:
        get_random()
    except Exception as err:
        print('-------->',err)

2.3.2.2自定义异常

raise+自定义异常:当系统的异常无法满足我们的需求的时候,就需要自定义异常

# 自定义异常类
class UserNameError(Exception):
    def __init__(self, *args, **kwargs):
        pass
class PasswordLengthError(Exception):
    def __init__(self, *args, **kwargs):
        pass
def register():
    username = input("请输入用户名:")
    password=input("请输入密码")
    if len(username) < 6 or username[0].isdigit():
        raise UserNameError('用户名格式错误')
    elif len(password)<6 or len(password)>10:
        raise PasswordLengthError('密码长度异常.')
    else:
        print("注册成功")
# 调用函数
if __name__ == '__main__':
    try:
        register()
    except Exception as err:
        print(err)

2.4模块

2.4.1模块基础

定义模块就是为了让其他模块使用,因此有了模块的导入:

  • from 文件名 import 类名
  • import 文件名

导入模块发生了什么

  • 先加载包,执行包中的__init__.py文件
  • 然后找到包中的模块(执行模块文件)
  • 可以使用包中模块的内容了

import的用法:

  • 一个模块就是一个文件
  • import 模块名.变量
  • import 模块名.函数
  • import 模块名.类名

from..import的用法:

  • from 模块名 import 变量名,函数名,类名
  • 使用:直接使用变量名,函数名,类名就可以了

获取自己的模块名

from package02.demo02 import fun02
print("导入的模块",__name__)
if __name__=="__main__":
    from package01.demo01 import fun01
    print("本模块",__name__)

限制导入

from 模块名 import *和import *都可以将当前所有的模块导入,为了限制,我们可以使用__all__对导入进行限制,这个只对星号起作用

__all__ = ['number', 'func']

__init__.py文件

可以认为是一个魔术模块.初始化包的时候会自动加载,因此我们可以将想要提前加载的东西放入到__init__.py文件中,这会使得在包被加载的时候就会和这个文件一起加载进来了,但是即使这个也是需要导入的.

test.py
from package01 import test
test()

__init__.py
def test():
    print("我是通过初始化方法加载进来的")
    print("我是着执行初始化的时候执行的...")

2.4.2循环导入问题

什么是循环导入问题?

package01中的demo01.py

from package02.demo02 import fun02
def fun01():
    print("我是package01中的demo01")

fun02()

package02中的demo02.py

from package01.demo01 import fun01
def fun02():
    print("我是package02中的demo02")
fun01()

看起来似乎没什么错,但是在导入包的时候,首先加载包dedemo01文件执行导包动作的时候,去package02中的demo02中找fun02方法,当它进入demo02中的时候首先加载的也是导包动作:from package01.demo01 import fun01,因此又去原来的位置重新加载,一次类推,这样下去就形成了死循环,也就形成了循环导入问题.

循环导入解决方案

  • 重构代码结构:在代码量很大的时候,这种方法是不可取的.
  • 将导入语句放到函数调用处(也就是放到函数中)
  • 导入语句向后面移动(可以设置执行权限,也就是使用if __name__=="__main__":如果是本模块中调用,那就去执行,如果是其他导入模块中执行的,那就不去执行)

2.5多任务

2.5.1进程

2.5.1.1进程基础

进程的状态

进程的相关概念

  • 每个进程都有自己的资源,包括主进程,他们各自的资源空间不同,因此执行起来也互不影响
  • 主进程在进行的时候默认执行;子进程可以开启p1=Process(target=task,args=())
  • 真正的进程分配资源使用start(),启动资源使用run()方法,start()内部启动了调用run()方法进程共享全局变量
  • 如果是全局变量,每个进程都会拥有一份全局变量,各自操作各自的全局变量,互不影响.

相关函数

  • os.getpid()获取当前进程号
  • current_process().name:获取当前进程的名字
  • os.getppid()获取父进程号
  • start()为进程分配资源,通过run()方法启动进程
  • run():只是一个普通的方法,通过调用该方法来启动进程执行
  • join([timeout=seconds])  阻塞主进程后面的代码不执行
  • is_alive():判断进程的任务是否完成
  • kill()杀死进程
  • terminate():终止进程

进程执行原理

  • 首先我们创建一个进程p1=Process(target=test),
  • 当我们调用run方法的时候首先操作系统会为这个进程独立分配空间,包括全局变量,都会独立存储起来,只供该进程使用,这是进程处于阻塞状态,
  • 然后通过start()方法调用run()方法来执行该进程(run()方法只是一个普通的方法,就是运行执行的,如果我们不通过start()直接调用run方法的话就不会存在资源的分配了,也就不存在多进程了.)
  • 这时我们可以使用is_alive()方法判断该进程是否活着(当然这时候肯定活着),run()方法中存储了我们要执行的内容,
  • 当run方法运行结束后,进程处于非活跃状态(我们可以通过is_alive来检测),start()方法通过调用close()方法来释放资源.

多进程案例

# 通过多任务更改全局变量
import multiprocessing
import time
def test1(count):
    for i in range(count):
        print("------1-------")
        time.sleep(0.5)
def test2(count):
    for i in range(count):
        print("--------2-------")
        time.sleep(0.5)
count=100
def main():
    t1=multiprocessing.Process(target=test1,args=(count,))
    t2=multiprocessing.Process(target=test2,args=(count,))
    t1.start()
    t2.start()
if __name__=='__main__':
    main()

相对线程来说,进程占用的资源要比线程多很多,子进程在执行的时候将主进程的代码资源统统复制了一份去执行,因此资源独立。而线程的资源却是资源共享的。

2.5.1.2进程之间的通信

队列:先进先出

  • put(timeout=3):向对列中存放数据,如果满了就阻塞,如果设置了超时时间,超过超时时间就会抛出异常
  • get(timeout=2):向队列中取数据,如果空了就阻塞,超过超时时间就会抛出异常.
  • full()判断队列中是否满了
  • emply()判断队列是否为空
  • qsize()获取队列长度

线程之间的通信可以通过共享全局变量实现,但进程之间的通信我们就需要另外一种方式队列queue。其实通过文件也是可以完成进程之间的通信,但是相对来说是很慢的.使用队列的资源共享的缺点是只能在同一个程序中实现资源的贡献.

 

import multiprocessing
import time
from  multiprocessing import Queue
def producer(q):
    for i in range(20):
        q.put(i)
        time.sleep(2)
        print('存储对象',i)
# 方法一
def consumer(q):
    while True:
        try:
            val = q.get()
            print('获取对象', val)
        except Exception as ex:
            print('没有数据了')
            break
# 方法二
def consumer(q):
    while True:
        val = q.get()
        print('获取对象', val)
        if q.qsize==0:
            print('没有数据了')
            break

if __name__ == '__main__':
    q = Queue(20)
    p1=multiprocessing.Process(target=producer,args=(q,))
    p2=multiprocessing.Process(target=consumer,args=(q,))
    p1.start()
    time.sleep(1)
    p2.start()

2.5.1.3进程池

按照队列的方式一个进程一个进程进,我们常用的是阻塞式进程池,在进程池中一个任务完成之后才会去做下一个任务.任务数是固定的话用普通进程,如果任务数是不固定的话就用进程池。multiprocessing.Pool常用函数解析:

阻塞式:

import os
import time
from multiprocessing import Pool
# 要执行的任务
def task1():
    print('洗衣服:', os.getpid(), os.getppid())
    time.sleep(0.5)
    return '我是进程:' + str(os.getpid())
# 对来自task的数据进程处理
def callback(msg):
    print('{}洗衣服任务完成!'.format(msg))
if __name__ == '__main__':
    pool = Pool(4)  # 创建一个进程池
    # 总共有10个任务,我们将其加入到进程池中,将task1执行后的结果传递给回调函数callback,
    # 让回调函数来处理数据,我们可以通过args和kwds来处理数据
    for i in range(10):
        pool.apply_async(task1,args=(),kwds={}, callback=callback)
    pool.close()  # 这是表示添加任务结束,停止向进程池中添加任务
    pool.join()  # 阻塞主进程,等待进程池执行任务
    print('main over')

非阻塞式:

import os
import time
from multiprocessing import Pool
def task1():
    for i in range(5):
        print('洗衣服:',i, os.getpid(), os.getppid())
        time.sleep(0.5)
if __name__ == '__main__':
    pool = Pool(4)
    for i in range(10):
        pool.apply(task1) # 阻塞式: 进程池中一个任务完成之后才能做下一个任务
    # 添加任务结束
    pool.close()
    # 阻塞主进程
    pool.join()
    print('main over')

阻塞式和非阻塞式

  • 我们举个例子,我们创建了一个进程池,内部可以创建四个进程,这是我们有十个任务,如果是阻塞式的话,必须一个任务一个任务的进进程池,等第一个任务执行完之后,第一个任务从进程池中出来后,下一个任务才能进入进程池去执行,这样和串行很是类似,只不过,在第一次执行之后,第二个任务进入的时候一看,还有空间,还可以创建进程对象,因此他会创建一个新的进程对象来执行任务,直到执行第五个任务的时候,第五个任务进入进程池之后发现,没有内存空间创建进程了,这时候他会随机选择一个空闲的以前创建好的进程来使用,也就是说此时的进程号号前四次中的进程号中的一个是一样的.这个在开发的时候不是很常用,一般用的是非阻塞式,非阻塞式表示我们创建好一个进程池后其他进程都可以进,直到进程池满了之后就开始阻塞,等进程池中只要有一个进程从进程池中出来,外面就可以进去一个执行任务.阻塞式是没有回调函数的.

2.5.1.4自定义进程

自定义进程步骤

  • 创建子类并集成Process
  • 重写run()方法
  • 使用进程子类
from multiprocessing import Process
import time
import os
class MyProcess(Process):
    def __init__(self):
        Process.__init__(self)
    def run(self) -> None:
        start=time.time()
        for i in range(10):
            print(os.getpid(),os.getppid())
            time.sleep(0.1)
        end=time.time()
        print('{}话费时间为:{}'.format(os.getpid(),end-start))
if __name__ == '__main__':
    mp=MyProcess()
    mp.name='进程1'
    mp.start()

2.5.2线程

2.5.2.1线程基础

线程中的方法

  •  run():运行线程
  •   start():启动线程
  •   join():阻塞线程
  •   name: 线程的名字,默认的Thread-1, Thread-2,....
  •   current_thread().name 获取当前线程的名字
  •   is_alive:判断线程是否活着

线程实例

在python中threading模块是thread模块的升级版本,对thread做了封装

当调用start()时,才会真正的创建线程,并且开始执行,主线程会等待所有的子线程结束后才结束

from threading import Thread,current_thread,enumerate
import time
def eating(name):
    for i in range(5):
        print("{}正在吃饭,第{}口饭".format(name,i),current_thread().name)
        time.sleep(0.2)
def singing(name):
    for i in range(5):
        print("{}正在唱歌,第{}首歌".format(name,i),current_thread().name)
        time.sleep(0.2)
if __name__ == '__main__':
    t1=Thread(target=eating,name='线程一',args=('gfb',))
    t2=Thread(target=singing,name='线程二',args=('gfb',))
    print('t1是否活着?',t1.is_alive())
    print(len(enumerate()))  # 查看当前线程数量
    t1.start()
    t2.start()
    print('t1是否活着?',t1.is_alive())  # 判断t1线程是否活着
    print(len(enumerate()))
    t1.join()  # 阻塞t1线程
    t2.join()
    print('t1是否活着?',t1.is_alive())
    print(len(enumerate()))
    print("main---",current_thread().name)

线程的执行顺序

  • 多线程程序的执行顺序是不确定的,当执行到sleep语句时,线程将被阻塞(Blocked),到sleep结束后,线程进入就绪(Runnable)状态,等待调度。而线程调度将自行选择一个线程执行。只能保证每个线程都运行完整个run函数,但是线程的启动顺序、run函数中每次循环的执行顺序都不能确定。

注意点:

  • 每个线程默认有一个名字,尽管上面的例子中没有指定线程对象的name,但是python会自动为线程指定一个名字。
  • 当线程的run()方法结束时该线程完成
  • 无法控制线程调度程序,但可以通过别的方式来影响线程调度的方式。

2.5.2.2自定义线程

步骤:

  • 定义一个类继承Thread
  • 重写: [__init__]     必须重写: run()
  • 创建线程类对象
  • 启动线程
from threading import Thread,current_thread
import time
def task(name):
    for i in range(10):
        print("{}正在执行任务...".format(name), current_thread().name)
        time.sleep(0.2)
from threading import Thread
class MyThread(Thread):
    def __init__(self,target,args):
        Thread.__init__(self)
        self.target=target
        self.args=args
    def run(self) -> None:
        self.target(self.args)
if __name__ == '__main__':
    mt1=MyThread(target=task,args=('gfb1'))
    mt2=MyThread(target=task,args=('gfb2'))
    mt1.start()
    mt2.start()
    mt1.join()
    mt2.join()
    print('main---',current_thread().name)

2.5.2.3线程共享全局变量

在一个进程内的所有线程共享全局变量,很方便在多个线程间共享数据;缺点就是,线程是对全局变量随意遂改可能造成多线程之间对全局变量的混乱(即线程非安全)

import threading
import time

g_num = 0

def work1(num):
    global g_num
    for i in range(num):
        g_num += 1
    print("----in work1, g_num is %d---"%g_num)

def work2(num):
    global g_num
    for i in range(num):
        g_num += 1
    print("----in work2, g_num is %d---"%g_num)

print("---线程创建之前g_num is %d---"%g_num)

t1 = threading.Thread(target=work1, args=(1000000,))
t1.start()

t2 = threading.Thread(target=work2, args=(1000000,))
t2.start()

while len(threading.enumerate()) != 1:
    time.sleep(1)

print("2个线程对同一个全局变量操作之后的最终结果是:%s" % g_num)

如果多个线程同时对同一个全局变量操作,会出现资源竞争问题,从而数据结果会不正确,解决线程不安全的方式:GIL锁

  • 系统调用t1,然后获取到g_num的值为0,此时上一把锁,即不允许其他线程操作g_num
  • t1对g_num的值进行+1
  • t1解锁,此时g_num的值为1,其他的线程就可以使用g_num了,而且是g_num的值不是0而是1
  • 同理其他线程在对g_num进行修改时,都要先上锁,处理完后再解锁,在上锁的整个过程中不允许其他线程访问,就保证了数据的正确性
from threading import Thread, Lock
number = 0
def task(lock):
    global number
    lock.acquire()  # 握住
    for i in range(100000):
        number += 1
    lock.release()  # 释放锁
if __name__ == '__main__':
    lock = Lock()
    t1 = Thread(target=task, args=(lock,))
    t2 = Thread(target=task, args=(lock,))
    t3 = Thread(target=task, args=(lock,))
    t1.start()
    t2.start()
    t3.start()
    t1.join()
    t2.join()
    t3.join()
    print('number:', number)

2.5.2.4GIL锁

GIL锁的介绍

GIL是什么呢?仍然用篮球比赛的例子来帮助理解:把篮球场看作是CPU,一场篮球比赛看作是一个线程,
如果只有一个篮球场,多场比赛要排队进行,就是一个简单的单核多线程的程序;如果有多块篮球场,
多场比赛同时进行,就是一个简单的多核多线程的程序。然而python有着特别的规定:
每场比赛必须要在裁判的监督之下才允许进行,而裁判只有一个。这样不管你有几块篮球场,
同一时间只允许有一个场地进行比赛,其它场地都将被闲置,其它比赛都只能等待。

注意:lock.acquire()和lock.release()默认是阻塞的

如果这个锁之前是没有上锁的,那么acquire不会堵塞,如果在调用acquire对这个锁上锁之前 它已经被 其他线程上了锁,那么此时acquire会堵塞,直到这个锁被解锁为止

当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。
每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。
线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。

GIL的优缺点

  • 优点:确保了某段关键代码只能由一个线程从头到尾完整地执行
  • 缺点:阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁

死锁:

  • 开发过程中使用线程,在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。尽管死锁很少发生,但一旦发生就会造成应用的停止响应,程序不做任何事情。
import time
from threading import Lock, Thread, current_thread
def task1(lock1, lock2):
    if lock1.acquire():
        print('{}获取到lock1锁。。。。'.format(current_thread().name))
        for i in range(5):
            print('{}------------------>{}'.format(current_thread().name, i))
            time.sleep(0.01)
        if lock2.acquire():
            print('{}获取了lock1,lock2'.format(current_thread().name))
            lock2.release()
        lock1.release()
def task2(lock1, lock2):
    if lock2.acquire():
        print('{}获取到lock2锁。。。。'.format(current_thread().name))
        for i in range(5):
            print('{}----->{}'.format(current_thread().name, i))
            time.sleep(0.01)
        if lock1.acquire():
            print('{}获取了lock1,lock2'.format(current_thread().name))
            lock1.release()
        lock2.release()
if __name__ == '__main__':
    lock1 = Lock()
    lock2 = Lock()
    t1 = Thread(target=task1, args=(lock1, lock2))
    t2 = Thread(target=task2, args=(lock1, lock2))
    t1.start()
    t2.start()

如何避免死锁

  • 代码重构
  • 设置超时时间,通过抛出异常来结束死锁这种方式很可能使得程序的部分代码执行不完整.
  • 银行家算法

2.5.2.5线程之间通信以及信号量

import random
import time
from threading import Thread, current_thread
from queue import Queue
def producer(queue):
    print('{}开门啦!'.format(current_thread().name))
    foods = ['红烧狮子头', '香肠烤饭', '蒜蓉生蚝', '酸辣土豆丝', '肉饼']
    for i in range(1, 21):
        food = random.choice(foods)
        print('{}正在加工中.....'.format(food))
        time.sleep(1)
        print('加工完成可以上菜了...')
        queue.put(food)
    queue.put(None)
def consumer(queue):
    print('{}来吃饭啦'.format(current_thread().name))
    while True:
        food = queue.get()
        if food:
            print('正在享用美食:', food)
            time.sleep(0.5)
        else:
            print('{}把饭店吃光啦,走人...'.format(current_thread().name))
            break

if __name__ == '__main__':
    queue = Queue(8)
    t1 = Thread(target=producer, name='馒头', args=(queue,))
    t2 = Thread(target=consumer, name='小黑', args=(queue,))
    t1.start()
    t2.start()

在内部有一个counter计数器,每当我们 s.acquire()一次,计数器就进行减1处理,每当 s.release()一次,计数器就进行加1处理,当计数器为0的时候其他线程的就处于等待的状态counter的值就是同一时间可以开启线程的个数(建议使用with),实现方式:

import time
from threading import Thread, Semaphore, current_thread
# from multiprocessing import Semaphore,Process
def task(s):
    # s.acquire()  # 减1
    with s:
        for i in range(5):
            print('{}扫地....{}'.format(current_thread().name, i))
            time.sleep(1)
    # s.release()  # 加1
if __name__ == '__main__':
    s = Semaphore(4)
    for i in range(10):
        t = Thread(target=task, args=(s,))
        t.start()

不光线程有信号量,进程也有信号量,如何理解信号量呢?

在进程中有个进程池,进程池中规定的存储空间只有四个的空间,刚进入进程池的时候存储空间是空的,每进入一个,进程池就创建一个相应的位置,直到四个满了,当四个中有一个任务执行完成了,出去之后外边等待的人进来之后使用的空间还是这四个中的其中一个.但是信号量的话表示我这个地方只规定四个人执行自己的任务,一旦里面的人执行完成,他会携带自己的任务走开,然后其他人进入去执行自己的任务.

进程池主要是值池中有四个位置空间来执行任务,这四个空间的地址不变,每个人进来之后,都在这四个位置其中一个上面执行任务;而信号量表示一个房间中只能供四个人来执行自己的任务,每执行完之后拿着自己完成的任务就走了,后续需要进入房间的人补上.每个人的任务相当于是自己的空间地址,因此各不相同.

2.5.3协程

协程是一种轻量级别的线程,底层通过生成器来实现,也因此在使用的时候通过yield来记录上一次的值,不会像函数调用一样从头开始执行.

2.5.3.1生成器实现协程

def eating():
    for i in range(5):
        print("小黑喜欢吃肉...",i)
        yield

def listen_music():
    for i  in  range(5):
        print("小黑在叫...",i)
        yield

if __name__ == '__main__':
    g1=eating()
    g2=listen_music()
    while True:
        try:
            next(g1)
            next(g2)
        except Exception:
            break

2.5.3.2greenlet实现

from greenlet import greenlet
def eating():
    for i in range(5):
        print('吃饭...')
        g2.switch()

def listen_music():
    for i in range(5):
        print('听音乐..', i)
        g1.switch()

if __name__ == '__main__':
    g1 = greenlet(eating)
    g2 = greenlet(listen_music)
    g1.switch()

解析:greenlet这个库对yield进行了封装,通过方法switch在协程之间进行自动切换。
启动的时候我们还得用switch方法进行启动。协程最大的特点就是将一起计算的延时时间充分利用做其他的事情。

通过这种方式实现的话需要手动进行协程之间的切换,而且最开始的时候还需手动启动,因为我们无法知道什么这个协程闲着,那个协程比较忙,因此,我们可以通过下面的方式实现协程之间自动的切换

3.5.3.3gevent实现真正的协程

import time
import gevent
from gevent import monkey
monkey.patch_all()
def eat():
    for i in range(5):
        print('吃饭...',i)
        time.sleep(0.1)
def listen_music():
    for i in range(5):
        print('听音乐..', i)
        time.sleep(0.1)
if __name__ == '__main__':
    g1 = gevent.spawn(eat)
    g2 = gevent.spawn(listen_music)
    g1.join()
    g2.join()
    print('----over---')

切记一点,在程序执行的开始位置,也就是导包之后我们需要加上猴子补丁monkey.patch_all(),这个就相当于一个监工,他来看谁闲着,谁忙着,如果说有人出现了耗时操作,比如time.sleep(1),那么就由他帮我们来切换协程.

2.5.4多任务基础

2.5.1.1并行和并发

  • 并行:真的多任务,任务数小于等于电脑的核数,多个程序同时执行
  • 并发:假的多任务,任务数大于电脑的核数,多个程序交互执行

2.5.1.2阻塞和非阻塞

  • 阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态
  • 阻塞调用是指调用结果返回之前,当前线程会被挂起.调用线程只有在得到结果之后才会返回
  • 非阻塞调用是指在不能立刻得到结果之前,该调用不会阻塞当前线程

2.5.1.3进程线程和协程

先有进程,然后进程创建多个线程,线程依附在进程里面,线程里面可以包含多个协程.

  • 进程之间不共享全局变量(资源分配的最小单位),线程之间共享全局变量(资源调度的最小单位,注意资源竞争问题)
  • 多进程开发要比单进程多线程开发稳定性强,但是多进程开发比多线程开发消耗资源大
  • 多线程开发线程之间无序存在,但是协程之间一定是交替执行(主要用于网络爬虫)
  • 开辟一个协程大概需要5k空间,开辟线程需要512k,开辟进程将占用资源更多

2.5.1.4程序和进程的区别

  • 简单的理解:一个软件,没有运行的时候叫程序,运行的时候就叫进程;
  • 实质上理解:程序没有调用系统的资源,但是当程序运行起来之后就会占用系统资源,这时候我们称它为进程。

2.5.1.5同步和异步

  • 同步就是协同步调,程序1执行的时候,程序2不能执行,只能等到查程序1执行完毕之后程序2才能去执行,可以和线程放在一起理解.
  • 异步就是两个程序执行的时候互不影响,可以和进程放在一起理解.

 

 

  • 14
    点赞
  • 146
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python进阶之路》是一本非常值得推荐的Python进阶书籍。这本书由一位经验丰富的Python大牛所著,作者拥有超过20年的Python开发经验。这本书涵盖了许多Python进阶知识点,如元编程、动态属性、属性描述符、异步处理等。书中详细列举了这些高级特性的使用方法,并讲解得非常透彻。如果你想从入门迈向进阶,这本书是必备的参考资料。 另外,《Python Cookbook》也是一本非常受欢迎的Python进阶书籍。这本书总结了大量精妙的编程技巧和实用的技术,无论你是Python新手还是老手,都会从中收获很多。豆瓣评分高达9.2分,可见其受到广大读者的认可。 除了以上两本书,《Python进阶技巧》也是一本非常值得一读的进阶书籍。这本书的作者将许多代码简化成了一行,展现了Python的高级技巧。虽然有些地方可能看起来有些夸张,但它确实帮助你了解Python的特性和一些不错的功能。而且,在关键时刻,这种技巧还可以让你轻松搞定其他人需要十几行代码才能完成的任务。对于想要进阶的同学来说,这本书的阅读是非常适合的。 总而言之,《Python进阶之路》、《Python Cookbook》和《Python进阶技巧》都是非常优秀的Python进阶书籍,适合想要深入学习Python的读者。 : 引用自《Python进阶之路》 : 引用自《Python Cookbook》 : 引用自《Python进阶技巧》

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值