2.1GIL(全局解释器锁)
GIL(Global Interpreter Lock)
2.1.1GIL执行流程
没有互斥锁的情形:
创建线程1和线程2,当启动线程1和线程2时,线程1先拿到了公共数据count(count的初始值为0),线程1向python解释器申请到了GIL,解释器开辟线程执行线程1,然后,cpu开始执行代码,执行了一段时间后,还未完成count+=1的操作,执行时间到了,释放GIL。此时,线程2向python解释器申请到了GIL,解释器开辟线程执行线程2,然后,cpu开始执行代码,经过一段时间后,完成了count+=1的操作,将count=1赋值给count并且释放GIL。此时线程1再次向python解释器申请到了GIL,解释器开始执行线程1,但此时是在之前执行到的那个位置继续执行,一段时间后,完成了count+=1的操作,并将count=1赋值给了count,但此时的count已经是1了,这就导致了本来预想的是给count加了两次,但实际上是只加了一次。
有互斥锁的情形:
创建线程1和线程2,当启动线程1和线程2时,线程1先拿到了公共数据count(count的初始值为0),并且给公共数据加了互斥锁,然后,线程1向python解释器申请到了GIL,解释器开辟线程执行线程1,接着,cpu开始执行代码,执行了一段时间后,还未完成count+=1的操作,执行时间到了,释放解释器的GIL,但此时并未释放公共数据的那把互斥锁,线程2即使拿到了GIL,也拿不到公共数据,线程2必须继续等待,只能切回线程1继续执行线程1。
2.1.2为什么会设计GIL这把锁
为了解决多线程之间数据完整性
2.1.3为何还要使用多进程
多线程:适用于IO密集型:数据传输,需要等待,不能影响其他线程的运行。
多进程:适用于计算密集型。比如计算某一个文件夹的大小。
计算密集型的任务完全是依靠于cpu的核数,所以需要尽量的完全利用cpu的。Python中多进程是可以利用多核的。
知识补充:使用htop命令可以查看cpu的执行情况
2.2浅拷贝和深拷贝
2.2.1赋值操作认知统一
(1)赋值是将一个对象的地址给一个变量,让该变量指向该地址
(2)修改不可变对象(str、tuple、int等)需要开辟新空间
(3)修改可变对象(list等)中的某一个值,该对象不需要开辟新空间;但若将可变对象整体修改,那么需要开辟新空间
2.2.2浅拷贝
(1)浅拷贝仅仅复制了容器中元素的地址
(2)浅拷贝的实现方式:
①使用copy模块中的copy函数
②使用切片操作
③使用工厂函数(list/set/dir)
2.2.3深拷贝(copy.deepcopy())
完全拷贝了一个容器的副本,容器内部元素地址都不一样
2.2.4总结浅拷贝和深拷贝
(1)如果是普通的=号,看等号右边是可变对象还是不可变对象
①如果是不可变对象,在重新赋值的时候,直接开辟一块新空间
②如果是可变对象,
A.如果改变的是可变对象的整体,地址会改变
B.如果改变的是可变对象的某一个元素,地址不变,改变的元素地址会发生变化
(2)如果是浅拷贝(切片、copy.copy()、工厂函数(list()、set()、dir())
返回的新对象,跟原对象的地址是不一样的,但是新对象中的元素的地址跟原对象中的地址是一样的。
(3)如果是深拷贝:copy.deepcopy()
①如果深拷贝的对象中,都是不可变对象,效果跟浅拷贝一样。
②如果深拷贝的对象中,是可变对象,重新来了一遍,对象以及对象中元素的地址都不一样。
2.3公有属性、私有属性和保护属性
xx:公有变量
_x:单前置下划线,From somemodule import * 禁止导入
__x:私有变量,外界不允许访问
__xx__:魔法对象
xx__:后置下划线,用于避免与python关键字重名
2.3.1私有属性的访问方法1
class Person(object):
def __init__(self,name,age,gender):
self.name = name
if age < 0 or age >100:
age = 18
self.__age = age
if gender != '男' and gender!='女':
gender = '男'
self.__gender = gender
def say_hello(self):
print("大家好,我叫%s,我今年%d岁了,我是%s生" %(self.name,self.__age,self.__gender))
person = Person("张三",180,"跟班长一样")
person.say_hello()
2.3.2私有属性的访问方法2
# 自定义set和get方法
class Person(object):
def __init__(self, name):
self.name = name
self.__age = 0
self.__sex = "男"
# def get_age(self):
# return self.__age
#
#
# def set_age(self, age):
# if age < 0 or age > 100:
# self.__age = 0
# else:
# self.__age = age
#Or:
def get_age(self):
if self.__age < 0 or self.__age > 100:
self.__age = 18
return self.__age
def set_age(self, age):
self.__age = age
person = Person("张三")
person.set_age(120)
print(person.get_age())
2.3.3私有属性的访问方法3:property
class Person(object):
def __init__(self, name):
self.name = name
self.__age = 0
self.__sex = "男"
def set_age(self, age):
if age < 0 or age > 100:
self.__age = 0
else:
self.__age = age
def get_age(self):
return self.__age
#Or:
# def get_age(self):
# if self.__age < 0 or self.__age > 100:
# self.__age = 0
# return self.__age
# else:
# return self.__age
#
# def set_age(self, age):
# self.__age = age
def del_age(self):
del self.__age
# property将get_age和set_age的引用进行了封装,赋值给了age
# 当对age进行赋值的时候,执行的是set_age
# 当对age进行取值的时候,执行的是get_age
# 注意顺序,必须先是get,然后是set
age = property(get_age, set_age, del_age)
person = Person("李四")
person.age = 180
print(person.age)
2.3.4私有属性的访问方法4:装饰器封装
class Person(object):
def __init__(self, name):
self.name = name
self.__age = 0
self.__sex = "男"
# 1、如果使用@property的方式,先把所有的方法名字,改为一致
# 2、在取值函数的上面加上@property
# 3、在设值和删除值的前面加上@......
@property
def age(self):
return self.__age
@age.setter
def age(self, age):
if age < 0 or age > 100:
self.__age = 0
else:
self.__age = age
#Or:
# @property
# def age(self):
# if self.__age < 0 or self.__age > 100:
# self.__age = 0
# return self.__age
# else:
# return self.__age
#
# @age.setter
# def age(self, age):
# self.__age = age
person = Person("李四")
person.age = 180
print(person.age)
2.4模块导入相关
2.4.1module和package
(1)module就是一个.py文件
(2)package就是一个包含.py文件的文件夹,文件夹中还包含一个特殊文件__.init__.py
2.4.2查看导入模块搜索路径
导入sys模块
输入:sys.path查看导入模块搜索路径(默认从当前路径找起)
2.4.3添加导入模块搜索路径
sys.path.append(“路径”)
sys.path.insert(“路径”)
2.4.4重新导入加载模块
from imp import reload
reload(模块名称)
2.4.5模块循环导入
“你中有我,我中有你”
在程序设计中,应该避免模块的循环导入
2.5反射
(1)概念:反射就是通过字符串的形式,导入模块,通过字符串的形式,去模块中寻找指定函数、并执行。
(2)作用:可以使用字符串的形式去模块中对模块中的成员进行增删改查
2.5.1动态导入模块(__import__)
将模块名以字符串的形式进行导入
__import__返回导入的模块对象的引用
fromlist:允许从其他的路径下进行导入
2.5.2反射中四个常用的函数
(1)getattr()
作用:找到指定对象中的指定成员,如果存在则返回成员引用,否则返回None
这个对象可以是模块,也可以普通的对象
参数一:表示要去哪个对象中查找成员
参数二:表示要查找的那个成员
参数三:表示如果找不到,返回default=None
(2)hasattr()
作用:判断指定对象中是否含有指定成员,有则返回True,否则返回False
参数一:要判断的模块对象
参数二:要找的成员
(3)setattr()
作用:给指定对象添加指定成员
例如:
#给指定对象添加属性
setattr(person,"age",18)
#给指定对象添加方法
setattr(person,"hello_python",lambda self:print("hello_python"))
# 由于hello_python是动态生成的,所以调用的时候,需要手动指定self的值
person.hello_python(person)
(4)delattr()
作用:删除指定对象中的指定成员
2.6多继承的问题
2.6.1继承的目的
为了让子类可以使用父类的成员,实现代码的复用。
2.6.2多继承带来的问题
钻石继承带来的问题就是子类会调用多次父类的__init__函数,造成资源浪费。
2.6.3解决方案:使用super()
(1)执行原理
super(cls,instance)
super会先获取instance的__mro__列表(方法关系解析顺序列表)然后找到列表中cls的下一个类,返回
(2)简化写法
super()
(3)使用super()的参数问题
在使用super()的过程中,会遇到参数传递混乱的问题
(4)使用*args和**kwargs可以解决super()的参数问题
建议:在使用super时,建议使用加参数的形式,这样更便于阅读
2.6.4*args和**kwargs的使用
(1)*args
当*args以形参出现时,解释器看到*args,会将传进来的多余的参数都赋给*args;当args以实参出现时,表示打包,会得到一个元组;当*args以实参出现时,表示拆包
(2)**kwargs
当**kwargs以形参出现时,解释器看到**kwargs,会将传进来的多余的参数都赋给**kwargs;当kwargs以实参出现时,表示打包,会得到一个字典;当**kwargs以实参出现时,表示拆包(注意:不支持print(**kwargs))
2.7类、实例对象、类方法、实例方法、类属性、实例属性、静态方法
2.7.1类、实例对象、类属性、实例属性
(1)类:用来创建对象
(2)实例对象:通过类创建出来的对象
(3)类属性:属于类的属性,内存中只有一份
(4)实例属性:属于实例的属性,每个对象中都有一份
2.7.2实例方法、类方法、静态方法
(1)三种方法的存储
实例方法、类方法、静态方法在内存中都存储在类中,区别就是调用不同。
(2)三种方法的调用
实例方法:由对象进行调用,必须传递一个self参数,调用时,将调用该方法的对象赋值给self。
类方法:由类调用,必须传递一个cls参数,调用时,将调用该方法的类传递给
cls
静态方法:由类调用,无默认参数
2.8两个原则
2.8.1开放封闭原则
对扩展开放
对修改封闭
2.8.2高内聚、低耦合
耦合:模块与模块之间的依赖程度
内聚:模块内部成员的独立性
2.9魔法方法
在python中,有一些内置好的方法,这些方法在我们执行某些特定的操作时会被自动调用,也可以手动调用,这些方法我们称之为魔法方法
2.9.1__class__()
获得当前实例的所属的类
2.9.2__getattribute_()
(1)属性访问拦截器,在访问实例属性的时候自动调用。
(2)类中的属性和方法,都可以使用__getattribute__进行获取。
(3)注意:以后不要在__getattribute__方法中调用self.xxxx
例子:
class Itcast(object):
def __init__(self,subject1):
self.subject1 = subject1
self.subject2 = 'cpp'
#属性访问时拦截器,打log
def __getattribute__(self,obj):
if obj == 'subject1':
print('log subject1')
return 'redirect python'
else: #测试时注释掉这2行,将找不到subject2
return object.__getattribute__(self,obj)
def show(self):
print('this is Itcast')
s = Itcast("python")
print(s.subject1)
print(s.subject2)
2.9.3__bases__()
获取指定类的所有父类构成元素,使用方法为 :类名.__bases__
2.9.4__call__()
具有__call__这个魔法方法的对象可以使用XXX()的形式进行调用。
比普通的函数,可以封装更多的功能
2.10上下文管理器
实现了上下文协议的对象即为上下文管理器
2.10.1上下文协议
__enter__、__exit__
2.10.2作用
用于资源的获取和释放
2.10.3总结
Python 提供了 with 语法用于简化资源操作的后续清除操作,是 try/finally 的替代方法,实现原理建立在上下文管理器之上。此外,Python 还提供了一个contextmanager 装饰器,更进一步简化上下管理器的实现方式
例子:
from contextlib import contextmanager
@contextmanager
def my_open(path, mode):
f = open(path, mode)
yield f
f.close()
调用:
with my_open('out.txt', 'w') as f:
f.write("hello , the simplest context manager")
2.11元类
Python中的类,本质上也是一个对象。type就是元类,也就是创建类的类
2.12Garbage collection(垃圾回收机制——gc)
2.12.1小整数对象池
Python 对小整数的定义是 [-5, 257) ,这些整数对象是提前建立好的,不会被垃圾回收。在一个 Python 的程序中,所有位于这个范围内的整数使用的都是同一个对象。但是当定义2个相同的字符串时,引用计数为0,触发垃圾回收
2.12.2大整数对象池
每一个大整数,均创建一个新的对象。
2.12.3intern机制——共享机制
python中有这样一个机制——intern机制,让其只占用一个”HelloWorld”所占的内存空间。靠引用计数去维护何时释放。
2.12.4总结
(1)小整数[-5,257)共用对象,常驻内存
(2)单个字符共用对象,常驻内存
(3)单个单词,不可修改,默认开启intern机制,共用对象,引用计数为0,则销毁
(4)字符串(含有空格等),不可修改,没开启intern机制,不共用对象,引用计数为0,销毁
(5)大整数不共用内存,引用计数为0,销毁
(6)数值类型和字符串类型在 Python 中都是不可变的,这就意味着无法修改这个对象的值,每次对变量的修改,实际上是创建一个新的对象
2.12.5垃圾回收机制—gc
(1)python中gc采用的是引用计数机制为主,标记-清除和隔代回收两种机制为辅的策略
(2)引用计数机制优缺点:
A.优点:
①简单
②实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。
B.缺点:①维护引用计数消耗资源②循环引用
(3)查看一个对象的引用计数
import sys
sys.getrefcount(xxx)
(4)导致引用计数+1的情况
- 对象被创建,例如a=23
- 对象被引用,例如b=a
- 对象被作为参数,传入到一个函数中,例如func(a)
- 对象作为一个元素,存储在容器中,例如list1=[a,a]
(5)导致引用计数-1的情况
- 对象的别名被显式销毁,例如del a
- 对象的别名被赋予新的对象,例如a=24
- 一个对象离开它的作用域,例如f函数执行完毕时,func函数中的局部变量(全局变量不会)
- 对象所在的容器被销毁,或从容器中删除对象
(6)隔代回收机制
把对象分为三代,一开始,对象在创建的时候,放在一代中,如果在一次一代的垃圾检查中,该对象存活下来,就会被放到二代中,同理在一次二代的垃圾检查中,该对象存活下来,就会被放到三代中。
(7)有三种情况会触发垃圾回收
①调用gc.collect()
②当gc模块的计数器达到阀值的时候。
③程序退出的时候
(8)gc模块常用功能解析
①gc.get_threshold() 获取的gc模块中自动执行垃圾回收的频率。
②gc.set_threshold(threshold0[, threshold1[, threshold2]) 设置自动执行垃圾回收的频率。
③gc.get_count() 获取当前自动执行垃圾回收的计数器,返回一个长度为3的列表
2.13闭包
(1)在一个函数中定义了一个函数,里面的函数使用到了外面函数的数据,并且外面这个函数的返回值是里面这个函数的引用,我们管这个过程称之为闭包。
(2)注意:如果在内函数中对外函数的数据直接修改会报错,这时需要在内函数中使用关键字nonlocal,目的是告诉解释器该数据在外函数中不在自己内部
(3)LEGB含义解释
L-Local(function):函数内的名字空间
E-Enclosing function locals:外部嵌套函数的名字空间(例如closure)
G-Global(module):函数定义所在模块(文件)的名字空间
B-Builtin(Python):Python内置模块的名字空间