冻结集的“多顿”实现-每个值仅一个实例

时间:2019-09-12 20:45:33

标签: python design-patterns factory frozenset multiton

无论如何创建frozenset,如何以一种可行的方式为frozenset实现Multiton设计模式?

我正在寻找的类的行为类似于frozenset,但是 gaurantees “完全实习”:对于任何两个实例,如果a == b则{{ 1}}。

this question的答案 似乎为传递给构造函数的每个参数都生成一个实例 (并且似乎还假定它们是可哈希的)。但是给定的a is b可以通过许多不同的方式来构造:构造函数可能会获得具有不同元素顺序或不可哈希列表的元组;或者您可以使用诸如a.union(b)之类的运算符来创建Frozenset等。

动机自然是在试图节省内存。我有一个图,其中有许多顶点(除其他外)用重复的frozenset标记。通过从旧的顶点创建新的顶点来“增长”图形,并通过在旧的顶点中添加或删除元素来获得新的frozenset

非常感谢!

2 个答案:

答案 0 :(得分:0)

这是一种可能的解决方案。

class Uniquifier(object) :
    """
    This class accepts an immutable object and returns a "canonical" instance of it, if one exists, or keeps it in store as that canonical instance. This is used as a kind of clunky multiton implementation.

    Of course instances are not destroyed until this object is destroyed.
    """
    def __init__(self):
        self._universe = {}


    def uniquify(self, item):
        try :
            return self._universe[item]
        except KeyError :
            self._universe[item] = item
            return self._universe[item]

运行此:

a = frozenset([3,5])
b = frozenset([5,3])
c = frozenset([3]).union([5])
print a==b, b==c
print a is b, b is c, a is c

导致:

True True
False False False

但这是

universe = Uniquifier()
a = universe.uniquify(frozenset([3,5]))
b = universe.uniquify(frozenset([5,3]))
c = universe.uniquify(frozenset([3]).union([5]))
print a == b, b==c
print a is b, b is c, a is c

给予

True True
True True True

根据需要。

我希望使用一些Python魔术可以将Uniquifier逻辑隐藏在“幕后”,但是我想这具有简单明了的优势。

答案 1 :(得分:0)

您可以使用__new__方法在frozenset周围创建包装器。我引用the doc

  

()主要用于允许不可变类型(例如int,str或tuple)的子类自定义实例创建。在自定义元类中也通常会覆盖它,以自定义类的创建。

这个想法是缓存每个创建的包装,并为相同的frozenset返回始终相同的实例。

有一个小技巧:frozenset本身就是frozenset的元素,也应该包装它们。

class FrozenSetWrapper:
    _cache = {}

    def __new__(cls, iterable=[]):
        fs = frozenset(FrozenSetWrapper(e) if isinstance(e, frozenset) else e
                        for e in iterable) # wrap recursively
        fsw = FrozenSetWrapper._cache.get(fs)
        if fsw is None: # was not in cache
            fsw = super(FrozenSetWrapper, cls).__new__(cls) # create an object
            fsw._fs = fs # init
            fsw.__doc__ = fs.__doc__
            FrozenSetWrapper._cache[fs] = fsw # cache
        return fsw # and return

示例:

f1 = FrozenSetWrapper([1,2,3])
f2 = FrozenSetWrapper([1,2,3])

print(f1, f2)
# <__main__.FrozenSetWrapper object at 0x7f7894f2fa90> <__main__.FrozenSetWrapper object at 0x7f7894f2fa90>

现在,我们必须重新实现frozenset的方法才能获得完美的匹配。对于其中一些人来说这很容易:只需将工作委托给包装好的frozenset

def __repr__(self):
    return self._fs.__repr__()

def __iter__(self):
    return self._fs.__iter__()

...

但是对于某些方法,您必须处理frozensetFrozenSetWrapper

def __contains__(self, e):
    elif isinstance(e, frozenset):
        e = FrozenSetWrapper(e)

    return self._fs.contains(e)

def __eq__(self, other):
    if isinstance(other, FrozenSetWrapper):
        return self is other
    elif isinstance(other, frozenset)
        return self._fs == other
    else:
        return False

...

或返回类型:

def __and__(self, other):
    if isinstance(other, FrozenSetWrapper):
        return FrozenSetWrapper(self._fs.__and__(other._fs))
    elif isinstance(other, frozenset):
        return FrozenSetWrapper(self._fs.__and__(other))
    else:
        raise TypeError("unsupported operand type(s) ...")

实习的想法很有意义,但由于情况极端,实施起来可能很棘手。