Python中的弱引用列表

21

我需要一个可以在对象失效时删除元素的弱引用列表。目前唯一的方法是手动清除列表(手动删除无效引用)。

我知道有WeakKeyDictionary和WeakValueDictionary,但我真正需要的是WeakList,请问是否有实现这个功能的方法?

以下是示例:

import weakref

class A(object):
    def __init__(self):
       pass

class B(object):
    def __init__(self):
        self._references = []

    def addReference(self, obj):
        self._references.append(weakref.ref(obj))

    def flush(self):
        toRemove = []

        for ref in self._references:
            if ref() is None:
                toRemove.append(ref)

        for item in toRemove:
            self._references.remove(item)

b = B()

a1 = A()
b.addReference(a1)
a2 = A()
b.addReference(a2)

del a1
b.flush()
del a2
b.flush()

弱引用(weak reference)是指会变得无效("dead")的引用。引用(reference)是指一种弱引用(http://docs.python.org/library/weakref.html)。刷新(flush)是指手动移除 = 迭代列表中的所有引用并移除其中无效的引用。 - Dan
我写了你的WeakList。请看这里https://dev59.com/dGsz5IYBdhLWcg3wiYY0 - Neil G
6个回答

6

你可以使用同样来自weakref模块的WeakSet(顺便说一下,它实际上是在其他地方定义的,但在那里被导入)。

>>> from weakref import WeakSet
>>> s = WeakSet()
>>> class Obj(object): pass # can't weakref simple objects
>>> a = Obj()
>>> s.add(a)
>>> print len(s)
1
>>> del a
>>> print len(s)
0

5
WeakSet 只能存储可哈希对象。 - Peter Graham
6
它还有一个缺点,就是无法保留其条目的顺序。 - SingleNegationElimination
3
@PeterGraham 和 TokenMacGuy:OP 没有说明他对于排序的要求。因此,如果他只是想要一个弱引用的[无序]包,并且这些物品是可哈希的,那么 WeakSet 的想法看起来是可行的。如果这些物品是他自己设计的对象,也许他可以在自己的对象中满足可哈希的要求。 - Dan H

6
您可以像之前一样自己实现,但是使用一个调用flush()的列表子类在尝试访问项目之前。
显然,您不希望在每次访问时都这样做,但是您可以通过在弱引用上设置回调来优化此过程,以便在某些东西死亡时标记列表已更改。然后,您只需要在上次访问后有东西死亡时刷新列表即可。
这里有一个使用此方法实现的列表类。(请注意,它没有经过很多测试,并且某些方法的实现效率不高(例如,仅将其转换为真实列表并调用该方法),但它应该是一个合理的起点:
import weakref

class WeakList(list):
    def __init__(self, seq=()):
        list.__init__(self)
        self._refs = []
        self._dirty=False
        for x in seq: self.append(x)

    def _mark_dirty(self, wref):
        self._dirty = True

    def flush(self):
        self._refs = [x for x in self._refs if x() is not None]
        self._dirty=False

    def __getitem__(self, idx):
        if self._dirty: self.flush()
        return self._refs[idx]()

    def __iter__(self):
        for ref in self._refs:
            obj = ref()
            if obj is not None: yield obj

    def __repr__(self):
        return "WeakList(%r)" % list(self)

    def __len__(self):
        if self._dirty: self.flush()
        return len(self._refs)

    def __setitem__(self, idx, obj):
        if isinstance(idx, slice):
            self._refs[idx] = [weakref.ref(obj, self._mark_dirty) for x in obj]
        else:
            self._refs[idx] = weakref.ref(obj, self._mark_dirty)
        
    def __delitem__(self, idx):
        del self._refs[idx]

    def append(self, obj):
        self._refs.append(weakref.ref(obj, self._mark_dirty))

    def count(self, obj):
        return list(self).count(obj)

    def extend(self, items):
        for x in items: self.append(x)
        
    def index(self, obj):
        return list(self).index(obj)
    
    def insert(self, idx, obj):
        self._refs.insert(idx, weakref.ref(obj, self._mark_dirty))
        
    def pop(self, idx):
        if self._dirty: self.flush()
        obj=self._refs[idx]()
        del self._refs[idx]
        return obj

    def remove(self, obj):
        if self._dirty: self.flush() # Ensure all valid.
        for i, x in enumerate(self):
            if x == obj:
                del self[i]
        
    def reverse(self):
        self._refs.reverse()

    def sort(self, cmp=None, key=None, reverse=False):
        if self._dirty: self.flush()
        if key is not None:
            key = lambda x,key=key: key(x())
        else:
            key = apply
        self._refs.sort(cmp=cmp, key=key, reverse=reverse)

    def __add__(self, other):
        l = WeakList(self)
        l.extend(other)
        return l

    def __iadd__(self, other):
        self.extend(other)
        return self
        
    def __contains__(self, obj):
        return obj in list(self)

    def __mul__(self, n):
        return WeakList(list(self)*n)
        
    def __imul__(self, n):
        self._refs *= n
        return self

1
当列表大小在迭代过程中发生变化时,它会如何表现?我指的是__iter__中的循环和remove中的循环。 - Niriel

5

由于我也需要像你一样的弱引用列表,所以我自己写了一个并且发布到了pypi上。

现在你可以这样做:

pip install weakreflist

那么:

from weakreflist import WeakList

3
为什么你不能像这样做呢:
import weakref

class WeakList(list):
    def append(self, item):
        list.append(self, weakref.ref(item, self.remove))

然后对于__iadd__extend等也做类似的操作。对我来说可行。


0
使用传递给弱引用的第二个参数的回调函数。
此代码应该有效:
import weakref

class WeakRefList(list):

    def add_reference(self, obj):
        self._references.append(weakref.proxy(obj, self.remove))

0
你打算如何使用B?我用weakref列表的唯一操作就是迭代它,所以它的实现很简单:
import weakref

class WeakRefList(object):
    "weakref psuedo list"
    def __init__(yo):
        yo._items = list()
    def __iter__(yo):
        yo._items = [s for s in yo._items if s() is not None]
        return (s() for s in yo._items if s() is not None)
    def __len__(yo):
        yo._items = [s for s in yo._items if s() is not None]
        return len(yo._items)
    def append(yo, new_item):
        yo._items.append(weakref.ref(new_item))
        yo._items = [s for s in yo._items if s() is not None]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接