文章目录
一、全局解释器锁GIL
- GIL全局解释器锁
1.1查看资源占用
- 输入htop
1.2单线程死循环
请用虚拟机运行
while True:
pass
- 会发现一个核占满了
- 再在另一个终端里,运行同一个文件,会发现。两个核都占满了。
1.3多线程死循环
请用虚拟机运行
import threading
# 子线程死循环
def test():
while True:
pass
t1 = threading.Thread(target=test)
t1.start()
# 主线程死循环
while True:
pass
- 会发现两个核都没满,但是两个核的占用率之和为100%
1.4多进程死循环
请用虚拟机运行
import multiprocessing
# 子进程死循环
def test():
while True:
pass
p1 = multiprocessing.Process(target=test)
p1.start()
# 主进程死循环
while True:
pass
- 会发现两个核占满了
1.5GIL全局解释器锁
- 为什么多线程不会真的并发?答,其实只有一个线程在执行,一个线程执行时,另一个在休息。
- GIL让多线程程序,保证同一时刻只有一个线程在运行。运行多线程时,线程之间先抢锁,再运行。
- 官网推荐的解释器为
CPython解释器
,由C语言写成。由于历史原因,CPython里有GIL。 - 多线程为什么比单线程快?答,因为网速和阻塞。
- 计算密集型(没有延时和阻塞) : 用多进程
- io密集型(输入输出密集型/读写/收发):用多线程、多协程
1.6解决GIL
- 方法1: 换成Jpython等其他解释器运行
- 方法2: 换成其他语言实现
- 以C语言为例,实现:
- 第一行,如果需要在屏幕上输出的话,就必须要这一行
- C语言必须要有一个main函数
编译刚才的文件:gcc 文件名
得到一个`a.out’,运行它
- 准备一个loop.c文件(编写死循环代码)
- 把loop.c编译成一个动态库(linux平台下)
- 准备一个test.py文件
二、拷贝
1.深拷贝与浅拷贝
- 浅拷贝: 只拷贝最上面这层
- 深拷贝: 拷贝整个数据
- Python中一般是新建指向。其它语言(C语言),一般是拷贝数据。
- 用
id()
可以知道数据所在的地址 - Python中拷贝:
import copy
a = 1
# 浅拷贝
b = copy.copy(a)
# 深拷贝
c = copy.deepcopy(a)
- 当一个变量 = XXX时,约定为:这个变量指向XXX
- 原理图:
- 说明(浅拷贝): 浅拷贝只拷贝最上层。你让我拷谁,我拷谁。把要拷贝的原封不动拷贝下来,管你里面是数据还是引用,复制就完了。
- 说明(深拷贝): 深拷贝拷贝到了最下层。我会把最深层的数据挖出来复制一遍。
- 浅拷贝:若最上层是不可变类型 (元组),只会建立指向。
- 深拷贝:若所有层都是不可变类型 (元组),同样只会建立指向。只要有一层是可变类型 ,就会真正地深拷贝。
2.拷贝的其它方式
2.1切片是浅拷贝
- b = copy.copy(a) 等同于 b = a[:]
2.2复制字典是浅拷贝
- 字典里存的是引用(值在别的地方存着)
2.3传递参数传递的是引用
- 传递参数传递的是引用
- 如果不希望对原参数进行修改,那么:
function(copy.deepcopy(参数))
三、私有化
变量名 | 说明 |
---|---|
XX | 公有变量 |
_XX | 私有化属性或方法。禁止导入。类对象和子类可以访问。 |
__XX | 避免与子类属性命名冲突,无法在外部直接访问(名字重整所以访问不到) |
__XX__ | 魔法对象和属性 |
XX_ | 用于避免与Python关键字冲突 |
四、import
1.1导入模块的几种方式
- import 有两个功能。一、让python解释器去加载模块。二、定义一个变量,使它指向这个模块
导入模块 |
---|
import a |
import a, b |
import a as A |
from a import aaa |
from a import aaa, bbb |
from a import * |
1.2导入模块的先后顺序
- 按下面列表的先后顺序
- 添加模块的搜索路径
sys.path.append(“一个模块的路径”)
- 搜索模块插队
sys.path.insert(0, “一个模块的路径”)
1.3重新加载模块
- 若先导入模块且程序未结束时,修改了这个模块的代码。程序不会改变,因为原先的模块已经被加载到缓存中。再次使用import程序导入,也不会改变(import可以防止模块重复导入)。
- 重新加载模块:
from imp import reload
reload(希望重新加载的模块名)
- 注意: 只有import形式可以,不能用from…import形式
- 注意: 重新加载之前,一定要先导入这个模块
1.4from的问题
- 假如data.py文件中有一个变量a = 1,有两种方式导入
- 方式1: import data
- 方式2: from data import a
# 方式1,会改变data里的变量值
import data
data.a = 2 # 此时,data模块里的a值会变为2
from data import a
a = 2 # 此时,data里的a值不会变
- 原来,from … import … ,相当于拿了一个变量名建立了对1的引用(而不是对data.py里a的引用)。a=2就是把a对1的引用,变为对2的引用。所以不会改变原模块的值。
- 而import data,相当于拿一个变量名建立了对模块本身的引用,data.a就通过前面的data. 来找到这个模块下的a, 对其进行修改。
五、封装、继承、多态
1.封装
- python3中每一个对象都有一个属性
__class__
,通过这个属性,就知道谁创建了这个对象(即它的模板),从而找到了类方法 __dict__
可以标记一个对象中,究竟有些什么属性。
- 为什么没有num,却有num2?答,因为num2是这个实例独有的属性。
- 方法里的self能不能改成其它的?答,能,它只是个形参,但最好遵循规定。
2.继承
- 继承可以解决冗余
- 继承可以进行迭代
3.多态
- 如果没有重写父类方法,则会调用父类方法。如果重写,则调用子类方法。这种调用,但不清楚是调用子类还是父类的方法,就叫多态。
六、继承中MRO
1.重写和重载
- python中子类写与父类同名的方法,叫做重写(就是覆盖)
- python中的函数名就是一个变量名,这个变量名指向了一个函数而已。(所以python中几乎都是重写)
- 重载: 同样的方法,因为传入参数的不同,导致的结果不同。(比如python中的数字相加和字符串相加)
2.多继承mro
2.1 父类.方法名
- 子类可以通过
父类.方法名(self, 参数)
来调用父类的方法。但是有一个问题,当孙调用子1和子2的方法时,子1和子2会调用父的方法两次! - 代码:
- 结果:
2.2 super().方法名
- 子类可以通过
super().方法名(参数)
来调用父类的方法。 __mro__
可以打印出一个对象,这个对象决定调用方法的先后顺序。- 代码:
- 结果:
- 以孙为例,在元组中找到孙,它的下一个是子1,所以super()调用的是子1。
- 注意: super()里可以传参数(类名),不传时,默认使用所属类名。比如,在孙类下的super(),相当于super(孙)。
2.3 mro是什么
- mro是python3中利用C3算法得到的元组,是一个顺序表。C3算法可以使得不会重复调用,但也导致了有时不会调用到父类方法,比如子1调用的是子2的方法,如果子2不调用父类方法,那么父类方法就不被调用。
3.单继承mro
七、*元组参数&**关键字参数
1.复习元组参数和关键字参数
- 代码:
- 结果:
2.关键字被作为元组参数传入了
- 代码:
- 结果:
3.拆包
- 代码:
- 结果:
- 原来,*和**相当于拆包,一个拆元组,一个拆字典
八、类与实例、属性与方法
1. 类与实例的内存空间
- 对象: 拥有自己的内存空间,和自己的属性方法的就叫对象。
- __new__=====>创建对象(有个内存空间)
- __init__=====>对刚刚申请的空间,进行初始化
self指向创建的实例对象。
- 类属性只有一个(共用的都放在类对象里)。实例对象有一个方法__class__,指向类对象。通过它来访问类对象里的方法和属性。
2.通过实例对象修改类属性
- 了解原理,别这样干
- 实例名.类属性 = “XXX” 并不会改变类属性,只会在实例里生成一个同名的实例属性(会被优先调用)
- 实例名.__class__.类属性 可以改变类属性。
3.实例方法与类方法与静态方法
- 实例方法:
def 实例方法名(self):
- 类方法:
@classmethod
def 类方法名(cls):
- 静态方法:
@staticmethod
def 静态方法名():
- 两者的区别是,实例方法默认传入实例对象,类方法默认传入类对象,类默认不传参数。
- 静态方法类似于一个普通函数(把装饰器抹去,取消def前缩进与类平齐)。但是这个普通函数并不想全局使用,只想这一个类、实例使用。
4.property属性
1. property属性含义
- property属性:
@property
def property属性名(self):
- 调用:(因为是属性,所以不需要括号。该方法一般用以返回一个值。)
类名.property属性名
- 这个可读性更高。(希望要一个结果,如果调用方法会影响可读性)
- 面向对象,封装继承多态。这就是封装的体现。
2. property属性应用
- 一般一个私有属性
__私有属性名
,会搭配一个getter公有方法,和一个setter公有方法。一个管获取,一个管设置。 - 原代码:
- 改造后代码:
- 使用装饰器后代码:
九、魔法属性
1. 私有属性
- 为什么外部获取不了私有属性?答,名字重整。
__私有属性名
被改为了_类名__私有属性名
- 如果在外部使用
实例名.__私有属性名 = 一个值
,并不会改变私有属性的值,只会给实例添加一个实例属性。 实例名.__dict__
可以看到一个实例的属性,包括所属类的私有属性类名.__dict__
可以看到一个类的所有属性,包括魔法属性
2.魔法属性、方法
- 魔法方法、魔法属性: 当执行魔法方法时,对应Python解释器里的特殊操作
2.1 常用魔法属性
- __doc__:展示类的描述信息
- __class__:创建这个实例对象的类
- __module__:创建这个实例对象的类,在哪一个模块里
- __init__:初始化方法
- __new__:创建方法。与初始化方法合称构造方法。
- __del__:对象内存被释放时,自动触发
- __call__:
实例()
触发 - __dict__:检查类或实例对象里的所有属性
- __str__:直接输出该对象时,该对象的返回值
2.2 索引魔法属性
- 用于索引操作,当字典用。
- __getitem__:获取数据
- __setitem__:设置数据
- __delitem__:删除数据
2.3 分片魔法属性
- 首先,来重新认识一下切片操作
- 用于分片操作,如:列表
十、with与上下文管理器
1. with
- 文件描述符fd在一个进程里最多1024个。
def fun():
f = open("文件名", "w")
f.write("hello python")
f.close()
- 如果进程出现问题,导致close无法被正常调用,资源就会一直占用
def fun():
f = open("文件名", "w")
try:
f.write("hello python")
except IOError:
pass
finally:
f.close()
- 上面的代码,等同于:
def fun():
with open("文件名", "w") as f:
f.write("hello python")
2. 上下文管理器contextmanager
- 点击APP,进入新界面时,会把之前页面的信息存储,以便可以返回。以这个新界面为基础,再点击新界面就叫下文,返回就叫上文。
- 看书,一句话的前面就叫上文,后面就叫下文。
- 任何实现了__enter__()和__exit__()方法的对象都可称为上下文管理器。上下文管理器可以使用with关键字。(文件对象实现了上下文管理器)
- with语句后面要跟上下文管理器对象。
- File(“test.txt”, “w”)会创建一个实例对象
- with会检查整改实例对象是否是一个上下文管理器(检查有没有__enter__()和__exit__()
- 如果是,with会自动地调用__enter__()方法,然后把它的返回值给as后的f
- 假如产生了异常,就会调用__exit__()方法
3. 利用装饰器实现上下文管理器
- 利用yield分割。yield前的就是__enter__(),后的就是__exit__()