问题描述
设置
假设我有一个 Snit:
class Snit(): pass
还有一个 Snot,它包含对最多四个 Snit 的弱引用:
import weakref class Snot(): def __init__(self,s1=None,s2=None,s3=None,s4=None): self.s1 = weakref.ref(s1) self.s2 = weakref.ref(s2) self.s3 = weakref.ref(s3) self.s4 = weakref.ref(s4)
我还有一个 Snot 工厂:
def snot_factory(snits,w,x,y,z): return Snot(snits[w],snits[x],snits[y],snits[z])
还有一个 list 的 Snit (一个 snit_list ):
snit_list = [] for i in range(12): snit_list.append(Snit())
现在我使用 snit_list 中的 Snits 制作一个 Snots 列表:
snot_list = [] for i in range(3): snot_list.append(snot_factory(snit_list[4*i],snit_list[4*i+1],snit_list[4*i+2],snit_list[4*i+3]))
问题
哎呀!我不再需要 snit_list[3],所以我会继续删除它:
snit_list.pop(3)
但现在我有一个 Snot 和一个死去的 Snit 一起出去玩:
snot_list[0].s4 # <weakref at 0x00BlahBlah; dead>
这不能忍受!Snot 和一个死的 Snit 显然是一派胡言.
所以我真的希望对 Snot 的任何引用至少在其一个或多个 Snit 被销毁后返回为 None.但理想情况下,将 Snot 也自动从 snot_list 列表中删除会更好(len(snot_list) 会缩小被删除的 Snot 的数量).
有什么好的方法可以解决这个问题?
澄清:
一个 Snot 是一个对象,只有当存在一组有效的 Snits 时才应该存在("有效"意味着它具有与初始化相同数量的已定义 Snits),具有以下内容行为:
- 如果 Snot 中的任何一个 Snit 消失(当没有强引用保留时),Snot 也应该消失(这就是为什么我将 s1、s2 等设置为是弱引用).请注意,Snot 可能已被初始化为 4、3、2 或 1 Snit.Snit 的数量无关紧要,重要的是 Snit 的 死亡.
- 如果包含对 Snit 的引用的任何一个 Snot 消失了,则 Snit 仍然存在.
- 可选:当一个 Snot 被删除时,包含对 Snot 对象的引用的数据结构也被更新(Snot 得到 popped)
- 可选:当 ALL 引用某个 Snit 的 Snots 消失时,Snit 也会消失,并且任何包含 Snit 的数据结构都会更新,如 #3(Snit 得到 popped).
因此,理想的解决方案将允许我进行设置,以便我可以编写如下代码:
snits = get_snits_list(some_input_with_10000_snits) snots = get_snots_list(some_cross_referenced_input_with_8000_snots) #e.g.: the input file will say: #snot number 12 is made of snits 1, 4, 7 #snot number 45 is made of snits 8, 7, 0, 14 do_stuff_with_snits() snits.pop(7) #snit 7 is common to snot 12 and 45 assert len(snots) == 7998 #snots 12 and 45 have been removed
但是,如果这太难了,我可以:
assert snots[12] == None assert snots[45] == None
我愿意改变一些事情.例如,如果它使设计更容易,我认为可以删除对 Snit 的弱引用,或者将它们移动到 Snit 列表而不是具有 Snot 成员成为弱裁判(尽管我看不出这些变化中的任何一个会如何改善事情).
我也考虑过创建 Snot 子类 - ClearSnot 有 1 个 Snit,YellowSnot 有 2 个 Snit,GreenSnot 有 3Snit 等等.我不确定是否这将使事情更容易维护,或更难.
推荐答案
没有什么是真正自动的.您需要有一个手动运行的函数来检查死 Snits,或者有一个作为 Snot 一部分的函数,当 Snot 发生任何有趣的事情时调用它来检查,并且删除,死 Snits.
例如:
class Snot: ... def __repr__(self): # check for and remove any dead Snits self._remove_dead_snits() return ... def _remove_dead_snits(self): if self.s1() is None: self.s1 = None ... # and so on and so forth
有趣的部分是添加对 _remove_dead_snits 的调用,用于与 Snot 的每一次有趣的交互——例如 __getitem__、__iter__ 以及您可以用它做的任何其他事情.
<小时>实际上,再多考虑一下,如果每个 Snot 只有四个可能的 Snit,则可以使用 SnitRef 描述符——这是代码,对原始代码进行了一些更改:
import weakref class Snit(object): def __init__(self, value): self.value = value # just for testing def __repr__(self): return 'Snit(%r)' % self.value class SnitRef(object): # 'object' not needed in Python 3 def __get__(self, inst, cls=None): if inst is None: return self return self.ref() # either None or the obj def __set__(self, inst, obj): self.ref = weakref.ref(obj) class Snot(object): s0 = SnitRef() s1 = SnitRef() s2 = SnitRef() s3 = SnitRef() def __init__(self,s0=None,s1=None,s2=None,s3=None): self.s0 = s0 self.s1 = s1 self.s2 = s2 self.s3 = s3 snits = [Snit(0), Snit(1), Snit(2), Snit(3)] print snits snot = Snot(*snits) print(snot.s2) snits.pop(2) print snits print(snot.s2)
当运行时:
[Snit(0), Snit(1), Snit(2), Snit(3)] Snit(2) [Snit(0), Snit(1), Snit(3)] None
问题描述
Set Up
Say I have a Snit:
class Snit(): pass
And a Snot, which contains weak references to up to, say, four Snits:
import weakref class Snot(): def __init__(self,s1=None,s2=None,s3=None,s4=None): self.s1 = weakref.ref(s1) self.s2 = weakref.ref(s2) self.s3 = weakref.ref(s3) self.s4 = weakref.ref(s4)
I also have a Snot factory:
def snot_factory(snits,w,x,y,z): return Snot(snits[w],snits[x],snits[y],snits[z])
And a list of Snits (a snit_list as it were):
snit_list = [] for i in range(12): snit_list.append(Snit())
Now I make a list of Snots using the Snits in my snit_list:
snot_list = [] for i in range(3): snot_list.append(snot_factory(snit_list[4*i],snit_list[4*i+1],snit_list[4*i+2],snit_list[4*i+3]))
The Problem
Whoops! I don't need snit_list[3] anymore, so I'll go ahead and remove it:
snit_list.pop(3)
But now I have a Snot hanging out there with a dead Snit:
snot_list[0].s4 # <weakref at 0x00BlahBlah; dead>
This cannot stand! A Snot with a dead Snit is - obviously - total nonsense.
So I would really like for any references to the Snot to at least return as None after one or more of its Snits has been destroyed. But ideally, it would be even better for the Snot to be automatically removed from the snot_list list as well (len(snot_list) shrinks by the number of removed Snots).
What's a good way of going about this?
Clarification:
A Snot is an object that should only exist when there is a valid set of Snits ("valid" means it has the same number of defined Snits it was initialized with), with the following behavior:
- If any one Snit in a Snot goes away (when no strong references remain), the Snot should also go away (this is why I have set the s1, s2, etc to be weak references). Note that a Snot could have been initialized with 4, 3, 2, or 1 Snit. The number of Snits doesn't matter, the death of the Snit is what matters.
- If any one Snot that contains a reference to a Snit goes away, the Snit remains.
- OPTIONAL: When a Snot is deleted, the data structure containing the reference to the Snot object is updated as well (the Snot gets popped)
- OPTIONAL: When ALL the Snots that reference a certain Snit are gone, the Snit goes away too, and any data structures containing the Snit are updated as in #3 (the Snit gets popped).
So the ideal solution will allow me to set things up such that I can write code like this:
snits = get_snits_list(some_input_with_10000_snits) snots = get_snots_list(some_cross_referenced_input_with_8000_snots) #e.g.: the input file will say: #snot number 12 is made of snits 1, 4, 7 #snot number 45 is made of snits 8, 7, 0, 14 do_stuff_with_snits() snits.pop(7) #snit 7 is common to snot 12 and 45 assert len(snots) == 7998 #snots 12 and 45 have been removed
However, if this is too hard, I'd be fine with:
assert snots[12] == None assert snots[45] == None
I am open to changing things around somewhat. For example, if it makes the design easier, I think it would be fine to remove the weak references to the Snits, or to maybe move them instead to the list of Snits instead of having the Snot members be weak refs (though I don't see how either of these changes would improve things).
I have also considered creating Snot subclasses - ClearSnot with 1 Snit, YellowSnot with 2 Snits, GreenSnot with 3Snit`s, etc. I'm uncertain if this would make things easier to maintain, or more difficult.
推荐答案
Nothing is truly automatic. You'll need to either have a function that you run manually to check for dead Snits, or have a function that is part of Snot that is called whenever anything interesting happens to a Snot to check for, and remove, dead Snits.
For example:
class Snot: ... def __repr__(self): # check for and remove any dead Snits self._remove_dead_snits() return ... def _remove_dead_snits(self): if self.s1() is None: self.s1 = None ... # and so on and so forth
The fun part is adding that call to _remove_dead_snits for every interesting interaction with a Snot -- such as __getitem__, __iter__, and whatever else you may do with it.
Actually, thinking a bit more about this, if you only have the four possible Snits per each Snot you could use a SnitRef descriptor -- here's the code, with some changes to your original:
import weakref class Snit(object): def __init__(self, value): self.value = value # just for testing def __repr__(self): return 'Snit(%r)' % self.value class SnitRef(object): # 'object' not needed in Python 3 def __get__(self, inst, cls=None): if inst is None: return self return self.ref() # either None or the obj def __set__(self, inst, obj): self.ref = weakref.ref(obj) class Snot(object): s0 = SnitRef() s1 = SnitRef() s2 = SnitRef() s3 = SnitRef() def __init__(self,s0=None,s1=None,s2=None,s3=None): self.s0 = s0 self.s1 = s1 self.s2 = s2 self.s3 = s3 snits = [Snit(0), Snit(1), Snit(2), Snit(3)] print snits snot = Snot(*snits) print(snot.s2) snits.pop(2) print snits print(snot.s2)
and when run:
[Snit(0), Snit(1), Snit(2), Snit(3)] Snit(2) [Snit(0), Snit(1), Snit(3)] None