zl程序教程

您现在的位置是:首页 >  Python

当前栏目

python核心知识汇总(精编版)

2023-02-18 16:23:10 时间

阿巩:

坚决不咕咕咕!!

你好啊,我是阿巩。转眼已连续更新一周了,可咱毕竟是讲Python的公众号,不来点Python基础干货就有些说不过去,就像茶馆里没有茶、犬舍里没有狗子、老婆饼里没有老婆(都什么乱七八糟的比喻?!)之前有写过篇万字长文,今天来根据面试常问的内容整理下,做个精编版。日拱一卒,让我们开始吧!

Python3标准数据类型:

  • 数字
  • 字符串
  • 列表
  • 元组
  • 集合
  • 字典

其中不可变类型:Number(数字)String(字符串)、Tuple(元组);

可变类型:List(列表)、Dictionary(字典)、Set(集合)。

可变/不可变对象

不可变对象,该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。

可变对象,该对象所指向的内存中的值可以被改变。当引用改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的出地址,即原地改变。

字符串

Python的字符串支持索引、切片和遍历等操作。

Python的字符串不可变,要想改变,只能通过创建新的字符串完成。

实现拼接字符串用str1+= str2即可。

常用函数:

  • string.split(separator),把字符串按照 separator 分割成子字符串,并返回一个分割后子字符串组合的列表;
  • string.strip(str),去掉首尾的 str 字符串;
  • string.lstrip(str),只去掉开头的 str 字符串;
  • string.rstrip(str),只去掉尾部的 str 字符串。

列表和元组

列表和元组,都是一个可以放置任意数据类型的有序集合。其中列表是动态的,长度大小不固定,可以随意地增加、删减或者改变元素;而元组是静态的,长度大小固定,无法增加删减或者改变。

list和tuple的内部实现都是array的形式,list因为可变,所以是一个over-allocate的array,tuple因为不可变,所以长度大小固定。

  • Python 中的列表和元组都支持负数索引,-1 表示最后一个元素,-2 表示倒数第二个元素,以此类推。
l = [1, 2, 3, 4]
l[-1]
4

tup = (1, 2, 3, 4)
tup[-1]
4
  • 列表和元组都支持切片操作
l = [1, 2, 3, 4]
l[1:3] # 返回列表中索引从1到2的子列表
[2, 3]

tup = (1, 2, 3, 4)
tup[1:3] # 返回元组中索引从1到2的子元组
(2, 3)
  • 列表和元组都可以随意嵌套
l = [[1, 2, 3], [4, 5]] # 列表的每一个元素也是一个列表

tup = ((1, 2, 3), (4, 5, 6)) # 元组的每一个元素也是一个元组
  • 两者也可以通过 list() 和 tuple() 函数相互转换
list((1, 2, 3))
[1, 2, 3]

tuple([1, 2, 3])
(1, 2, 3)

列表和元组常用的内置函数:

  • count(item) 表示统计列表 / 元组中 item 出现的次数。
  • index(item) 表示返回列表 / 元组中 item 第一次出现的索引。
  • list.reverse() 和 list.sort() 分别表示原地倒转列表和排序(注意,元组没有内置的这两个函数)。
  • reversed() 和 sorted() 同样表示对列表 / 元组进行倒转和排序,reversed() 返回一个倒转后的迭代器;sorted() 返回排好序的新列表。

字典和集合

字典是一系列由键(key)和值(value)配对组成的元素的集合。相比于列表和元组,字典的性能更优,特别是对于查找、添加和删除操作,字典都能在O(1)时间复杂度内完成。字典和集合的内部结构都是一张哈希表。

  • 创建:无论是键还是值,都可以是混合类型。
  • 查询:字典可以直接索引键,也可以使用 get(key, default) 函数来进行索引;集合并不支持索引操作,因为集合本质上是一个哈希表,和列表不一样。要判断一个元素在不在字典或集合内,可以用 value in dict/set 来判断。
  • 更新:字典增加、更新时指定键和对应的值对即可,删除可用pop() 操作;集合增加可用add()函数,删除可用remove()函数。
  • 排序:字典可使用函数sorted()并且指定键或值,进行升序或降序排序;集合排序直接调用 sorted(set) 即可。

合并字典

# 两个字典
dict1 = {'a': 10, 'b': 8}
dict2 = {'d': 6, 'c': 4}
# 方法一:
dict1.update(dict2)
print(dict1)
# 方法二:
dic = {**dict1, **dict2}
print(dic)
# 输出:{'a': 10, 'b': 8, 'd': 6, 'c': 4}

遍历字典

d = {'name': 'jason', 'dob': '2000-01-01', 'gender': 'male'}
for k in d: # 遍历字典的键
    print(k)
# name
# dob
# gender

for v in d.values(): # 遍历字典的值
    print(v)
# jason
# 2000-01-01
# male    

for k, v in d.items(): # 遍历字典的键值对
    print('key: {}, value: {}'.format(k, v))
# key: name, value: jason
# key: dob, value: 2000-01-01
# key: gender, value: male

按键/值对字典排序

d1 = {3: 'three', 1: 'one', 2: 'two'}
# 按键排序
print(sorted(d1.items(), key=lambda k: k[0]))
# 输出:[(1, 'one'), (2, 'two'), (3, 'three')]

d2 = {'three': 3, 'one': 1, 'two': 2}
# 按值排序
print(sorted(d2.items(), key=lambda k: k[1]))
# 输出:[('one', 1), ('two', 2), ('three', 3)]

有序字典

collections模块的OrderedDict,所谓有序而非字典序,指的是元素的插入顺序。


Python3 数据结构

单链表节点

class SingleNode(object):
    """单链表的结点"""
    def __init__(self, item):
        #  item存放数据元素
        self.item = item
        #  next是下一个节点的标识
        self.next = None

双向链表节点

class Node(object):
    """双向链表节点"""
    def __init__(self, item):
        self.item = item
        self.next = None
        self.prev = None

将列表当作栈:列表的append()和pop()方法

>>> stack = [3, 4, 5]
>>> stack.append(6)
>>> stack.append(7)
>>> stack
[3, 4, 5, 6, 7]
>>> stack.pop()
7
>>> stack
[3, 4, 5, 6]

将列表当作队列:deque的append()和popleft()方法

>>> from collections import deque
>>> queue = deque(["Eric", "John", "Michael"])
>>> queue.append("Terry")           
>>> queue.append("Graham")          
>>> queue.popleft()                 
'Eric'
>>> queue.popleft()                
'John'
>>> queue                           
deque(['Michael', 'Terry', 'Graham'])

列表推导式

提供了一种简便方法创建列表。

[i for i in range(10) if i % 2 == 0]
s = [x*y for x in range(1, 5) if x > 2 for y in range(1, 4) if y < 3]
print(s)
# 输出 [3, 6, 4, 8]
# 等同于:
for x in range(1, 5):
    if x > 2:
        for y in range(1, 4):
            if y < 3:
                x*y

输入和输出

Python3中input()函数接受任意标准输入数据,返回String类型。

格式化输出可使用f‘{}’来代替.format()

a = 1
b = 2
s = a + b
print(f'Sum of a and b is {s}')
# 输出:Sum of a and b is 3

文件输入和输出

  1. 用 open() 函数拿到文件的指针,其中第一个参数指定文件位置;第二个参数,如果是 'r'表示读取,如果是'w' 则表示写入,当然也可以用 'rw' ,表示读写都要。'a' 表示追加(append)即从原始文件的最末尾开始写入。
  2. 拿到指针后,通过 read() 函数,来读取文件的全部内容。
  3. 给 read 指定参数 size ,表示读取的最大长度。还可以通过 readline() 函数,每次读取一行,如果每行之间没有关联,这种做法也可以降低内存的压力。
  4. write() 函数,可以把参数中的字符串输出到文件中。

注意所有 I/O 都应该进行错误处理。

with open

with语句相当于在open加上try-except-finally,用with语句的好处就是到达语句末尾时会自动关闭文件。with语句实际上是一个非常通用的结构,允许你使用所谓的上下文管理器。上下文管理器是支持两个方法的对象:__enter__和 __exit__。

JSON序列化

JSON是一种轻量级的数据交换格式,它的设计意图是把所有事情都用设计的字符串来表示。实际应用中遇到多种数据类型混在一起的情况可使用JSON序列化处理:

  • json.dumps() 函数,接受 Python 的基本数据类型,然后将其序列化为 string。
  • json.loads() 函数,接受一个合法字符串,然后将其反序列化为 Python 的基本数据类型。

当开发一个第三方应用程序时,可以通过 JSON 将用户的个人配置输出到文件,方便下次程序启动时自动读取。这也是现在普遍运用的成熟做法。


条件与循环

Python 不支持 switch 语句,因此,当存在多个条件判断时,我们需要用elif实现。

for循环

Python 中的数据结构只要是可迭代的比如列表、集合等等,那么都可以通过下面这种方式遍历:

for item in <iterable>:
    ...

使用range() 函数,拿到索引,再去遍历访问集合中的元素。

l = [1, 2, 3, 4, 5, 6, 7]
for index in range(0, len(l)):
    if index < 5:
        print(l[index])        

当同时需要索引和元素时,使用Python 内置的函数 enumerate()。

l = [1, 2, 3, 4, 5, 6, 7]
for index, item in enumerate(l):
    if index < 5:
        print(item)  

while循环

l = [1, 2, 3, 4]
index = 0
while index < len(l):
    print(l[index])
    index += 1

异常处理

try:后放会出现异常的代码;except后方要捕获的异常,捕获所有异常用Exception;as后为别名;finally后为无论如何都会执行的代码。

def fun():
    try:
        print('try--start')
        a = 1/0
    except ValueError as ret:
        print(ret)
    finally:
        return 'finally'
print(fun())
# 输出:
try--start
finally

捕获多个异常

def model_exception(x, y):
    try:
        a = x / y
    except(ZeroDivisionError, NameError, TypeError):
        print('one of ZeroDivisionError or NameError or TypeError happend')
# 调用函数结果
model_exception(2, 0)

函数

def name(param1, param2, ..., paramN):
    statements
    return/yield value # optional

python支持函数嵌套

def connect_DB():
    def get_DB_configuration():
        ...
        return host, username, password
    conn = connector.connect(get_DB_configuration())
    return conn

我们只能通过调用外部函数 connect_DB() 来访问get_DB_configuration()

闭包

和嵌套函数类似,不同在于外部函数返回的是一个函数,而不是一个具体的值。返回的函数通常赋予一个变量,这个变量可以在后面被继续执行调用。

不定长参数

当参数个数不确定时使用不定长参数,有两种类型分别为*args和**kwargs。

加了*的参数会以元组tuple的形式导入,而**的参数以字典形式导入。

匿名函数

python使用lambda来创建匿名函数。

sum = lambda arg1, arg2: arg1 + arg2

函数式编程

map() 、filter()、reduce()

map()函数:对iterable中的每个对象都运行func函数,最后返回一个新的可遍历的集合。

l = [1, 2, 3, 4, 5]
new_list = map(lambda x: x * 2, l) # [2, 4, 6, 8, 10]

filter()函数:对 iterable 中的每个元素,都使用 func 判断并返回 True 或者 False,最后将返回 True 的元素组成一个新的可遍历的集合。

l = [1, 2, 3, 4, 5]
new_list = filter(lambda x: x % 2 == 0, l) # [2, 4]

reduce()函数:它通常用来对一个集合做一些累积操作。比如要计算某个列表元素的乘积。

l = [1, 2, 3, 4, 5]
product = reduce(lambda x, y: x * y, l) # 1*2*3*4*5 = 120

迭代器和生成器

在 Python 中一切皆对象,对象的抽象就是类,而对象的集合就是容器。列表(list: [0, 1, 2]),元组(tuple: (0, 1, 2)),字典(dict: {0:0, 1:1, 2:2}),集合(set: set([0, 1, 2]))都是容器。对于容器,你可以很直观地想象成多个元素在一起的单元;而不同容器的区别,正是在于内部数据结构的实现方法。

容器是可迭代对象,可迭代对象调用 iter() 函数,可以得到一个迭代器。迭代器可以通过 next() 函数来得到下一个元素,从而支持遍历。调用next()方法后,你要么得到这个容器的下一个对象,要么得到一个 StopIteration 的错误。

class MyNumbers:
    def __iter__(self):
        self.a = 1
        return self

    def __next__(self):
        x = self.a
        self.a += 1
        return x  

myclass = MyNumbers()
myiter = iter(myclass)

生成器是一种特殊的迭代器,它包含yield。每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行(yield就有点像断点)。

可以使用yield来读取文件,如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用,而yield利用固定长度的缓冲区来不断读取文件内容。

def read_file(fpath): 
    BLOCK_SIZE = 1024 
    with open(fpath, 'rb') as f: 
        while True: 
            block = f.read(BLOCK_SIZE) 
            if block: 
                yield block 
            else: 
                return

python中的参数传递方式

引用传递,不是指向一个具体的内存地址,而是指向一个具体的对象。

  • 如果对象是可变的,当其改变时,所有指向这个对象的变量都会改变。
  • 如果对象不可变,简单的赋值只能改变其中一个变量的值,其余变量则不受影响。

通过一个函数来改变某个变量的值,通常有两种方法:第一种直接将可变数据类型(比如列表,字典,集合)当作参数传入,直接在其上修改;第二种是创建一个新变量,来保存修改后的值,然后将其返回给原变量。在实际工作中,我们更倾向于使用后者,因为其表达清晰明了,不易出错。

python变量及其赋值

  • 变量的赋值,只是表示让变量指向了某个对象,并不表示拷贝对象给变量;而一个对象,可以被多个变量所指向。
  • 可变对象(列表,字典,集合等等)的改变,会影响所有指向该对象的变量。
  • 对于不可变对象(字符串、整型、元组等等),所有指向该对象的变量的值总是一样的,也不会改变。但是通过某些操作(+= 等等)更新不可变对象的值时,会返回一个新的对象。
  • 变量可以被删除,但是对象无法被删除。

面向对象编程

四要素:封装、继承、多态、抽象。

  • 封装就是把功能封装抽象的方法和其他属性和方法,使得代码更加模块化,代码复用度更高;
  • 继承使得子类不仅拥有自己的属性和方法,还能使用父类的属性和方法;
  • 多态可以实现函数重写,使得相同方法具有不同功能。
  • 抽象不同子类的相同方法和属性形成父类,在通过继承,多态,封装使得代码更加紧凑,简洁易读

封装是基础。抽象和多态依赖于继承实现。

构造函数:用def __init__(self, args...)声明,第一个参数self代表当前对象的引用,其他参数是在对象化时需要传入的属性值;构造函数在一个对象生成时(即实例化时)会被自动调用。

__init__用来初始化,__new__用来生成一个实例。

成员函数:是正常的类的函数,第一个参数必须是self;可通过此函数来实现查询或修改类的属性等功能。

静态函数:属于当前类的命名空间下,且对第一个参数没有要求;一般用来做一些简单独立的任务,既方便测试也能优化代码结构;一般使用装饰器@staticmethod来声明。

类函数:类函数的第一个参数一般为cls,表示必须传一个类进来;最常用的功能是实现不同的init构造函数;需要装饰器@classmethod来声明。

元类:MetaClass是创建类的类,元类允许我们控制类的生成,比如修改类的属性等。

元类最常见的场景是ORM中。

装饰器

在不修改原函数的情况下,为函数增加功能。

编写一个记录耗时操作的装饰器:

# 用函数编写一个装饰器
import time
def log_time(func):  # 接收一个函数作为参数
def _log(*args, **kwargs):
        beg = time.time()
        res = func(*args, **kwargs)
        print(f'use time: {time.time() - beg}')
        return res
    return _log
@log_time  # @ 为装饰器的语法糖
def mysleep():
    time.sleep(1)
# mysleep()
# 等价于:
newsleep = log_time(mysleep)
# newsleep()
# 用类编写一个装饰器
class LogTime:
def __call__(self, func):
def _log(*args, **kwargs):
            beg = time.time()
            res = func(*args, **kwargs)
            print(f'use time: {time.time() - beg}')
            return res
return _log
@LogTime()  # 初始化装饰器类的实例,所以此处添加‘()’
def mysleep2():
    time.sleep(1)
# mysleep2()
# 给装饰器增加参数
# 使用类装饰器方便实现装饰器参数
class LogTimeParams:
def __init__(self, use_int=False):
        self.use_int = use_int
def __call__(self, func):
def _log(*args, **kwargs):
            beg = time.time()
            res = func(*args, **kwargs)
if self.use_int:
                print(f'use time: {int(time.time()-beg)}')
else:
                print(f'use time: {time.time()-beg}')
return res
        return _log
        
@LogTimeParams(True)
def mysleep():
    time.sleep(1)
mysleep()

类装饰器主要依赖于函数__call__(),每当调用一个类的示例时,函数__call__()就会被执行一次。

LRU cache缓存装饰器,在 Python 中的表示形式是@lru_cache。@lru_cache会缓存进程中的函数参数和结果,当缓存满了以后,会删除 least recenly used 的数据

编写一个用于身份认证的装饰器:

import functools
def authenticate(func):
    @functools.wraps(func)
def wrapper(*args, **kwargs):
        request = args[0]
if check_user_logged_in(request): # 如果用户处于登录状态
return func(*args, **kwargs) # 执行函数post_comment() 
else:
            raise Exception('Authentication failed')
return wrapper
@authenticate
def post_comment(request, ...)
    ...

设计模式

工厂模式:

"""
什么是工厂模式?
    解决对象创建问题
    解耦对象的创建和使用
    包括工厂方法和抽象工厂
"""
# 工厂模式例子
class DogToy:
    def speak(self):
        print('wang wang')

class CatToy:
    def speak(self):
        print('miao miao')

def toy_factory(toy_type):
    if toy_type == 'dog':
        return DogToy()
    if toy_type == 'cat':
        return CatToy()

单例模式:

"""
什么是单例模式?
    一个类只能创建同一个实例,无论创建多少个实例,都是同一个对象
    Python的模块其实就是单例的,只会import一次,在模块中定义的全局变量就是单例的
    使用共享同一个实例的方式创建单例模式    
"""
class Singleton:
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            _instance = super().__new__(cls, *args, **kwargs)
            cls._instance = _instance
        return cls._instance

class Myclass(Singleton):
    pass

c1 = Myclass()
c2 = Myclass()
assert c1 is c2  # c1和c2是同一个实例

Python对象的比较和拷贝

'==' 与 'is'

'=='操作符比较对象之间的值是否相等。执行a == b相当于是去执行a.__eq__(b),而 Python 大部分的数据类型都会去重载__eq__这个函数,其内部的处理通常会复杂一些。

比较操作符'is'效率优于'==',因为'is'操作符无法被重载,执行'is'操作只是简单的获取对象的 ID,并进行比较;而'=='操作符则会递归地遍历对象的所有值,并逐一比较。

浅拷贝和深拷贝

浅拷贝,是指重新分配一块内存,创建新对象,其内容非原对象本身的引用,而是原对象内第一层对象的引用。浅拷贝有三种形式:切片操作、工厂函数、copy 模块中的 copy 函数。

深拷贝,是指重新分配一块内存,创建一个新的对象,并且将原对象中的元素,以递归的方式,通过创建新的子对象拷贝到新对象中。深拷贝只有一种形式,copy 模块中的 deepcopy()函数。深拷贝和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。因此,它的时间和空间开销要高。

对于元组,使用 tuple() 或者切片操作符':'不会创建一份浅拷贝,相反,它会返回一个指向相同元组的引用。

拷贝注意点

对于非容器类型,如数字、字符,以及其他的“原子”类型,没有拷贝一说,产生的都是原对象的引用。

如果元组变量值包含原子类型对象,即使采用了深拷贝,也只能得到浅拷贝。


Python协程

协程是实现并发编程的一种方式,是用户态的线程,由用户决定在什么地方交出控制权,切换到下一个任务。以下为使用协程写异步爬虫程序:

import asyncio

async def crawl_page(url):
    print('crawling {}'.format(url))
    sleep_time = int(url.split('_')[-1])
    await asyncio.sleep(sleep_time)
    print('OK {}'.format(url))

async def main(urls):
    tasks = [asyncio.create_task(crawl_page(url)) for url in urls]
    for task in tasks:
        await task

%time asyncio.run(main(['url_1', 'url_2', 'url_3', 'url_4']))

########## 输出 ##########

crawling url_1
crawling url_2
crawling url_3
crawling url_4
OK url_1
OK url_2
OK url_3
OK url_4
Wall time: 3.99 s

协程执行的三步:

  1. 通过await来调用。
  2. 通过asyncio.create_task()来创建任务。
  3. 使用asynic.run来触发运行。

并发和并行

并发,通过线程和任务之间互相切换的方式实现,但同一时刻,只允许有一个线程或任务执行。通常应用于 I/O 操作频繁的场景,比如从网站上下载多个文件,I/O 操作的时间可能会比 CPU 运行处理的时间长得多。

并行,则是指多个进程同时执行。更多应用于 CPU heavy 的场景,比如 MapReduce 中的并行计算,为了加快运行速度,一般会用多台机器、多个处理器来完成。

单线程与多线程性能比较

#单线程实现方式
def download_all(sites):
    for site in sites:
        download_one(site)

#多线程实现方式
def download_all(sites):    
    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:                    
        executor.map(download_one, sites)

这里创建了一个线程池,总共有 5 个线程可以分配使用。executer.map() 与前面所讲的 Python 内置的 map() 函数类似,表示对 sites 中的每一个元素,并发地调用函数 download_one()。


并发编程之Asycio

Sync(同步)和Async(异步)

Sync同步,是指操作一个接一个地执行,下一个操作必须等上一个操作完成后才能执行。

Async异步,是指不同操作间可以相互交替执行,如果其中的某个操作被 block 了,程序并不会等待,而是会找出可执行的操作继续执行。

Asyncio 工作原理

Asyncio 是单线程的,但其内部 event loop 的机制,可以让它并发地运行多个不同的任务,并且比多线程享有更大的自主控制权。

假设任务只有两个状态:一是预备状态;二是等待状态。event loop 会维护两个任务列表,分别对应这两种状态;并且选取预备状态的一个任务,使其运行,一直到这个任务把控制权交还给 event loop 为止。当任务把控制权交还给 event loop 时,event loop 会根据其是否完成,把任务放到预备或等待状态的列表,然后遍历等待状态列表的任务,查看他们是否完成。如果完成,则将其放到预备状态的列表;如果未完成,则继续放在等待状态的列表。周而复始,直到所有任务完成。

Asyncio 用法

Asyncio 版本的函数 download_all():

tasks = [asyncio.create_task(download_one(site)) for site in sites]
await asyncio.gather(*task)

如何选择多线程还是Asyncio

  • 如果是 I/O bound,并且 I/O 操作很慢,需要很多任务 / 线程协同实现,那么使用 Asyncio 更合适。
  • 如果是 I/O bound,但是 I/O 操作很快,只需要有限数量的任务 / 线程,那么使用多线程就可以了。
  • 如果是 CPU bound,则需要使用多进程来提高程序运行效率。

Python多进程和多线程如何创建

  • 多进程:multiprocessing.Process类
  • 多线程:threading.Thread类

Python GIL

GIL全局解释器锁,每一个 Python 线程,在 CPython 解释器中执行时,都会先锁住自己的线程,阻止别的线程执行。

由于CPython解释器的内存管理并不是线程安全的,为了保护多线程下对Python对象的访问引入了GIL锁。

GIL的影响:同一时间只能有一个线程执行字节码,CPU密集程序难以利用多核优势。IO期间由于会释放锁,对IO密集程序影响不大。

如何规避GIL影响

  • CPU密集可以使用多进程+进程池
  • IO密集使用多线程/协程
  • 将关键性能代码放到C中实现

为什么有了GIL还要关注线程安全:python中只有原子操作是可以保证线程安全的,即一个操作如果是一个字节码指令可以完成就是原子的。


Python的内存管理机制

python的内存管理机制包括内存池机制垃圾回收机制

Python的垃圾回收机制

以引用计数为主,标记清除和分代回收为辅,其中标记清除和分代回收解决循环引用的问题。

引用计数

函数内部声明的局部变量,在函数返回后,局部变量的引用会注销掉;此时变量指代对象的引用数为 0,Python 便会执行垃圾回收。

s.getrefcount() 这个函数,可以查看一个变量的引用次数。

手动释放内存方法:先调用” del 变量名 “删除对象的引用;然后强制调用 gc.collect(),清除没有引用的对象。

标记清除

标记清除算法:遍历并标记一个有向图,在遍历结束后,未被标记的节点即为不可达节点,需要进行垃圾回收。(实现方法:dfs (深度优先搜索)遍历,从起点开始遍历,对遍历到的节点做个记号。遍历完成后,再对所有节点扫一遍,没有被做记号的,就是需要垃圾回收的。)

只有容器类对象才有可能产生循环引用。

分代回收

Python 将所有对象分为三代。刚刚创立的对象是第 0 代;经过一次垃圾回收后,依然存在的对象,便会依次从上一代挪到下一代。而每一代启动自动垃圾回收的阈值,则是可以单独指定的。当垃圾回收器中新增对象减去删除对象达到相应的阈值时,就会对这一代对象启动垃圾回收。 gc.get_threshold()可查看三代阈值。