3

假设 C 是一个 Python 类,并假设 C 的构造函数接受一个整数作为参数。

现在考虑说明

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

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

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

如果一些 Python 装饰器能完成这项工作,我会非常高兴。

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

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

4

3 回答 3

5

您可能想使用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__

于 2015-05-08T09:24:45.297 回答
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
于 2015-05-17T08:49:09.713 回答
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)
于 2013-08-29T03:59:49.120 回答