Python del()方法:销毁对象

Python 通过调用 init() 方法构造当前类的实例化对象,而节 del() 方法,功能正好和 init() 相反,其用来销毁实例化对象。

事实上在编写程序时,如果之前创建的类实例化对象后续不再使用,最好在适当位置手动将其销毁,释放其占用的内存空间(整个过程称为垃圾回收(简称GC))。
大多数情况下,Python 开发者不需要手动进行垃圾回收,因为 Python 有自动的垃圾回收机制,能自动将不需要使用的实例对象进行销毁。
无论是手动销毁,还是 Python 自动帮我们销毁,都会调用 del() 方法

1
2
3
4
5
6
7
class CLanguage:
def __init__(self):
print("调用 __init__() 方法构造对象")
def __del__(self):
print("调用__del__() 销毁对象,释放其空间")
clangs = CLanguage()
del clangs

读者千万不要误认为,只要为该实例对象调用 del() 方法,该对象所占用的内存空间就会被释放

1
2
3
4
5
6
7
8
9
class CLanguage:
def __init__(self):
print("调用 __init__() 方法构造对象")
def __del__(self):
print("调用__del__() 销毁对象,释放其空间")
clangs = CLanguage()
cl = clangs
del clangs
print('nihao')

可以看到,当程序中有其它变量(比如这里的 cl)引用该实例对象时,即便手动调用 del() 方法,该方法也不会立即执行。这和 Python 的垃圾回收机制的实现有关。

Python 采用自动引用计数(简称 ARC)的方式实现垃圾回收机制。

该方法的核心思想是:
每个 Python 对象都会配置一个计数器,初始 Python 实例对象的计数器值都为 0,如果有变量引用该实例对象,其计数器的值会加 1,依次类推;反之,每当一个变量取消对该实例对象的引用,计数器会减 1。如果一个 Python 对象的的计数器值为 0,则表明没有变量引用该 Python 对象,即证明程序不再需要它,此时 Python 就会自动调用 del() 方法将其回收。

以上面程序中的 clangs 为例,实际上构建 clangs 实例对象的过程分为 2 步,先使用 CLanguage() 调用该类中的 init() 方法构造出一个该类的对象(将其称为 C,计数器为 0),并立即用 clangs 这个变量作为所建实例对象的引用( C 的计数器值 + 1)。在此基础上,又有一个 cl 变量引用 clangs(其实相当于引用 CLanguage(),此时 C 的计数器再 +1 ),这时如果调用del clangs语句,只会导致 C 的计数器减 1(值变为 1),因为 C 的计数器值不为 0,因此 C 不会被销毁(不会执行 del() 方法)。

需要额外说明的是,如果我们重写子类的 del() 方法(父类为非 object 的类),则必须显式调用父类的 del() 方法,这样才能保证在回收子类对象时,其占用的资源(可能包含继承自父类的部分资源)能被彻底释放。

1
2
3
4
5
6
7
8
9
class CLanguage:
def __del__(self):
print("调用父类 __del__() 方法")

class cl(CLanguage):
def __del__(self):
print("调用子类 __del__() 方法")
c = cl()
del c

正确做法:

1
2
3
4
5
6
7
8
9
class CLanguage:
def __del__(self):
print("调用父类 __del__() 方法")
class cl(CLanguage):
def __del__(self):
print("调用子类 __del__() 方法")
c = cl()
del c
CLanguage.__del__(cl)
Python dir()用法:列出对象的所有属性(方法)名

Python 内置函数时,提到了 dir() 函数,通过此函数可以某个对象拥有的所有的属性名和方法名,该函数会返回一个包含有所有属性名和方法名的有序列表。
注意,通过 dir() 函数,不仅仅输出本类中新添加的属性名和方法(最后 3 个),还会输出从父类(这里为 object 类)继承得到的属性名和方法名。

值得一提的是,dir() 函数的内部实现,其实是在调用参数对象 dir() 方法的基础上,对该方法返回的属性名和方法名做了排序。
所以,除了使用 dir() 函数,我们完全可以自行调用该对象具有的 dir() 方法:

1
2
3
4
5
6
7
8
9
class CLanguage:
def __init__(self):
print("调用 __init__() 方法构造对象")
def __del__(self):
print("调用__del__() 销毁对象,释放其空间")
def __say(self):
pass
clangs = CLanguage()
print(clangs.__dir__())

使用 dir() 方法和 dir() 函数输出的数据是相同,仅仅顺序不同。

Python __dict__属性:查看对象内部所有属性名和属性值组成的字典

在 Python 类的内部,无论是类属性还是实例属性,都是以字典的形式进行存储的,其中属性名作为键,而值作为该键对应的值。

为了方便用户查看类中包含哪些属性,Python 类提供了 dict 属性。需要注意的一点是,该属性可以用类名或者类的实例对象来调用

类名直接调用 __dict__,会输出该由类中所有类属性组成的字典;

而使用类的实例对象调用 __dict__,会输出该类中所有实例属性组成的字典。

1
2
3
4
5
6
7
8
9
10
11
12
class CLanguage:
a =23
b=21
def __init__(self):
print("调用 __init__() 方法构造对象")
self.name=12
self.data=42
def __say(self):
pass
clangs = CLanguage()
print(clangs.__dict__)
print(CLanguage.__dict__)

对于具有继承关系的父类和子类来说,父类有自己的 __dict__,同样子类也有自己的 __dict__,它不会包含父类的 dict

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class CLanguage:
a = 1
b = 2
def __init__ (self):
self.name = "12"
self.add = "23"

class CL(CLanguage):
c = 1
d = 2
def __init__ (self):
self.na = "22"
self.ad = "h24"
#父类名调用__dict__
print(CLanguage.__dict__)
#子类名调用__dict__
print(CL.__dict__)
#父类实例对象调用 __dict__
clangs = CLanguage()
print(clangs.__dict__)
#子类实例对象调用 __dict__
cl = CL()
print(cl.__dict__)

Python setattr()、getattr()、hasattr()函数用法详解
Python hasattr()函数

hasattr() 函数用来判断某个类实例对象是否包含指定名称的属性或方法。
语法格式:

hasattr(obj, name)

其中 obj 指的是某个类的实例对象,name 表示指定的属性名或方法名。同时,该函数会将判断的结果(True 或者 False)作为返回值反馈回来。

1
2
3
4
5
6
7
8
9
10
11
class CLanguage:
def __init__ (self):
self.name = "12"
self.add = "23"
def say(self):
print("我正在学Python")

clangs = CLanguage()
print(hasattr(clangs,"name"))
print(hasattr(clangs,"add"))
print(hasattr(clangs,"say"))

无论是属性名还是方法名,都在 hasattr() 函数的匹配范围内。因此,我们只能通过该函数判断实例对象是否包含该名称的属性或方法,但不能精确判断,该名称代表的是属性还是方法。

Python getattr() 函数

getattr() 函数获取某个类实例对象中指定属性的值。没错,和 hasattr() 函数不同,该函数只会从类对象包含的所有属性中进行查找。
语法格式:
getattr(obj, name[, default])

obj 表示指定的类实例对象
name 表示指定的属性名
default 是可选参数,用于设定该函数的默认返回值,即当函数查找失败时,如果不指定 default 参数,则程序将直接报 AttributeError 错误,反之该函数将返回 default 指定的值。

1
2
3
4
5
6
7
8
9
10
11
12
class CLanguage:
def __init__ (self):
self.name = "12"
self.add = "23"
def say(self):
print("我正在学Python")

clangs = CLanguage()
print(getattr(clangs,"name"))
print(getattr(clangs,"add"))
print(getattr(clangs,"say"))
print(getattr(clangs,"display",'no display'))

对于类中已有的属性,getattr() 会返回它们的值,而如果该名称为方法名,则返回该方法的状态信息;反之,如果该明白不为类对象所有,要么返回默认的参数,要么程序报 AttributeError 错误。

Python setattr()函数

setattr() 函数的功能相对比较复杂,它最基础的功能是修改类实例对象中的属性值。其次,它还可以实现为实例对象动态添加属性或者方法。
语法格式:

setattr(obj, name, value)

1
2
3
4
5
6
7
8
9
10
11
12
13
class CLanguage:
def __init__ (self):
self.name = "12"
self.add = "23"
def say(self):
print("我正在学Python")
clangs = CLanguage()
print(clangs.name)
print(clangs.add)
setattr(clangs,"name","Python")
setattr(clangs,"add","change")
print(clangs.name)
print(clangs.add)

甚至利用 setattr() 函数,还可以将类属性修改为一个类方法,同样也可以将类方法修改成一个类属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
def say(self):
print("我正在学Python")

class CLanguage:
def __init__ (self):
self.name = "12"
self.add = "23"

clangs = CLanguage()
print(clangs.name)
print(clangs.add)
setattr(clangs,"name",say)
clangs.name(clangs)

显然,通过修改 name 属性的值为 say(这是一个外部定义的函数),原来的 name 属性就变成了一个 name() 方法。

使用 setattr() 函数对实例对象中执行名称的属性或方法进行修改时,如果该名称查找失败,Python 解释器不会报错,而是会给该实例对象动态添加一个指定名称的属性或方法。:

1
2
3
4
5
6
7
8
9
def say(self):
print("我正在学Python")
class CLanguage:
pass
clangs = CLanguage()
setattr(clangs,"name","漂亮鬼")
setattr(clangs,"say",say)
print(clangs.name)
clangs.say(clangs)

虽然 CLanguage 为空类,但通过 setattr() 函数,我们为 clangs 对象动态添加了一个 name 属性和一个 say() 方法。

Python call()方法

该方法的功能类似于在类中重载 () 运算符,使得类实例对象可以像调用普通函数那样,以“对象名()”的形式使用。

1
2
3
4
5
6
7
class CLanguage:
# 定义__call__方法
def __call__(self,name,add):
print("调用__call__()方法",name,add)

clangs = CLanguage()
clangs("漂亮鬼","https://xiaoyangzst.vercel.app")

通过在 CLanguage 类中实现 call() 方法,使的 clangs 实例对象变为了可调用对象

Python 中,凡是可以将 () 直接应用到自身并执行,都称为可调用对象。可调用对象包括自定义的函数、Python 内置函数以及本节所讲的类实例对象。