手动删除pytorch包 怎么删除pytorch_键值对


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