035.猴子补丁、内置函数、垃圾回收机制及进程理论知识(一)

一、猴子补丁

(一)什么是猴子补丁

​ 属性在运行时的动态替换,叫做猴子补丁(Monkey Patch)。

​ 猴子补丁的核心就是用自己的代码替换所用莫块的源代码。

(二)猴子补丁的功能(一切皆对象)

1.拥有在模块运行时替换的功能,例如:一个函数对象赋值给另一个函数对象(把函数原本的执行的功能给替换了)。

(三)monkey patch 的应用场景

​ 如果我们的程序中已经基于json模块编写了大量代码了,发现有一个模块ujson比他性能更高,但用法一样,我们肯定不会想把所有的代码都换成ujson.dumps或者ujson.loads,那我们可能会想到这么做:import ujson as json,但是这么做需要将每一个文件都重新导入一下,维护成本依然很高,此时我们就可以用到猴子补丁了。

import json 
import ujson

def monkey_patch_json():
    json.__name__ = 'ujson'
    json.dumps = ujson.dumps
    json.loads = ujson.loads
   
monkey_patch_json() # 之所以在入口处加,是因为模块在导入一次后,后续的导入便直接引用第一次的成果

#其实这种场景也比较多, 比如我们引用团队通用库里的一个模块, 又想丰富模块的功能, 除了继承之外也可以考虑用Monkey Patch.采用猴子补丁之后,如果发现ujson不符合预期,那也可以快速撤掉补丁。

**缺点:**可能会污染源代码。

二、内置函数补充

1.@classmethod

​ 把一个方法封装成类方法。

​ 一个类方法把类自己作为第一个实参,就像一个实例(对象)方法把实例自己作为第一个实参。

class C:
    aaa = 'CCC'

    @classmethod
    def fk(cls):
        print('fk u————>from class C', cls, cls.aaa)


C.fk()  # fk u————>from class C <class '__main__.C'> CCC
obj1 = C()
obj1.fk()  # fk u————>from class C <class '__main__.C'> CCC


class D(C):
    aaa = 'DDD'

    def dk(self):
        print('fk u————>from class D')


D.fk()  # fk u————>from class C <class '__main__.D'> DDD
obj2 = D()
obj2.fk()  # fk u————>from class C <class '__main__.D'> DDD        

​ 类方法的调用可以在类上进行 (例如 C.f()) 也可以在实例上进行 (例如 C().f())。 其所属类以外的类实例会被忽略。 如果类方法在其所属类的派生类上调用,则该派生类对象会被作为隐含的第一个参数被传入。

2.enumerate

enumerate(iterable, start=0)

​ 返回一个枚举对象。iterable 必须是一个序列,或 iterator,或其他支持迭代的对象。 enumerate() 返回的迭代器的 __ next __() 方法返回一个元组,里面包含一个计数值(从 start 开始,默认为 0)和通过迭代 iterable 获得的值。

3.filter(function, iterable)

​ 用 iterable 中函数 function 返回真的那些元素,构建一个新的迭代器。iterable 可以是一个序列,一个支持迭代的容器,或一个迭代器。如果 function 是 None ,则会假设它是一个身份函数,即 iterable 中所有返回假的元素会被移除。

​ 请注意, filter(function, iterable) 相当于一个生成器表达式,

​ 当 function 不是 None 的时候为 (item for item in iterable if function(item));

​ function 是 None 的时候为 (item for item in iterable if item) 。

def is_odd(n):
    return n % 2 == 1
tmplist = filter(is_odd, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print(list(tmplist))  # [1, 3, 5, 7, 9]

3.globals()

​ 会以字典类型返回当前位置的全部全局变量。这总是当前模块的字典(在函数或方法中,不是调用它的模块,而是定义它的模块)。

4.locals()

​ 更新并返回当前位置的全部局部变量,以字典类型的形式。 在函数代码块但不是类代码块中调用 locals() 时将返回自由变量。 请注意在模块层级上,locals() 和 globals() 是同一个字典。

5.max()

*max(iterable, [, key, default])
*max(arg1, arg2, args[, key])

​ 返回可迭代对象中最大的元素,或者返回两个及以上实参中最大的。

​ 如果只提供了一个位置参数,它必须是非空 iterable,返回可迭代对象中最大的元素;如果提供了两个及以上的位置参数,则返回最大的位置参数。

​ 有两个可选只能用关键字的实参。key 实参指定排序函数用的参数,如传给 list.sort() 的。default 实参是当可迭代对象为空时返回的值。如果可迭代对象为空,并且没有给 default ,则会触发 ValueError。

​ 如果有多个最大元素,则此函数将返回第一个找到的。这和其他稳定排序工具如 sorted(iterable, key=keyfunc, reverse=True)[0] 和 heapq.nlargest(1, iterable, key=keyfunc) 保持一致。

salary = {'egon': 4.4,"lqz": 3.3,'yj': 2.2}

res = max(salary, key=lambda k: salary[k])
print(res)  # egon

6.min()

*min(iterable, [, key, default])
*min(arg1, arg2, args[, key])

​ 返回可迭代对象中最小的元素,或者返回两个及以上实参中最小的。

​ 如果只提供了一个位置参数,它必须是 iterable,返回可迭代对象中最小的元素;如果提供了两个及以上的位置参数,则返回最小的位置参数。

​ 有两个可选只能用关键字的实参。key 实参指定排序函数用的参数,如传给 list.sort() 的。default 实参是当可迭代对象为空时返回的值。如果可迭代对象为空,并且没有给 default ,则会触发 ValueError。

​ 如果有多个最小元素,则此函数将返回第一个找到的。这和其他稳定排序工具如 sorted(iterable, key=keyfunc)[0] 和 heapq.nsmallest(1, iterable, key=keyfunc) 保持一致。

salary = {'egon': 4.4,"lqz": 3.3,'yj': 2.2}

res = sorted(salary, key=lambda k: salary[k], reverse = True)
print(res)  # ['egon', 'lqz', 'yj']

7.sorted()

*sorted(iterable, , key=None, reverse=False)

​ 根据 iterable 中的项返回一个新的已排序列表。

​ 具有两个可选参数,它们都必须指定为关键字参数。

​ key 指定带有单个参数的函数,用于从 iterable 的每个元素中提取用于比较的键 (例如 key=str.lower)。 默认值为 None (直接比较元素)。

​ reverse 为一个布尔值。 如果设为 True,则每个列表元素将按反向顺序比较进行排序。

​ 内置的 sorted() 确保是稳定的。 如果一个排序确保不会改变比较结果相等的元素的相对顺序就称其为稳定的 — 这有利于进行多重排序(例如先按部门、再按薪级排序)。

salary = {'egon': 4.4,"lqz": 3.3,'yj': 2.2}

res = sorted(salary, key=lambda k: salary[k])
print(res)  # ['yj', 'lqz', 'egon']

三、垃圾回收机制

(一)什么是垃圾回收机制

​ 垃圾回收机制(简称GC)是Python解释器自带一种机,专门用来回收不可用的变量值所占用的内存空间。

(二)为何要用垃圾回收机制

​ 程序运行过程中会申请大量的内存空间,而对于一些无用的内存空间如果不及时清理的话会导致内存使用殆尽(内存溢出),导致程序崩溃,因此管理内存是一件重要且繁杂的事情,而python解释器自带的垃圾回收机制把程序员从繁杂的内存管理中解放出来。

(三)理解GC原理的基础知识

1.堆区与栈区

​ 定义变量的时候,变量名与变量值都是需要存储的,分别对应内存中的两块区域:堆区与栈区。

# 1、变量名与值内存地址的关联关系存放于栈区.

# 2、变量值存放于堆区,内存管理回收的则是堆区的内容.

2.直接引用与间接引用

​ 直接引用指的是从栈区出发直接引用到的内存地址。

​ 间接引用指的是从栈区出发引用到堆区后,再通过进一步引用才能到达的内存地址。

(四)垃圾回收机制原理分析

​ Python的GC模块主要运用了“引用计数”(reference counting)来跟踪和回收垃圾。在引用计数的基础上,还可以通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用的问题,并且通过“分代回收”(generation collection)以空间换取时间的方式来进一步提高垃圾回收的效率。

1.引用计数

​ 引用计数就是:变量值被变量名关联的次数(被间接引用也会增加引用计数)。

2.引用计数的问题一:循环引用

​ 引用计数机制存在着一个致命的弱点,即循环引用(也称交叉引用)。

# 如下我们定义了两个列表,简称列表1与列表2,变量名l1指向列表1,变量名l2指向列表2
>>> l1=['xxx']  # 列表1被引用一次,列表1的引用计数变为1   
>>> l2=['yyy']  # 列表2被引用一次,列表2的引用计数变为1   
>>> l1.append(l2)             # 把列表2追加到l1中作为第二个元素,列表2的引用计数变为2
>>> l2.append(l1)             # 把列表1追加到l2中作为第二个元素,列表1的引用计数变为2

# l1与l2之间有相互引用
# l1 = ['xxx'的内存地址,列表2的内存地址]
# l2 = ['yyy'的内存地址,列表1的内存地址]
>>> l1
['xxx', ['yyy', [...]]]
>>> l2
['yyy', ['xxx', [...]]]
>>> l1[1][1][0]
'xxx'

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XrllclLc-1597843180574)(D:\Onedrive\文档\01学习资料\全栈15博客笔记\python学习博客及作业\day035.猴子补丁、内置函数、垃圾回收机制及操作系统简介\001.jpg)]

​ 循环引用会导致:值不再被任何名字关联,但是值的引用计数并不会为0,应该被回收但不能被回收。

>>> del l1 # 列表1的引用计数减1,列表1的引用计数变为1
>>> del l2 # 列表2的引用计数减1,列表2的引用计数变为1

​ 此时,只剩下列表1与列表2之间的相互引用:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HFFI4cgZ-1597843180576)(D:\Onedrive\文档\01学习资料\全栈15博客笔记\python学习博客及作业\day035.猴子补丁、内置函数、垃圾回收机制及操作系统简介\002.jpg)]

​ 但此时两个列表的引用计数均不为0,但两个列表不再被任何其他对象关联,没有任何人可以再引用到它们,所以它俩占用内存空间应该被回收,但由于相互引用的存在,每一个对象的引用计数都不为0,因此这些对象所占用的内存永远不会被释放,所以循环引用是致命的,这与手动进行内存管理所产生的内存泄露毫无区别。 所以Python引入了“标记-清除” 与“分代回收”来分别解决引用计数的循环引用与效率低的问题。

3.问题一解决方案:标记–清除

​ 容器对象(比如:list,set,dict,class,instance)都可以包含对其他对象的引用,所以都可能产生循环引用。而“标记-清除”计数就是为了解决循环引用的问题。

​ 标记/清除算法的做法是当应用程序可用的内存空间被耗尽的时,就会停止整个程序,然后进行两项工作,第一项则是标记,第二项则是清除.

#1、标记
通俗地讲就是:
栈区相当于“根”,凡是从根出发可以访达(直接或间接引用)的,都称之为“有根之人”,有根之人当活,无根之人当死。

具体地:标记的过程其实就是,遍历所有的GC Roots对象(栈区中的所有内容或者线程都可以作为GC Roots对象),然后将所有GC Roots的对象可以直接或间接访问到的对象标记为存活的对象,其余的均为非存活对象,应该被清除。

#2、清除
清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉。

​ 基于上例的循环引用,当我们同时删除l1与l2时,会清理到栈区中l1与l2的内容以及直接引用关系。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zGWt2kXe-1597843180577)(D:\Onedrive\文档\01学习资料\全栈15博客笔记\python学习博客及作业\day035.猴子补丁、内置函数、垃圾回收机制及操作系统简介\003.jpg)]

​ 这样在启用标记清除算法时,从栈区出发,没有任何一条直接或间接引用可以访达l1与l2,即l1与l2成了“无根之人”,于是l1与l2都没有被标记为存活,二者会被清理掉,这样就解决了循环引用带来的内存泄漏问题。

4.引用计数的问题二:效率问题

​ 基于引用计数的回收机制,每次回收内存,都需要把所有对象的引用计数都遍历一遍,这是非常消耗时间的,于是引入了分代回收来提高回收效率,分代回收采用的是用“空间换时间”的策略。

5.问题二解决方案:分代–回收

分代:

​ 分代回收的核心思想是:在历经多次扫描的情况下,都没有被回收的变量,gc机制就会认为,该变量是常用变量,gc对其扫描的频率会降低,具体实现原理如下:

分代指的是根据存活时间来为变量划分不同等级(也就是不同的代)

新定义的变量,放到新生代这个等级中,假设每隔1分钟扫描新生代一次,如果发现变量依然被引用,那么该对象的权重(权重本质就是个整数)加一,当变量的权重大于某个设定得值(假设为3),会将它移动到更高一级的青春代,青春代的gc扫描的频率低于新生代(扫描时间间隔更长),假设5分钟扫描青春代一次,这样每次gc需要扫描的变量的总个数就变少了,节省了扫描的总时间,接下来,青春代中的对象,也会以同样的方式被移动到老年代中。也就是等级(代)越高,被垃圾回收机制扫描的频率越低。

回收:

​ 回收依然是使用引用计数作为回收的依据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-reWG2ss0-1597843180579)(D:\Onedrive\文档\01学习资料\全栈15博客笔记\python学习博客及作业\day035.猴子补丁、内置函数、垃圾回收机制及操作系统简介\004.jpg)]

​ 虽然分代回收可以起到提升效率的效果,但也存在一定的缺点:

#例如一个变量刚刚从新生代移入青春代,该变量的绑定关系就解除了,该变量应该被回收,但青春代的扫描频率低于新生代,这就到导致了应该被回收的垃圾没有得到及时地清理。

没有十全十美的方案:
毫无疑问,如果没有分代回收,即引用计数机制一直不停地对所有变量进行全体扫描,可以更及时地清理掉垃圾占用的内存,但这种一直不停地对所有变量进行全体扫描的方式效率极低,所以我们只能将二者中和。

综上
垃圾回收机制是在清理垃圾&释放内存的大背景下,允许分代回收以极小部分垃圾不会被及时释放为代价,以此换取引用计数整体扫描频率的降低,从而提升其性能,这是一种以空间换时间的解决方案目录

四、进程理论知识(一)

(一)操作系统的作用

1.隐藏丑陋复杂的硬件接口,提供良好的抽象接口;应用程序员有了这些接口后,就不用再考虑操作硬件的细节,专心开发自己的应用程序即可。

2.管理、调度进程,并且将多个进程对硬件的竞争变的有序。

(二)多道技术

1.产生背景:

​ 针对单核,实现并发。

​ 多道技术中的多道指的是多个程序,多道技术的实现是为了解决多个程序竞争或者说共享同一个资源(比如cpu)的有序调度问题,解决方式即多路复用,多路复用分为时间上的复用和空间上的复用。

PS:现在的主机一般都是多核,那么每个核都会利用多道技术。有4个cpu,运行于cpu1的某个程序遇到io阻塞,会等到io结束再重新调度,会被调度到4个cpu中的任意一个,具体由操作系统调度算法决定。

2.空间上的复用:如内存中同时有多道程序

​ 将内存分为几部分,每个部分放入一个程序,这样,同一时间内存中就有了多道程序。

3.时间上复用:复用一个cpu的时间片

​ 当一个程序在等待I/O时,另一个程序可以使用cpu,如果内存中可以同时存放足够多的作业,则cpu的利用率可以接近100%,即统筹的执行任务。(操作系统采用了多道技术后,可以控制进程的切换,或者说进程之间去争抢cpu的执行权限。这种切换不仅会在一个进程遇到io时进行,一个进程占用cpu时间过长也会切换,或者说被操作系统夺走cpu的执行权限)。

(三)程序

​ 程序就是一堆代码文件。

(四)进程

​ 进程是一个抽象的概念,就是一个程序的运行过程。

​ 负责执行令进程的是CPU。

​ 进程是操作系统最核心的概念。

注意:同一个程序执行两次,那也是两个进程。

(五)串行、并发与并行

​ 无论是并发与并行,在用户看来都是“同时”运行的,不管是进程还是线程,都只是一个任务而已,真正干活的是CPU,CPU来做者写任务,而一个CPU同一时刻只能执行一个任务。

1.串行

​ 一个运行完毕再运行下一个。

2.并发

​ 是伪并行,及看起来是同时运行。单个cpu与多道技术可以实现并发,(并行也属于并发)。

3.并行

​ 同时运行,只有具有多个cpu才可以实现并行。

​ 单核下,可以利用多道技术,多个核,每个核也可以利用多道技术(多道技术是针对单核而言的)。

​ 有四个核,六个任务,这样同一时间有四个任务被执行,假设分别被分配给了cpu1,cpu2,cpu3,cpu4,一旦任务1遇到I/O就被迫中断执行,此时任务5就拿到cpu1的时间片去执行,这就是单核下的多道技术,而一旦任务1的I/O结束了,操作系统会重新调用它(需知进程的调度、分配给哪个cpu运行,由操作系统说了算),可能被分配给四个cpu中的任意一个去执行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XTJi6tOp-1597843180582)(D:\Onedrive\文档\01学习资料\全栈15博客笔记\python学习博客及作业\day035.猴子补丁、内置函数、垃圾回收机制及操作系统简介\006.png)]

(四)线程

​ 在传统的操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程。

​ 线程顾名思义,就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程是一个进程车间负责把资源整合到一起,是一个资源单位,而一个车间内至少有一个流水线流水线的工作需要电源,电源就相当于cpu所以,进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。

​ 多线程(即多个控制线程)的概念是,在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间,相当于一个车间内有多条流水线,都共用一个车间的资源。

单核下的多道技术,而一旦任务1的I/O结束了,操作系统会重新调用它(需知进程的调度、分配给哪个cpu运行,由操作系统说了算),可能被分配给四个cpu中的任意一个去执行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值