语言基础
1. 垃圾回收
- 引用计数 在python中每当你建立了一个Object的时候, Python都会帮你数着, 这个object上有多少个引用。当我们写python的时候呢,我们是不需要考虑引用这些事情的,引用的增加和减少都是在cpython中帮我们处理好了的。
引用计数是一种垃圾回收机制,它用于跟踪每个对象被引用的次数。每当一个对象被引用时,其引用计数加1;当一个引用指向该对象被删除或者超出作用域时,其引用计数减1。当引用计数减少到0时,对象就不再被引用,可以认为它没有被使用,此时垃圾回收器会自动回收该对象的内存空间。
Python中的引用计数机制是一种轻量级的垃圾回收机制,它是实时的,即当引用计数减少时,就会立即回收对象的内存。这种机制非常高效,基本上消除了动态分配和释放内存的开销。
下面是一些与引用计数相关的操作:
对象创建:当创建一个新对象时,其引用计数设置为1。 引用增加:当一个对象被赋值给一个变量、作为参数传递给函数等情况下,其引用计数加1。 引用减少:当一个对象的引用被删除、变量重新赋值、函数执行完毕等情况下,其引用计数减1。 引用计数为0:当一个对象的引用计数减少到0时,说明该对象没有任何引用指向它,即不再被使用,此时垃圾回收器会自动回收该对象的内存空间
循环引用问题(使用gc.collect()这个垃圾回收就是争对, 他就是去判断所有的对象,是不是所有的引用是不是循环引用
#定义两个列表
l1 = [111, ] # 列表[111,],引用计数+1
l2 = [222, ] # 列表[222,],引用计数+1
l1.append(l2) # l1=[值111的内存地址,l2列表的内存地址]
l2.append(l1) # l2=[值222的内存地址,l1列表的内存地址]
# 两个列表之间存在相互引用,在当将两个之间的关联解除是,但是值的引用并不会因为解除而变为0,那么就不会触发GC机制将占用的内存空间回收,例如:
del l1 # 列表1的引用计数减1,列表1的引用计数变为1
del l2 # 列表2的引用计数减1,列表2的引用计数变为1
容器container(可以引用其他对象的对象)
例如list, dict, class, object 等就是属于容器 但是int, str 这些就不是属于容器。
在cpython的源码中会发现,容器和非容器建立的时候 int类型使用的是pyobject_malloc来获取内存 而容器类型的object比如说list是用pyobject_gc_new来获取内存 在建立的时候就要告诉他,你是一个容器,你要被garbage collection所意识到。
在list的object在拿完内存之后,会吧这个object放到一个垃圾回收的双向链表里。
每一次触发垃圾回收的时候, 都会从这个双向链表里面找, 看看哪个容器只剩下循环引用了。
在python的官方文档里, 有个东西叫generation, 存在的意义是什么 因为在实际编程过程中,在大量的Object生命周期都非常短用完就可以扔掉,少量生命周期非常长扔不掉 如果我们吧所有的container都放在一个列表里,一直找这个列表,我们每找一次, 那些生命周期很长的object都会被检查一次 而判断循环引用是一件耗时非常长的事情,当我们的程序运行时间越来越长,这些生命周期很长的对象可能越来越多,就导致我们每一次垃圾回收都很慢。 因为对一些我们已经检查了好几次,知道他已经存活了很久的object, 还在不停的检查他。
在当前版本的generations总 第一代为700 二代为10 三代为10
但是还是会有一个问题,当程序运行时间很长的时候, 第三代的对象数量也可能越来越多,每一次垃圾回收的时间也越来越长,而且第三代中的对象基本上都是被检查过的对象,就是长期使用的,都是大概率不需要被回收, 这个结果依然不是很理想 所以cpython还引入了一个优化机制,对于第三代,只有在没检查过的对象数量, 占总对象数量的25%以上的时候,才开启这个第三代垃圾回收。
-
标记清除 分为两个阶段:
1标记阶段 (解决循环引用的问题)在标记阶段,Python从根对象开始遍历整个对象图,标记所有可达对象。根对象包括全局变量、函数调用栈、当前线程的本地变量等,它们是程序中所有可达对象的起点。Python通过递归遍历对象图来标记所有可达对象。
2清除阶段 在清除阶段,Python遍历整个堆内存,将未被标记的对象视为垃圾对象并将其释放。同时,Python会将所有已经使用的内存空间合并成连续的块,以便更好地管理和分配内存。 需要注意的是,在标记阶段期间,Python会停止程序的正常执行,直到标记完成。这可能会对程序的性能和响应性产生一定的影响。因此,Python还提供了其他垃圾回收机制,如增量垃圾回收(incremental garbage collection)和分代垃圾回收(generational garbage collection),以提高垃圾回收效率和程序性能。
-
分代回收 分为3个连表 0代: 当对象数量达到700扫描一次 1代: 当0代扫描10次, 1代扫描一次 2代: 当1代扫描10次, 2代扫描一次 当我们创建对象的时候, 他会加到0代里面去, 当0代对象数量达到700会扫描一次, 是垃圾则清除, 存活下来的就加入到1代中.
2. 上下文管理
2.1最简单的上下文管理器
这里有一个固定的知识点,一个类,实现了__enter__和__exit__方法,这个类的实例就是上下文管理器。
class CTManager:
def __enter__(self):
print('__enter__被执行')
def __exit__(self, exc_type, exc_val, exc_tb):
print('__exit__被执行')
with CTManager() as ct:
print('显示在上下文管理器中间')
#执行结果:
#__enter__被执行
#显示在上下文管理器中间
#__exit__被执行
除了自定义上下文管理器外,Python还提供了一些内置的上下文管理器,如open()函数、threading.Lock()等。这些内置的上下文管理器可以方便地管理文件、线程等资源。
总之,上下文管理是一种常见的用于管理资源的模式。它可以确保资源在不再需要时正确关闭,并可以提供更简单的代码。上下文管理器是一个实现了__enter__()和__exit__()方法的对象,可以通过with语句来使用。
2.2 contextmanager实现上下文管理器
Python还提供了一个contextmanager的装饰器,更进一步简化了上下文管理器的实现方法。通过yield将函数分割成两部分,yield之前的部分相当于在__enter__方法中执行,yield之后的部分相当于在__exit__方法中执行,紧跟在yield之后的返回值给as之后的变量。
from contextlib import contextmanager
class File:
def __init__(self, filename, mode):
self.f = open(filename, mode)
def reads(self):
return self.f.read()
def closed(self):
self.f.close()
@contextmanager
def read_file():
print(1)
rf = File("README.md", "rb")
yield rf
print(3)
rf.closed()
with read_file() as f:
print(2)
f.reads()
3. Classmethod与Staticmethod
在Python中,@classmethod和@staticmethod是两种装饰器,用于定义类方法和静态方法。它们有以下区别:
@classmethod装饰器用于定义类方法,而@staticmethod装饰器用于定义静态方法。
类方法(classmethod)是与类相关联的方法,可以通过类或实例进行调用。类方法的第一个参数通常命名为"cls",表示类本身。类方法可以访问和修改类属性,也可以在类范围内执行其他操作。使用类方法时,不需要创建类的实例。
静态方法(staticmethod)是与类无关的方法,可以通过类或实例进行调用。静态方法与类的状态无关,不可以访问或修改类属性。静态方法通常用于执行与类相关但不依赖于类状态的操作,例如辅助函数。
下面是一个示例来说明@classmethod和@staticmethod的区别:
class MyClass:
class_attr = "Class Attribute"
def __init__(self, instance_attr):
self.instance_attr = instance_attr
@classmethod
def class_method(cls):
print("This is a class method.")
print(cls.class_attr)
@staticmethod
def static_method():
print("This is a static method.")
# 调用类方法,不需要创建实例
MyClass.class_method()
# 调用静态方法,不需要创建实例
MyClass.static_method()
# 创建实例
my_obj = MyClass("Instance Attribute")
# 通过实例调用
[Something went wrong, please try again later.]
4. 生成器和迭代器, 可迭代对象
在Python中,生成器(Generator)和迭代器(Iterator)是两个相关且常用的概念。
生成器是一种特殊的函数,使用yield语句来定义。它可以通过迭代的方式按需生成值,而不需要一次性生成所有值并存储在内存中。生成器能够节省内存,并且在处理大量数据时非常高效。生成器可以通过迭代器进行访问。
迭代器是一个实现了迭代协议的对象。迭代协议要求对象提供一个__iter__()方法,该方法返回一个迭代器对象。迭代器对象必须实现__next__()方法,用于返回序列中的下一个元素。迭代器可以逐个地遍历数据集合,而不需要提前将整个序列加载到内存中。
可迭代对象(Iterable)是指实现了__iter__()方法的对象,或者实现了__getitem__()方法的对象。可迭代对象可以用于创建迭代器,并支持使用for循环进行遍历操作。常见的可迭代对象包括列表、元组、字典、集合等。
生成器通常用于惰性计算、处理大数据集或无限序列等场景。通过生成器,我们可以逐个地获取数据,而不必事先将所有数据加载到内存中。这对于处理大型数据集或需要逐步计算的情况非常有用。
下面是一个使用生成器的简单示例:
python
def my_generator():
for i in range(5):
yield i
# 使用生成器
gen = my_generator()
for item in gen:
print(item)
在这个示例中,my_generator()函数是一个生成器函数,通过yield语句逐个生成数字。我们可以通过迭代器对象gen来访问生成器的值,而不需要一次性生成所有数字。
总结起来,生成器是特殊的函数,可以按需生成值,节省内存并高效处理大量数据。迭代器是实现了迭代协议的对象,用于逐个遍历序列。可迭代对象是实现了__iter__()方法的对象,可以用于创建迭代器。
5. deepcopy()深拷贝和copy()浅拷贝

多个可变对象, 嵌套的情况下 1. copy()时只会拷贝最外层, 内层的数据其实还是指向同一个内存地址, 所以当改变内层数据的时候, 会导致多个对象数据一起改变. 2. deepcopy()会把所有层数的数据全部拷贝, 全部数据开辟一个新的内存空间, 被拷贝对象与拷贝对象的数据互不干扰.
6. python的基本数据结构
列表(List):列表是Python中最常用的数据结构之一,它是一个有序、可变的集合。列表可以包含不同类型的元素,并且可以通过索引访问和修改元素。列表使用方括号([])来表示,可以使用多种方法对列表进行操作,如添加、删除、切片等。
元组(Tuple):元组与列表类似,也是一个有序的集合,但是元组是不可变的,即不能修改其元素。元组使用圆括号(())来表示,通常用于存储不可变的数据。
字典(Dictionary):字典是Python中的一种键值对(key-value)映射的数据结构。字典是无序的,并且可以动态地添加、删除和修改键值对。字典使用花括号({})来表示,每个键值对由冒号(:)分隔。
集合(Set):集合是一个无序、不重复的集合。集合提供了一些常见的集合操作,如并集、交集、差集等。集合使用花括号({})或set()函数来表示。
字符串(String):字符串是一串字符的序列,使用引号(单引号或双引号)来定义。字符串是不可变的,即不能修改其中的字符,但可以通过切片等方式进行操作。
除了这些基本数据结构外,Python还提供了其他一些高级的数据结构,如堆(heapq)、队列(Queue)、栈(Stack)等,这些数据结构通常需要通过导入相应的模块来使用。
总而言之,Python中的基本数据结构包括列表、元组、字典、集合和字符串。这些数据结构可以满足大部分常见的数据存储和处理需求。
set() # 是可以变集合, 只能存储不可变对象(字符串, 数字...)
a = [1, 2, 3]
set(a)
-> {1, 2, 3}
frozenset() # 是不可变集合
a = [1, 2, 3]
frozenset(a)
-> {1, 2, 3}
-
字典(可变对象) 无序的对象集合
-
布尔(不可变) False, True
7. filter、map、reduce的作用?
- filter()过滤可迭代对象
filter(lamda x: x>0, [-1, 0, 1, 2, 3])
-> [1, 2, 3]
- map()
生成器 = map(函数, 可迭代对象)
需要使用next(生成器)才能获取值
a = map(lamda x: x*x, [1, 2, 3])
print(next(a))
或者进行迭代循环
for i in a:
...
- reduce()方法, python3放入到了functools中了
是将一个函数作用在一个序列上,把结果继续和序列的写一个元素作为函数的参数做累积计算,返回累计结果
reduce(lamda x, y:x*y, [1, 2, 3])
相当于
先计算1 * 2 = 2
再通过第一次计算的结果2 * 3 = 6
这样不断的累积计算, 返回结果
8. 虚拟机和字节码
1. 所有的python代码都会被complie成一个 code object
2. python的解释器, 在运行python代码之前会建立一个frame他提供了一个程序运行的环境, 局部变量, 全局变量, 包含这段代码对应的bytecode, 还有上一个运行的bytecode。
3. 除了最开始运行python代码建立的frame之外,再之后每一次调用一次函数, 都会建立一个frame, 那你可以理解为每一个bytecode都是在一个frame里面执行的, 然后每一个frame处理最外面那个之外, 都是一个function call都是一个函数的调用。
4. 我们可以使用dis这个模块对程序运行的字节码进行打印
总结:
当你每一次调用函数或者开始运行Python的时候
会建立一个新的frame, 然后在这个frame这个环境下
会一条一条的运行bytecode, 每一条bytecode在cpython里面都有相应的代码去执行他
在每一个frame里python会维护一个栈,然后bytecode会根这个栈进行交互
当然也会和这个code object里保存的乱七八糟的东西进行交互, 接着就是进行计算, 返回结果继续。。。
9. code object
def f():
pass
# code object
print(f.__code__)
# 查看这个obj里面的所有属性
dir(f.__code__)
# 理解为真正的bytecode二进制字节玛
code.co_code
10. 多线程是什么
是计算机cpu调度的最小单位, 可以简单的理解为我们的程序都是运行在线程里面的, 每个线程都有属于自己的上下文。
多线程出现之初, 是在不现实 的切换任务情况下, 让cpu可以同时处理多个任务。
1. io瓶颈任务, 文件读取, 网络传输
通过多线程可以把这段闲着的时间利用起来。
当然对于io瓶颈的任务, 我们现在经常可以使用携程asyncio来完成, 携程相对多线程更轻, 而且没有竞争冒险的问题,所以在网络传输的领域用的越来越多了。
但是携程有一个问题, 必须要显示的放权, 就是必须通过await显示的把控制器交还给计算器大脑。
在一个线程情况下使用携程:
假如说有一个大量计算的问题, 而且不着急可以慢慢做, 但是又有很多其他小任务, 要在低延迟的情况下解决,
在这样的情况下如何解决这个任务的调度, 如果不做任何的处理, 就会陷入到大量计算任务中出不来, 可是如果你在计算任务中, 人工安置一些放权点,会让你这个任务看起来很奇怪, 而且需要你对这个任务足够了解。
而且当你调用的是别人的函数, 就没有办法在别人的函数里面放权的。
这种情况下使用多线程就可以方便的解决这种情况, 当然了在asyncio里面使用run_in_executor来解决这个问题,
这个函数本质上也是使用多线程或者多进程来解决这个问题的。
11. 多进程是什么
进程比线程根大
每个进程有自己的内存阿之类的, 一个进程可以有好几个线程, 这些线程会共享这个进程所有的内存, 可以理解为都可以读写同样 的变量。
在c 和 c++里面我们需要显示的释放内存的, 当你光分配不释放, 内存就越来越多, 就会爆炸。
而在python里面, 我们并不需要显示的释放内存, 所以的obj, dict , list属性, 拿来直接用就可以了。我们不用做这些烦心事
是因为在Python的解释器memory management帮我们做了这件事,python是如何做到自动分配, 释放内存的呢
尤其是释放内存的, python使用的机制是 引用技术reference count
就是每一个python的obj都数着有多少 个地方用到了自己, 就把自己的引用计数+1, 如果这个地方不用了,或者没有了 引用计数就-1, 这样只要你数数数的是对的
那么我们就可以知道, 什么时候引用计数到0了, 没有人用到为了, 就把这块内存释放掉。
问题1: 竞争冒险的问题
1. 加锁GIL锁
2. 他的作用就是拿到一个全局锁, 他会保证当前线程会拿到这个GIL锁, 通过这种机制, 每一个bytecode在运行的时候, 都是拿到这个线程锁的,换言之没有bytecode可以被其他的线程打断,这样的话每个bytecode在c里面运行的程序都是线程安全的
很多人诟病说python这个GIL是阻止python前进的一个至顾,但是他最开始这样设计,肯定是有他的初衷的
2. 1 第一就是他是一个非常简单的设计
2.2 由于只有一个线程锁, 他避免了死锁的问题,当一个线程里存在两个以上的锁的时候, 就有可能造成死锁的问题,对与单线程的程序, 或者没办法并行的多线程程序,
全局锁的性能是非常优秀的。
2.3 但是由于GIL锁的机制, 就出现了不管你有多少个线程, 实际上运行python代码的只有一条线程,这就导致了, python的多线程没有办法利用多核来优化自己的代码
最简单的就是用多进程来解决这个问题, 虽然一个进程没有办法利用多个cpu, 但是可以有多个进程, 或者可以使用一些没有GIL的一些解释器
像是Jython, IronPython都是没有GIL的
但是PyPy是有的。
12. python解释器, cpython
一.官方解释器默认cpython
1. 速度: 由于这个传统虚拟机的架构
2.GIL: 全局解释器锁, 导致多线程是一个残废的多线程
二.pypy解释器
速度是cpython的4.5倍
由于这些解释器都是先等cpython先实现出来了, 再根据cpython的工作原理, 来现实其他的解释权, 所以就很难跟上cpython的步伐, 是个不公平的竞争。
而且很多很优秀的库, 而这些库都是先适配cpython的, 假如说, 虚拟环境...
jpython
使得python与java之间的交互变得简单
micropython
单片机上被经常用到,只实现了python的核心功能, 做的很小。
13. 常见的字符集
utf-8, utf8mb4, gbk, ascii
14. 阶乘
func(n):
if n == 1:
return n
return n*func(n-1)
15. 装饰器是什么
在Python中,装饰器是一种特殊的函数,它可以修改其他函数的行为。实际上,装饰器是一个高阶函数,它接受一个函数作为参数,并返回一个新的函数。
使用装饰器,可以在不修改原有函数代码的情况下,给函数增加额外的功能。常见的应用包括:
记录函数执行时间、日志和调试信息 验证用户权限和登录状态 缓存函数结果,避免重复计算 对函数参数和返回值进行类型检查和转换 增加函数的安全性,如防止SQL注入和跨站脚本攻击等 Python中的装饰器通过 @ 符号来表示。定义装饰器时,需要在装饰器函数前面添加 @ 符号,并在函数内部定义一个接受另一个函数作为参数的函数,并返回一个新的函数。例如:
def my_decorator(func):
def wrapper():
print("Before function is called.")
func()
print("After function is called.")
return wrapper
@my_decorator
def say_hello():
print("Hello, World!")
在上述例子中,我们定义了一个名为 my_decorator 的装饰器函数,并在 say_hello 函数前面添加 @my_decorator 符号。在 my_decorator 函数内部,我们定义了一个名为 wrapper 的闭包函数,该函数先于原有函数执行一些操作,然后再执行原有函数,最后再执行一些其他操作。通过将原有函数 say_hello 作为参数传递给 my_decorator 函数,并将返回的 wrapper 函数赋值给 say_hello 函数,我们实现了对原有函数的装饰。
使用装饰器时,可以在函数定义前面添加多个装饰器,它们会按照从下到上的顺序依次执行。例如:
@decorator_one
@decorator_two
def my_function():
# Function body here
在上述例子中,my_function 函数先被 decorator_two 装饰器修饰,然后再被 decorator_one 装饰器修饰。
总之,装饰器是Python中非常有用和强大的特性,可以帮助我们实现代码重用、减少代码冗余、提高代码可读性和维护性等目标。掌握装饰器的使用方法和技巧,可以让我们在编写Python代码时更加高效和灵活。
16. 解释Python中的异常处理机制及其语法。
在Python中,异常处理机制用于捕获和处理程序运行过程中出现的错误和异常情况,以便优雅地处理这些问题,避免程序崩溃或产生不可预测的结果。
异常处理的语法结构主要包含以下几个关键字:
try: 用于定义一段可能会出现异常的代码块。 except: 用于捕获指定类型的异常,并定义对异常的处理逻辑。 else: 当try代码块中没有发生异常时,执行else代码块中的语句。 finally: 不论是否发生异常,都会执行finally代码块中的语句。 下面是一个异常处理的基本语法示例:
try:
# 可能会出现异常的代码块
# ...
except ExceptionType1:
# 处理 ExceptionType1 类型的异常逻辑
# ...
except ExceptionType2:
# 处理 ExceptionType2 类型的异常逻辑
# ...
else:
# 没有发生异常时执行的逻辑
# ...
finally:
# 不论是否发生异常都会执行的逻辑
# ...
在上述示例中,程序首先执行 try 代码块中的内容。如果在执行过程中出现了指定类型的异常,则会跳转到与该异常类型匹配的 except 代码块,执行相应的异常处理逻辑。可以根据需要添加多个 except 代码块来处理不同类型的异常。
如果在 try 代码块中没有发生任何异常,那么会执行 else 代码块中的语句。else 代码块是可选的,用于处理在没有异常发生时需要执行的逻辑。
无论是否发生异常,finally 代码块中的语句始终都会被执行。通常,finally 代码块用于释放资源或进行一些清理操作。
除了使用 except 捕获特定类型的异常外,还可以使用以下方式捕获所有异常:
try:
# 可能会出现异常的代码块
# ...
except:
# 处理所有异常的逻辑
# ...
在这种情况下,except 代码块中的逻辑将捕获所有类型的异常,但不推荐这样做,因为无法区分不同类型的异常,可能会隐藏真正的问题。
在处理异常时,还可以使用关键字 raise 来手动引发异常,以便在特定条件下触发异常并进行相应处理。
如何使用微服务的原理。
微服务是一种软件架构模式,它将一个应用程序拆分为一组小型、独立部署的服务,每个服务都专注于实现特定的业务功能。这些微服务可以通过网络互相通信,并且可以使用不同的编程语言、存储技术和数据处理工具。
通常情况下,每个微服务都有自己的数据库,并且可以独立部署、升级和扩展。微服务架构的优势包括灵活性、可伸缩性和容错性,因为每个微服务都可以独立地开发和部署,而不会影响整个应用程序的其他部分。
微服务架构通常与容器化技术(如Docker)和容器编排工具(如Kubernetes)结合使用,以便更好地管理和部署大量的微服务。这种架构模式在构建大型、复杂的分布式系统时非常流行,因为它有助于降低应用程序的耦合度,并且能够更好地应对快速变化和需求的增长。
async 使用await怎么能让重新一直不停运行
import asyncio
async def my_coroutine():
while True:
print("Async task running...")
await asyncio.sleep(1) # 等待1秒
# 创建事件循环
loop = asyncio.get_event_loop()
# 将异步任务加入事件循环
loop.create_task(my_coroutine())
# 运行事件循环,直到手动取消
try:
loop.run_forever()
except KeyboardInterrupt:
pass
finally:
loop.close()
总结起来,异常处理机制使我们能够通过捕获和处理异常,确保程序的稳定性和可靠性。合理运用异常处理,可以优雅地应对可能出现的错误和异常情况,并提供相应的处理逻辑。