一、Python 的内存管理机制及调优手段?
Python内存管理机制
:引用计数、垃圾回收、内存池。
1、引用计数
引用计数是一种非常高效的内存管理手段, 当一个 Python 对象被引用时其引用计数增加 1, 当其不再被一个变量引用时则计数减 1, 当引用计数等于 0 时对象被删除。
2、垃圾回收
(1) 引用计数
引用计数也是一种垃圾收集机制,而且也是一种最直观,最简单的垃圾收集技术。当 Python 的某个对象的引用计数降为 0 时,说明没有任何引用指向该对象,该对象就成为要被回收的垃圾了。比如某个新建对象,它被分配给某个引用,对象的引用计数变为 1。如果引用被删除,对象的引用计数为 0, 那么该对象就可以被垃圾回收。不过如果出现循环引用的话,引用计数机制就不再起有效的作用了。
(2) 标记清除
如果两个对象的引用计数都为 1,但是仅仅存在他们之间的循环引用,那么这两个对象都是需要被回收的,也就是说,它们的引用计数虽然表现为非 0,但实际上有效的引用计数为 0。所以先将循环引用摘掉,就会得出这两个对象的有效计数。
(3) 分代回收
-
分代回收是一种以空间换时间的操作方式,Python 将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python 将内存分为了 3“代”,分别为年轻代(第 0 代)、中年代(第 1 代)、老年代(第 2 代),他们对应的是 3 个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。
-
新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python 垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。
-
同时,分代回收是建立在标记清除技术基础之上。分代回收同样作为 Python 的辅助垃圾收集技术处理那些容器对象。
3、内存池
Python在运行期间会大量地执行malloc
和free
的操作,频繁地在用户态和核心态之间进行切换,这将严重影响Python的执行效率。为了加速Python的执行效 率,Python引入了一个内存池机制,用于管理对小块内存的申请和释放。
4、调优手段
1、手动垃圾回收。对Python的垃圾回收进行调优的一个最简单的手段便是关闭自动回收, 根据情况手动触发。
2、调高垃圾回收阈值。调高阈值的方法能在一定程度上避免内存溢出的问题(但不能完全避免), 同时可能减少可观的垃圾回收开销. 根据具体项目 的不同, 甚至是程序输入的不同, 合适的阈值也不同. 因此需要反复测试找到一个合适的阈值。
3、避免循环引用。使用良好的编程习惯尽可能的避免循环引用. 两种常见的手段: 手动解循环引用和使用弱引用。
二、什么是 lambda 函数,有什么好处?
lambda
函数是一个可以接收任意多个参数(包括可选参数)并且返回单个表达式值的函数。
lambda 函数好处:
1、lambda 函数比较轻便,即用即扔,很适合需要完成一项功能,但是此功能只在此一处使用, 连名字都很随意的情况下;
2、匿名函数,一般用来给 filter, map 这样的函数式编程服务;
3、作为回调函数,传递给某些应用,比如消息处理。
举个例子,提取列表中的偶数。
三、你对装饰器的理解?
装饰器
本质上是一个Python函数,它可以让已有的函数不做任何改动的情况下增加功能。非常适合有切面需求的场景,比如权限校验,日志记录和性能测试等等。如果你想要执行某个函数前记录日志或者记录时间来统计性能,又不想改动这个函数,就可以通过装饰器来实现。
写出一个计时器装饰器,记录函数的执行时间。
四、Python 排序算法的理解
以下主要介绍冒泡排序、选择排序、插入排序、快速排序四种排序算法:
1、冒泡排序
思路:每相邻的两个数进行比较,如果前边的比后边的数大,则交换这两个数,重复操作,这样的话每一趟会确定一个最大值。
如下代码所示,冒泡排序的时间复杂度为O(n2)。
2、选择排序
思路:第一趟遍历选择一个最小的数(或最大),放到第一个位置,下一趟遍历继续从列表中找最小的值放到已排序序列的末尾,说白了就是每趟找一个最小的值(或者最大的)~这个算法的关键在于最小数的【位置】。
选择排序时间复杂度为O(n2)。
3、插入排序
思路:将列表分为有序区和无序区,开始的时候有序区只有一个元素,每次从无序区取一个元素插入到有序区内,直到无序区内的元素被取完~这个算法的关键是【有序区已有】的元素和【即将要插入】到有序区的元素。
插入排序的时间复杂度为O(n2)。
4、快速排序
思路:从列表中取一个元素,假如取第一个元素,将这个元素排好后前面的元素都比这个元素小,后面的都比这个元素大,即这个元素所在的位置即是完全排序后该元素的位置,后面递归处理即可~这个算法的关键即【递归】。
快速排序的时间复杂度为O(nlogn)。
五、Python常见的魔法方法
Python 的类⾥提供的,两个下划线开始,两个下划线结束的⽅法,就是魔法⽅法
,魔法⽅法在恰当的时候就会被激活,⾃动执⾏。 使用Python的魔法方法可以使Python的自由度变得更高,当不需要重写时魔法方法也可以在规定的默认情况下生效,在需要重写时也可以让使用者根据自己的需求来重写部分方法来达到自己的预期。
1、__init__⽅法
__init__() ⽅法,在创建⼀个对象时默认被调⽤,不需要⼿动调⽤。在开发中,如果希望在创建对象的同时,就设置对象的属性,可以对 __init__⽅法进⾏改造。
2、__new__方法
__init__() ⽅法,当一个实例被创建的时候初始化的方法,但是它并不是实例化调用的第一个方法,__new__才是实例化对象调用的第一个方法。__new__执行后会返回实例对象(self),然后将self作为第一个参数传给__init__()方法。__new__很少使用,但是也有它适合的场景,尤其是当类继承自一个像元组或者字符串这样不经常改变的类型的时候。
使用__new__实现单例模式。
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
def __new__(cls, *args, **kwargs):
if not hasattr(cls, 'instance'):
cls.instance = super().__new__(cls)
return cls.instance
a = Person('coco', 18)
b = Person('vivi', 20)
print(id(a))
print(id(b))
输出结果:
3、__str__⽅法
__str__ ⽅法返回对象的描述信息,使⽤ print() 函数打印对象时,其实调⽤的就是这个对象的 __str__ ⽅法。
class Cat:
def __init__(self,name,color):
self.name = name
self.color = color
tom = Cat('Tom','white')
# 使⽤ print ⽅法打印对象时,会调⽤对象的 __str__ ⽅法,默认会打印类名和对象的内存地址名
print(tom) # <__main__.Cat object at 0x0000021BE3B9C940>
如果想要修改对象的输出的结果,可以重写 __str__ ⽅法。
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
def __str__(self):
return '哈哈'
p = Person('vivi',18)
print(p) # 哈哈 打印对象时,会⾃动调⽤对象的 __str__ ⽅法
⼀般情况下,我们在打印⼀个对象时,可能需要列出这个对象的所有属性。
class Student:
def __init__(self,name,score):
self.name = name
self.score = score
def __str__(self):
return '姓名是:{},成绩是{}分'.format(self.name,self.score)
s = Student('lisi',95)
print(s) # 姓名是:lisi,成绩是95分
4、__call__⽅法
让一个类的实例像函数一样被调用,对象后⾯加括号,触发执⾏。
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('__call__方法被调用了')
obj = Foo() # 执⾏ __init__
obj() # 执⾏ __call__
5、__getitem__、__setitem__、__delitem__
__getitem__、__setitem__、__delitem__,取值、赋值、删除“三剑客”。Python中,标识符后面加圆括号,通常代表执行或调用方法的意思。而在标识符后面加中括号[],通常代表取值的意思。Python设计了__getitem__()、__setitem__()、__delitem__()这三个特殊成员,用于执行与中括号有关的动作。它们分别表示取值、赋值、删除数据。
也就是如下的操作:
a = 标识符[] : 执行__getitem__方法
标识符[] = a : 执行__setitem__方法
del 标识符[] : 执行__delitem__方法
class Foo:
def __getitem__(self, key):
print('__getitem__',key)
def __setitem__(self, key, value):
print('__setitem__',key,value)
def __delitem__(self, key):
print('__delitem__',key)
obj = Foo()
result = obj['k1'] # 自动触发执行 __getitem__
obj['k2'] = 'jack' # 自动触发执行 __setitem__
del obj['k1'] # 自动触发执行 __delitem__
六、Python单例模式
单例模式
是一种常用的软件设计模式,在单例模式的核心结构中,只包含一个被称为单例类的特殊类,通过单例模式可以保证系统中一个类只有一个实例,而且这个实例可以轻易被外界访问,方便控制实例对象的个数以节约系统资源。
单例模式的要点有三个:
-
某个类只能有一个实例
-
这个类必须自行创建其唯一实例
-
这个类必须自行向整个系统提供这个唯一实例。
单例模式应用的场景一般发现在以下条件下:
资源共享的情况下,避免由于资源操作时导致的性能或损耗等,如日志文件,应用配置。
控制资源的情况下,方便资源之间的互相通信,如线程池等。
以下介绍,单例模式实现的三种方式:
1、使用__new__方法
先定义一个类,类中定义__new__方法,然后将类的一个实例类绑定到类变量中。
如果类的_instance值为None,则说明这个类还没有被实例化过,程序会自动实例化一个类的实例,然后返回。如果类的_instance值不为None,则程序会直接返回_instance。
代码如下:
class Singleton(object):
_instance = None
def __init__(self):
pass
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls._instance
class MyClass(Singleton):
a = 1
if __name__ == '__main__':
cls1 = MyClass()
cls2 = MyClass()
print(id(cls1))
print(id(cls2))
print(cls1 == cls2)
print(cls1 is cls2)
得到的结果:
2、使用decorator装饰器
我们知道,装饰器(decorator)可以动态地修改一个类或函数的功能。在这里使用装饰器来装饰某个类,使其只能生成一个实例,代码如下:
def singleton(cls):
instances={}
def getinstance(*args,**kwargs):
if cls not in instances:
instances[cls]=cls(*args,**kwargs)
return instances[cls]
return getinstance
@singleton
class MyClass(object):
a=1
if __name__ == '__main__':
cls1 = MyClass()
cls2 = MyClass()
print(id(cls1))
print(id(cls2))
print(cls1 == cls2)
print(cls1 is cls2)
得到的结果为:
3、使用元类(metaclass)
元类(metaclass)可以控制类的创建过程,它主要做三件事:
用元类实现单例模式的代码如下:
class Singleton(type):
_inst = {}
def __call__(self, *args, **kw):
if self not in self._inst:
self._inst[self] = super(Singleton, self).__call__(*args, **kw)
return self._inst[self]
class MyClass(metaclass=Singleton):
def __init__(self):
self.xx = 0
if __name__ == '__main__':
cls1 = MyClass()
cls2 = MyClass()
print(id(cls1))
print(id(cls2))
print(cls1 == cls2)
print(cls1 is cls2)
得到的结果为: