第3章_Python进阶(一)

1.lambda函数

该函数有一个有趣的例子,代码如下:

def multipliers():
    return [lambda x:i*x for i in range(4)]
print([m(2) for m in multipliers()])

# [6, 6, 6, 6]

一般的解释来说,这是闭包延迟绑定的效果。我们可以更改如下:

lst = [lambda x, i=i: x*i for i in range(4)]
res = [m(2) for m in lst]

相关的参考文献有:
关于 [lambda x: xi for i in range(4)] 理解
说说我对[lambda x: xi for i in range(4)]的理解
fun = [lambda x: x*i for i in range(4)] 本质解析/原理,LEGB规则 闭包原理
python 函数及变量作用域及装饰器decorator @详解

2.单下划线
  1. 在交互解释器当中,_代表上一条命令的执行结果。在代码当中,它代表被丢弃的临时变量。

  2. 在属性和方法之前加上_,代表该属性、方法和类只能在内部使用,是API的非公开部分。如果用 from <module> import *from <package> import *时,这些属性、方法、类将不被导入。但是,如果在代码当中定义了__all__这个字符串列表,则列表当中所有的变量不受_的影响。__all__也可以定义在包的__init__.py文件当中。

3.双下划线
  1. 父类在属性和方法之前加上__,代表该属性或方法不会被子类轻易覆盖,即父类的私有属性和方法。

  2. 如果名称前后都有双下划线,则说明这是Python内部定义的方法,可以进行重写,例如:__init__

4.函数参数传递方式
  1. 对于不可变类型(数值型、字符串、元组),函数参数传递的是值传递;对于可变数据类型(列表、字典)来说。,函数的参数传递是引用传递。

  2. 参数列表当中定义的缺省参数会随着被调用而改变1,例如:

    def extendList(val, list=[]):
        list.append(val)
        return list
    list1 = extendList(10)
    list2 = extendList(123, [])
    list3 = extendList('a')
    
    # list1 = [10, 'a']
    # list2 = [123]
    # list3 = [10, 'a']
    

    首先,我们解释list3的情况:list是可变类型,所以其属于引用传递。也就是,这个缺省的参数会随着被调用而改变。在list1执行的时候,缺省的参数list就已经添加了10,所以在执行list3的时候,就是在10后面加入a。然后我们来看一看list1的情况:我们知道List是可变类型,所以此处给list1的赋值应该属于引用赋值2。那么,当参数list发生改变时,其引用list1也应该发生了改变。

5.闭包3

一般来说,闭包的定义如下:

  1. 在一个外函数中定义了一个内函数。

  2. 内函数里运用了外函数的临时变量。

  3. 并且外函数的返回值是内函数的引用。

闭包也有一些陷阱,例如上文lambda函数的例子

闭包的应用有装饰器和单例模式4

6.*args**kwarg

*arg**kwarg是函数形式参数的两个格式,分别代表以下含义5

*args:可以理解为长度不固定的列表。
**kwarg:可以理解为长度不固定的字典

特别地:6

如果我们想把元组作为星号参数的参数值,在元组值前加上" * " 即可。

7.__getattr____setattr____delattr__

__getattr__函数的作用: 如果属性查找(attribute lookup)在实例以及对应的类中(通过__dict__)失败, 那么会调用到类的__getattr__函数, 如果没有定义这个函数,那么抛出AttributeError异常。由此可见,__getattr__一定是作用于属性查找的最后一步,兜底。78

在对一个属性设置值的时候,会调用到这个函数,每个设置值的方式都会进入__setattr__这个方法。9

__delattr__是个删除属性的方法。__delattr__也要避免死循环的问题,就如__setattr__一样,在重写__delattr__,避免重复调用。10

8.@property

使方法可以像属性一样访问,防止属性被修改11

9.rbuf12
  • r:去掉反斜杠的转义机制,常用于正则表达式,对应着re模块。

  • b:表明后面是bytes 类型。网络编程中,服务器和浏览器只认bytes 类型数据。

  • u:后面字符串以 Unicode 格式进行编码,一般用在中文字符串前面,防止因为源码储存格式问题,导致再次使用时出现乱码。

  • f:表示在字符串内支持大括号内的python表达式,类似str.format()的格式化函数,详细用法见13

10.大文件读取

如果要处理一个大小为10G的文件,但是内存只有4G,需要怎么做?既然无法一次性读入10G文件,那就需要分批读入。分批读入数据,要记录好每次读入数据的位置。同时要控制好每次读取数据的大小,太小会在读取操作花费过多时间。1415

# 方法1-------------------------------------------
def get_lines():
    with open('file.txt','rb') as f:
        for i in f:
            yield i
# 使用生成器,每次只返回一部分,下一次再从上一次打断的地方开始。

# 方法2-------------------------------------------
from mmap import mmap
def get_lines(fp):
    with open(fp,"r+") as f:
        m = mmap(f.fileno(), 0)
        tmp = 0
        for i, char in enumerate(m):
            if char==b"\n":
                yield m[tmp:i+1].decode()
                tmp = i+1

if __name__=="__main__":
    for i in get_lines("fp_some_huge_file"):
        print(i)
# 使用mmap,将文件映射到内存。再使用生成器,每次返回一行

# 方法3-------------------------------------------
def pandas_read(filename,sep=',',chunksize=5):
    reader = pd.read_csv(filename,sep,chunksize=chunksize)
    while True:
        try:
            yield reader.get_chunk()
        except StopIteration:
            print('---Done---')
            break
if __name__ == '__main__':
    g = pandas_read('./data/movies.dat',sep="::")
    for c in g:
        print(c)
        # process c
# 使用pandas,每次读取5行

# 方法一有缺点,逐行读入,频繁的 IO 操作拖累处理效率。是否有一次 IO ,读取多行的方法?
# pandas 包 read_csv 函数,参数有 38 个之多,功能非常强大。
# 关于单机处理大文件,read_csv 的 chunksize 参数能做到,设置为 5, 意味着一次读取 5 行。

上文当中还有一些疑问没有得到解决,我们先说一下生成器的问题:

for i in get_lines("fp_some_huge_file"):

函数get_lines当中使用了yield,属于一个生成器。这个生成器在这里是以类似列表的方式被使用的,下面举一个其他例子16

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
def fab(max): 
    n, a, b = 0, 0, 1 
    while n < max: 
        yield b      # 使用 yield
        # print b 
        a, b = b, a + b 
        n = n + 1
 
for n in fab(5): 
    print n

然后来说一下mmap的问题,其解释如下17

为了随机访问文件的内容,使用mmap将文件映射到内存中是一个高效和优雅的方法。例如,无需打开一个文件并执行大量的seek()read()write()调用,只需要简单的映射文件并使用切片操作访问数据即可。内存映射一个文件并不会导致这个文件被读取到内存中。也就是说,文件并没有被复制到内存缓存或数组中。相反,操作系统仅仅为文件内容保留了一段虚拟内存。当访问文件的不同区域时,这些区域的内容才根据需要被读取并映射到内存区域中。而那些从没被访问到的部分还是留在磁盘上。所有这些过程都是透明的,在幕后完成。

其使用方法等举例如下18

>>> with open("myfile.txt", "wb") as f:
... f.write(b"Hello Python!\n")

>>> import mmap
>>> with open("myfile.txt", "r+b") as f:
... mapf = mmap.mmap(f.fileno(), 0)
... print(mapf.readline()) # prints b"Hello Python!\n"
... print(mapf[:5]) # prints b"Hello"
... mapf.tell()
... mapf[6:] = b" world!\n"
... mapf.seek(0)
... print(mapf.readline()) # prints b"Hello world!\n"
... mapf.close()
...
b'Hello Python!\n'
b'Hello'
14
b'Hello world!\n'

还有一些其他参考文献:
Python之mmap内存映射模块(大文本处理)说明
认真分析mmap:是什么 为什么 怎么用

11.装饰器

装饰器本质上是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的 前提下增加额外的功能,装饰器的返回值也是一个函数对象。19

其主要有以下功能19

  • 引入日志
  • 函数执行时间统计
  • 执行函数前预备处理
  • 执行函数后清理功能
  • 权限校验等场景
  • 缓存

其中,functools模块下的wraps装饰器还可以解决自定义装饰器的一些问题。20用法如:

from functools import wraps

def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """doc of wrapper"""
        print('123')
        return func(*args, **kwargs)

    return wrapper

@decorator
def say_hello():
    """doc of say hello"""
    print('同学你好')

print(say_hello.__name__)
print(say_hello.__doc__)

# say_hello
# doc of say hello

如果不加@wraps(func),则:

def decorator(func):
    def wrapper(*args, **kwargs):
        """doc of wrapper"""
        print('123')
        return func(*args, **kwargs)

    return wrapper

@decorator
def say_hello():
    """doc of say hello"""
    print('同学你好')

print(say_hello.__name__)
print(say_hello.__doc__)

# wrapper
# doc of wrapper

还有一些内置装饰器,如:

  • property装饰器:用于类中的方法,使得我们可以像访问属性一样来获取一个方法的返回值。其内部具体运行机制详见21

    class XiaoMing:
        first_name = '明'
        last_name = '小'
    
        @property
        def full_name(self):
            return self.last_name + self.first_name
    
    xiaoming = XiaoMing()
    print(xiaoming.full_name)
    
    # 小明
    
  • setter装饰器:用于实现对私有属性的修改。一般来说类的私有属性不能够在类的外部被访问。而经过property装饰器,私有属性可以在类外被访问。同样,经过setter装饰器,私有属性可以在类外被修改。22

    class Person:
        def __init__(self,name):
            self._name = name
    
        # 利用property装饰器将获取name方法转换为获取对象的属性
        @property
        def get_name(self):
            return self._name
    
        # 利用setter装饰器将设置name方法转换为设置对象的属性
        @get_name.setter
        def set_name(self,name):
            self._name = name
    
    p = Person('小黑')
    print(p.get_name)   
    # 原 p.get_name()  , 现 p.get_name
    p.set_name = '小灰' 
    # 原 p.set_name('小灰')  ,现 p.set_name = '小灰'
    print(p.get_name)
    
  • staticmethod 装饰器:用于类中的方法,这表示这个方法将会是一个静态方法,意味着该方法可以直接被调用无需实例化,但同样意味着它没有self参数,也无法访问实例化后的对象。应用场景等详见23

    class XiaoMing:
        @staticmethod
        def say_hello():
            print('同学你好')
    
    XiaoMing.say_hello()
    
    # 实例化调用也是同样的效果
    # 有点多此一举
    xiaoming = XiaoMing()
    xiaoming.say_hello()
    # 同学你好
    # 同学你好
    
  • classmethod 装饰器:用于类中的方法,这表示这个方法将会是一个类方法,意味着该方法可以直接被调用无需实例化,但同样意味着它没有self参数,也无法访问实例化后的对象。相对于staticmethod的区别在于它会接收一个指向类本身的cls参数,即可以访问类中的某些变量。应用场景等详见23

    class XiaoMing:
        name = '小明'
    
        @classmethod
        def say_hello(cls):
            print('同学你好, 我是' + cls.name)
            print(cls)
    
    XiaoMing.say_hello()
    
    # 同学你好, 我是小明
    # <class '__main__.XiaoMing'>
    

同时装饰器也可以实现一些缓存功能,例如性能优化当中的函数返回值缓存:

import time

class Cache:
    __cache = {}

    def __init__(self, func):
        self.func = func

    def __call__(self):

        # 如果缓存字典中有这个方法的执行结果
        # 直接返回缓存的值
        if self.func.__name__ in Cache.__cache:
            return Cache.__cache[self.func.__name__]

        # 计算方法的执行结果
        value = self.func()
        # 将其添加到缓存
        Cache.__cache[self.func.__name__] = value
        # 返回计算结果
        return value

@Cache
def long_time_func():
    time.sleep(5)
    return '我是计算结果'

start = time.time()
print(long_time_func())
end = time.time()
print(f'计算耗时{end-start}秒')

start = time.time()
print(long_time_func())
end = time.time()
print(f'计算耗时{end-start}秒')

# 我是计算结果
# 计算耗时5.001157283782959秒
# 我是计算结果
# 计算耗时0.0秒

关于__call__24

call():可以让类的实例具有类似于函数的行为,
进一步模糊了函数和对象之间的概念。
使用方式:对象后面加括号,触发执行。即:对象() 或者 类()()

class Person(object):
    def __call__(self, *args, **kwargs):
        print('call...')
        
person = Person()  # 将Person()的内存地址赋值给person
person()  # 内存地址()
#或者是Person()()

# call...

如上代码所示,调用__call__时,并不需要person.__call__。因为在person=Person()时,就已经将Person()的内存地址赋值给了person,所以后面调用时person本身就代表了那个内存地址,当成函数使用person()

12.生成器

创建方式:

  1. 把一个列表生成式的[]改成()

  2. 函数中包含yield关键字

yield的作用:

  • yield可以保存当前运行状态(断点),然后暂停执行,即将函数挂起。将yield关键字后面表达式的值作为返回值返回,此时可以理解为起到了return的作用,如果使用next()send()让函数从断点处继续执行,就会唤醒函数。

  • 在用for循环的时候,每次取一个元素就会计算一次。使用yield的函数叫做generator,与iterator一样,它的好处是不用一次性计算所有元素,而是用一次计算一次,这样可以节省很多空间。generator每次计算都需要上一次的计算结果,所以要使用yield,否则在使用return之后,上次计算结果就丢失了。

14.迭代器

创建迭代器:

  1. 使用iter()25

    >>>lst = [1, 2, 3]
    >>> for i in iter(lst):
    ...     print(i)
    ... 
    1
    2
    3
    
  2. 自定义迭代器:26

    class MyRange(object):
        def __init__(self, end):
            self.start = 0
            self.end = end
    
        def __iter__(self):
            return self
    
        def __next__(self):
            if self.start < self.end:
                ret = self.start
                self.start += 1
                return ret
            else:
                raise StopIteration
    
    from collections.abc import *
    
    a = MyRange(5)
    print(isinstance(a, Iterable))
    print(isinstance(a, Iterator))
    
    for i in a:
        print(i)
    
    # True
    # True
    # 0
    # 1
    # 2
    # 3
    # 4
    

    说明:27

    1. 能被for循环的对象,里面一定有__iter__和 __next__两种方法,该对象被for循环时,会优先执行__iter__里面的内容,再循环执行__next__里面的内容
    2. 能被next调用的对象,里面一定有__next__方法,__iter__方法是否具有不能确定

    补充:StopIteration异常用于标识迭代的完成28

    li=[1,2,3,4]
    it=iter(li)
    
    print(next(it))
    print(next(it))
    print(next(it))
    print(next(it))
    print(next(it))  # next()完成后引发StopIteration异常
    ---------------------------------------------------------
    for l in it:  # for循环自带异常处理 
        print(l)
    ---------------------------------------------------------
    import sys  # while循环需要带异常处理
    while True:
        try:
            print(next(it))
        except StopIteration:
            sys.exit()
    

    补充:__getitem__也是模拟的返回一个迭代器29

    class Person:
        def __init__(self,persion_list):
            self.persion_list=persion_list
        def __getitem__(self, item):
            return self.persion_list[item]
     
    body=Person(["Xiuwu","Adny","Maggie"])
    
    for i in body:
        print (i)
    

    区别:30

    生成器能做到迭代器能做的所有事,而且因为自动创建了iter()next()方法,生成器显得特别简洁,而且生成器也是高效的,使用生成器表达式取代列表解析可以同时节省内存。除了创建和保存程序状态的自动方法,当发生器终结时,还会自动抛出 StopIteration异常。

15.isinstance()type()

区别如下:31

  • type()不会认为子类是一种父类类型,不考虑继承关系。
  • isinstance()会认为子类是一种父类类型,考虑继承关系。
16.赋值、浅拷贝、深拷贝

解释如下:32

  • 直接赋值:其实就是对象的引用(别名)。
  • 浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象。
  • 深拷贝(deepcopy)copy 模块的 deepcopy方法,完全拷贝了父对象及其子对象。

举例如下:

#!/usr/bin/python
# -*-coding:utf-8 -*-

import copy
a = [1, 2, 3, 4, ['a', 'b']] 
#原始对象

b = a                       
#赋值,传对象的引用
c = copy.copy(a)            
#对象拷贝,浅拷贝
d = copy.deepcopy(a)        
#对象拷贝,深拷贝
 
a.append(5)                 
#修改对象a
a[4].append('c')            
#修改对象a中的['a', 'b']数组对象
 
print( 'a = ', a )
print( 'b = ', b )
print( 'c = ', c )
print( 'd = ', d )

# ('a = ', [1, 2, 3, 4, ['a', 'b', 'c'], 5])
# ('b = ', [1, 2, 3, 4, ['a', 'b', 'c'], 5])
# ('c = ', [1, 2, 3, 4, ['a', 'b', 'c']])
# ('d = ', [1, 2, 3, 4, ['a', 'b']])
17.内存管理3334
  1. 引用计数机制

    Python内部使用引用计数,来保持追踪内存中的对象,所有对象都有引用计数。创建对象、作为参数传递给函数等会造成引用计数的增加,显式del对象、本地引用离开其作用范围(引用该对象的函数结束)等会造成引用计数的减少。sys.getrefcount()函数可以获得对象的当前引用计数。

    引用计数器的底层机制参见博文35

  2. 垃圾回收机制

    当一个对象的引用计数归零时,它将被垃圾收集机制处理掉。

    相互引用:

    当两个对象a和b相互引用时,del语句可以减少a和b的引用计数,并销毁用于引用底层对象的名称。然而由于每个对象都包含一个对其他对象的应用,因此引用计数不会归零,对象也不会销毁。(从而导致内存泄露)。为解决这一问题,解释器会定期执行一个循环检测器,搜索不可访问对象的循环并删除它们。

    循环引用:

    如果显式重写了__del__方法,就有可能无法自动回收循环引用的对象,造成内存泄漏,具体解释见36。当然这是python2.x的环境,我在python3.x的环境当中测试了一下,似乎没有影响,可以回收内存,即文中注释掉重写__del__方法的那种情况。

  3. 内存池机制

    Python中所有小于256个字节的对象都使用pymalloc实现的分配器,即管理对小块内存申请和释放的内存池机制,而大的对象则使用系统的malloc

    补充:

    对于Python对象,如整数,浮点数和List,都有其独立的私有内存池,对象间不共享他们的内存池。也就是说如果你分配又释放了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数。

    这里的意思应该是指整数释放的内存归入整数的私有内存池,用于下次新建整数对象时分配内存。这个情况貌似叫free_list机制37

其他:

  • 当退出python时,是否释放全部内存?38

    并没有释放全部内存。循环引用其他对象或引用自全局命名空间的对象的模块,在退出时并非完全释放。另外,也不会释放C库保留的内存部分

  • 引用计数的优缺点39

    优点:

    1. 高效
    2. 运行期没有停顿:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。
    3. 对象有确定的生命周期
    4. 易于实现

    缺点:

    1. 维护引用计数消耗资源,维护引用计数的次数和引用赋值成正比,而不像mark and sweep等基本与回收的内存数量有关。
    2. 无法解决循环引用的问题。AB相互引用而再没有外部引用AB中的任何一个,它们的引用计数都为1,但显然应该被回收。
    		# 循环引用示例
    		list1 = []
    		list2 = []
    		list1.append(list2)
    		list2.append(list1)
    

    为了解决这两个缺点Python还引入了另外的机制:标记清除和分代回收

  • 标记清除(Mark—Sweep)40

    • 标记删除第一步:对执行删除(-1)后的每个引用-1,那么a的引用就是0,b的引用为1,将a放到死亡容器,将b放到存活容器。
    • 标记删除第二步:循环存活容器,发现b引用a,复活a:将a放到存活容器内。
    • 标记删除第三步:删除死亡容器内的所有对象。

    还有一种类似于图的解释,参见这两篇博文4142

  • 分代收集40

    1、新创建的对象做为0代
    2、每执行一个【标记-删除】,存活的对象代数就+1
    3、代数越高的对象(存活越持久的对象),进行【标记-删除】的时间间隔就越长。这个间隔,江湖人称阀值。

    其相关的底层源码参见43

其他参考文献:
python学习分享之垃圾回收-引用计数器35
python学习分享之垃圾回收-缓存机制37

18.zip()函数

该函数以一个或多个序列(可迭代对象)作为参数,返回一个元组的列表。

a = [1,2,3]
b = [4,5,6]
c = [7,8,9]
print(list(zip(a,b,c)))
# [(1, 4, 7), (2, 5, 8), (3, 6, 9)]
print(*zip(a,b,c))
# (1, 4, 7) (2, 5, 8) (3, 6, 9)

*zip()函数是zip()函数的逆过程,将zip对象变成原先组合前的数据。44

*zip()似乎是把迭代器zip()转化回了元组,但依旧对参数进行了逐个配对

19.hasattrgetattrsetattr45
  • hasattr(object, name):判断object对象中是否存在name属性,返回布尔值

  • getattr(object, name[, default]):获取object对象的属性的值

    • 存在则返回
    • 不存在则:
      • 没有default参数时,会直接报错
      • 给定了default参数
        • 若对象本身没有name属性,则会返回给定的default值
        • 如果给定的属性name是对象的方法,则返回的是函数对象,需要调用函数对象来获得函数的返回值;调用的话就是函数对象后面加括号,如func之于func();
  • setattr(object, name, value):给object对象的name属性赋值value

    • 如果对象原本存在给定的属性name,则setattr会更改属性的值为给定的value
    • 如果对象原本不存在属性namesetattr会在对象中创建属性,并赋值为给定的value
20.方法遮蔽

子类所继承的父类中的方法,如果存在同名,前面的会“遮蔽”后面的。即前面的直接父类优先级高于后面的。46

#coding=utf-8
class Item:
    def info(self):
        print("Item中的方法",'这是一个商品')
        
class Product:
    def info(self):
        print('Product中的方法','这是一个能赚钱的商品')
        
class Computer(Item,Product):
    pass
    
c = Computer()
c.info()

# Item中的方法",'这是一个商品

  1. python基础面试题 ↩︎

  2. python基础-引用赋值与按值赋值 ↩︎

  3. 理解Python闭包概念 ↩︎

  4. Python实现Singleton模式的几种方式 ↩︎

  5. python *arg **kwarg 详解 ↩︎

  6. Python 带星号(* 或 **)的函数参数详解 ↩︎

  7. __getattr__使用 ↩︎

  8. getattr__和__setattr ↩︎

  9. python魔法方法之__setattr__() ↩︎

  10. Python 魔法方法(三) getattrsetattrdelattr ↩︎

  11. python @property的介绍与使用 ↩︎

  12. Python 字符串前面加u,r,b,f的含义 ↩︎

  13. Python format 格式化函数 ↩︎

  14. 一个10G的文件,但是内存只有4G,使用python如何进行性能调优? ↩︎

  15. 4G 内存处理 10G 大小的文件 ↩︎

  16. Python yield 使用浅析 ↩︎

  17. Python中mmap文件映射 ↩︎

  18. python中使用mmap函数所需的示例 ↩︎

  19. Python装饰器及相关案例 ↩︎ ↩︎

  20. 如何理解Python装饰器? - 三眼鸭的编程教室的回答 - 知乎 ↩︎

  21. Python @property使用的问题? - 灵剑的回答 - 知乎 ↩︎

  22. python中property和setter装饰器 ↩︎

  23. Python 中的 classmethod 和 staticmethod 有什么具体用途? - 灵剑的回答 - 知乎 ↩︎ ↩︎

  24. python中的常用魔术方法 ↩︎

  25. Python iter() 函数 ↩︎

  26. 【Python魔术方法】迭代器(iter__和__next) ↩︎

  27. python中__iter__和__next__执行顺序 ↩︎

  28. python next()迭代器完成会引发StopIteration异常 ↩︎

  29. python3:深刻理解__iter__和__next__ 迭代器的原理(用自定义迭代器方法进行讲解) ↩︎

  30. 迭代器和生成器总结 ↩︎

  31. Python:isinstance() 与 type() 区别 ↩︎

  32. Python 直接赋值、浅拷贝和深度拷贝解析 ↩︎

  33. Python 底层原理知识 ↩︎

  34. Python内存管理机制 ↩︎

  35. python学习分享之垃圾回收-引用计数器 ↩︎ ↩︎

  36. python类中显示重写__del__方法,引起循环引用的对象无法释放,造成垃圾泄露问题 ↩︎

  37. python学习分享之垃圾回收-缓存机制 ↩︎ ↩︎

  38. 当退出python时,是否释放全部内存 ↩︎

  39. 搞定这套 Python 爬虫面试题,Python面试 so easy ↩︎

  40. Python垃圾回收详解:引用计数+标记清理+分代回收 ↩︎ ↩︎

  41. Python垃圾回收机制(引用计数+标记清除+分代回收) ↩︎

  42. Python的垃圾回收机制(引用计数+标记清除+分代回收) ↩︎

  43. python学习分享之垃圾回收-标记清除和分代回收 ↩︎

  44. Python中的zip()与*zip()函数详解 ↩︎

  45. HASATTR() GETATTR() SETATTR() 函数使用方法 ↩︎

  46. Python关于多继承 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值