Python对象的唯一表示

时间:2013-08-28 21:45:28

标签: python object caching constructor decorator

设C为Python类,并假设C的构造函数将整数作为参数。

现在考虑说明

x = C(0)
y = C(0)

Python的默认行为意味着x和y在内存中占据两个不同的位置。

  

是否可以强制x和y在内存中共享相同的位置?

如果有一些Python装饰工作,我会非常高兴。

[注意] 我正在寻找一种记忆构造函数的方法(有关函数的记忆,请参阅http://en.wikipedia.org/wiki/Memoization。)

[添加] Sage开源数学软件通过课程UniqueRepresentation为此问题提供了一个非常好的解决方案(参见here)。任何类都应该继承此类以具有预期的行为。不过,我想知道是否有一个纯Python解决方案来解决这个问题。

3 个答案:

答案 0 :(得分:2)

您可能想要使用lru_cache。如果您的班级定义是

@lru_cache(maxsize=32)
class C(object):
    def __init__(self, num):
        self.num = num

然后它表现得像

>>> a = C(1)
>>> a.num = 2
>>> b = C(1)
>>> b.num
2
>>> a is b
True

但是,这使名称C成为一个函数,并且在实际实例化类之前,任何类功能都不可用。如果需要,还可以直接缓存方法__new__,该方法负责创建对象。 __new__是一个方法,它接受与__init__相同的所有参数,并在创建类实例时在__init__之前调用它。

由于缓存__new__的输出很简单,我们可以让事情变得更有趣。让我们创建一个新的装饰器,它就像lru_cache一样工作,但它可以与类一起使用来缓存__new__的输出:

def lru_cache_class(maxsize):
    def wrap(klass):
        @lru_cache(maxsize=maxsize)
        def new(cls, *args, **kwargs):
            self = object.__new__(cls)
            return self
        klass.__new__ = new
        return klass
    return wrap

我们给__new__所有可能的参数和关键字参数,以便它也可以与其他类一起使用。现在我们可以像这样缓存类C2的实例:

@lru_cache_class(maxsize=32)
class C2(object):
    def __init__(self, num):
        self.num = num

我们可以看到对象被缓存:

>>> c = C2(2)
>>> c is C2(2)
True
然而,与第一种方法相比,这种方法存在另一个细微差别。例如:

>>> d = C2(3)
>>> d.num = 4
>>> d.num
4
>>> e = C2(3)
>>> d.num == e.num
>>> d.num
3

此行为是预期的,因为无论如何都会调用__init__,尽管对象的内存位置保持不变。根据您的使用情况,您可能还想缓存__init__的输出。

答案 1 :(得分:1)

您可以覆盖__new__来存储每个对象的缓存版本:

class C(object):
    _cache = {}

    def __new__(cls, x):
        if x not in C._cache:
            C._cache[x] = object.__new__(cls, x)
        return C._cache[x]

    def __init__(self, x):
        self.x = x

演示:

>>> a = C(1)
>>> b = C(1)
>>> a is b
True
>>> id(a) == id(b)
True

显然,如果您稍后更改x而不是创建新类,则它将不会成为与之前使用x的值定义的对象相同的对象:

>>> a = C(1)
>>> b = C(2)
>>> a.x = 2
>>> a is b
False

答案 2 :(得分:0)

如果您愿意让函数为您创建类实例,这可能会有效。假设你的班级C接受一个整数:

def C_getter(num, _class_archive={}):
    """\
    Returns an instance of the `C` class,
    making sure that if an object already exists with that
    integer number a new object is not created.

    The _class_archive is used to keep a record of all the instances
    in memory local to this function.  Don't actually supply an
    argument to _class_archive when you call this function.
    """

    if num not in _class_archive:
        _class_archive[num] = C(num)
    return _class_archive[num]

像这样使用:

>>> a = C_getter(0)
>>> b = C_getter(0)
>>> a is b
True
>>> c = C(0)
>>> a is c
False

我正在利用这样一个事实:如果你使用一个可变对象作为一个函数的默认参数,每次调用该函数时都会使用相同的可变对象。

修改

如果您想使这个通用(假设您的所有类都需要一个数字),您可以执行以下操作:

def getter(your_class, num, _class_archive={}):
    if (your_class, num) not in _class_archive:
        _class_archive[(your_class, num)] = your_class(num)
    return _class_archive[(your_class, num)]

你可以像这样使用它:

>>> a = getter(C, 0)
>>> b = getter(C, 0)
>>> c = getter(A, 0)