0、问题引入
要分析的代码很简单,只是torch的nn.Module类中的__delattr__()函数【1】,这个函数用于删除类中的属性,源码如下:
def __delattr__(self, name):
# 这三种类型的容器都是collections提供的OrderedDict类型
# 即self._xxx为OrderedDict类型
if name in self._parameters:
del self._parameters[name]
elif name in self._buffers:
del self._buffers[name]
elif name in self._modules:
del self._modules[name]
# 不是上述三种类型,则采用object的__delattr__()方法
else:
object.__delattr__(self, name)
初读这段代码的时候觉得很简单,再仔细一看发现,其中涉及到了del和对应的__del__()与__delattr__()方法,所以决定仔细梳理对应的语法,防止使用中出现意料之外的错误。
1、del操作如何删除OrderedDict键值对
del对应OrderedDict的情况,而OrderedDict却是Module类三大类型的实现方式。于是查看了OrderedDict类的源码【2】,对应找到了其__delitem__()函数的定义:
def __delitem__(self, key, dict_delitem=dict.__delitem__):
'od.__delitem__(y) <==> del od[y]'
# Deleting an existing item uses self.__map to find the link which gets
# removed by updating the links in the predecessor and successor nodes.
# 省略。。。。。。
# 删除对应的就是删除节点
link.prev = None
link.next = None
尝试从本地修改源码中此定义来的内容,进而测试del操作是否触发这个函数,但是不知为何始终无法触动OrderedDict类的任何函数(希望修改过python源码的朋友指导我一下)。总之,我觉得del语句对应触发的就是这个__delitem__函数。
2、__delattr__函数何时被触发
这个问题比较简单,只要采用del语句删除类实例属性时__delattr__就会被触发,如下的测试代码:
class Net(nn.Module):
def __init__(self):
super().__init__()
self.layer = nn.Linear(1, 1)
def __delattr__(self, name):
print("del {} now".format(name))
net = Net()
del net.layer
# 此时输出:del layer now
print(net.layer)
# 仍然会输出:Linear(in_features=1, out_features=1, bias=True)
执行del net.layer时会输出“del layer now”,这就说明删除类实例属性时触动了__delattr_()方法。但是由于自己重载了__delattr__()却没有执行删除操作,所以net.layer并没有被删除(重载需谨慎)。
3、为什么if-else一共四个分支
去除三大类型外,其余都采用object的__delattr__()方法进行删除。需要注意的是,删除的不是_xxx而是_xxx中的键值对,这反映出Module类的“属性分类”思想,也就是说某些属性负责管理其它属性,自己定义了下面这个类来模仿这种行为:
class Fun(object):
def __init__(self):
# 必须第一个被初始化
self.attribute_manager = {}
self.name = "Tom Ford"
self.age = 50
def __setattr__(self, name, value):
# 针对不同属性定义不同行为
if name == "attribute_manager":
object.__setattr__(self, name, value)
else:
# 任何属性都将不会被注册到self的__dict__中
# 而是注册到self的attribute_manager中
self.attribute_manager[name]=value
fun = Fun()
print(fun.attribute_manager)
某些属性负责管理其它属性:
{'name': 'Tom Ford', 'age': 50}
4、__del__()和__delattr__()的区别
del语句作用于类实例和类实例属性是两种不同的情况,前者触发__del__(),而后者才触发__delattr__(),测试代码如下:
class Fun:
def __init__(self):
self.name = "Tom Ford"
def __del__(self):
print("calling __del__")
def __delattr__(self, name):
print("calling __delattr__")
fun = Fun()
del fun.name
del fun
# 输出如下:
calling __delattr__
calling __del__
这里还必须指出,del对象时删除的是“引用”,所以当同一个对象存在多个引用时,仅在删除最后一个引用才会触发__del__(),测试如下:
fun = Fun()
del fun_1
print("*"*20)
del fun_2
print("*"*20)
del fun
输出如下:
********************
********************
calling __del__
这里体现的就是“万物皆引用”了,尽管自己写代码时很少会给一个对象多个引用。
参考:
【1】https://github.com/pytorch/pytorch/blob/master/torch/nn/modules/module.py
【2】https://github.com/python/cpython/blob/3.8/Lib/collections/__init__.py