Python对象的唯一表示形式
假设C是一个Python类,并假设C的构造函数以整数作为参数 现在考虑一下说明Python对象的唯一表示形式,python,object,caching,constructor,decorator,Python,Object,Caching,Constructor,Decorator,假设C是一个Python类,并假设C的构造函数以整数作为参数 现在考虑一下说明 x = C(0) y = C(0) Python的默认行为意味着x和y在内存中占据两个不同的位置 可以强制x和y在内存中共享同一个位置吗 如果某个Python装饰师能胜任这项工作,我会非常高兴 [注意]我正在寻找一种记忆构造函数的方法(有关函数的记忆,请参阅) [Add]Sage开源数学软件通过类UniqueRepresentation(请参阅)为这个问题提供了一个非常好的解决方案。任何类都应从该类继承,以具有预期
x = C(0)
y = C(0)
Python的默认行为意味着x和y在内存中占据两个不同的位置
可以强制x和y在内存中共享同一个位置吗
如果某个Python装饰师能胜任这项工作,我会非常高兴
[注意]我正在寻找一种记忆构造函数的方法(有关函数的记忆,请参阅)
[Add]Sage开源数学软件通过类
UniqueRepresentation
(请参阅)为这个问题提供了一个非常好的解决方案。任何类都应从该类继承,以具有预期的行为。然而,我想知道是否有一个纯Python的解决方案来解决这个问题。如果您愿意让一个函数为您创建类实例,这可能会奏效。假设您的类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
>>> a = getter(C, 0)
>>> b = getter(C, 0)
>>> c = getter(A, 0)
@lru_cache_class(maxsize=32)
class C2(object):
def __init__(self, num):
self.num = num
我利用了这样一个事实:如果使用可变对象作为函数的默认参数,那么每次调用函数时都会使用相同的可变对象
编辑
如果要使其成为泛型(假设所有类都需要一个数字),可以执行以下操作:
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 = C_getter(0)
>>> b = C_getter(0)
>>> a is b
True
>>> c = C(0)
>>> a is c
False
>>> a = getter(C, 0)
>>> b = getter(C, 0)
>>> c = getter(A, 0)
@lru_cache_class(maxsize=32)
class C2(object):
def __init__(self, num):
self.num = num
您可能想使用。如果您的类定义是
@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
成为一个函数,在类实际实例化之前,任何类功能都不可用。如果需要,还可以直接缓存负责创建对象的方法\uuuu new\uuuu
\uuuuuuuuuuuuuu
是一种方法,它采用与\uuuuuu init\uuuuuuu
相同的所有参数,当我们创建类实例时,它在\uuuuuu init\uuuuuuuuuuuuu>之前被调用
由于缓存\uuuuu new\uuuuuu
的输出非常简单,我们可以让事情变得更加有趣。让我们创建一个新的decorator,它的工作原理与lru\u cache
类似,但它可以与类一起用于缓存\uuuu new\uuu
的输出:
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
我们给出了\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。现在我们可以像这样缓存classC2
的实例:
>>> a = C_getter(0)
>>> b = C_getter(0)
>>> a is b
True
>>> c = C(0)
>>> a is c
False
>>> a = getter(C, 0)
>>> b = getter(C, 0)
>>> c = getter(A, 0)
@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
这种行为是预期的,因为尽管对象的内存位置保持不变,但仍会调用\uuuu init\uuuu
。根据您的使用情况,您可能还需要缓存\uuuuu init\uuuu
的输出。您只需覆盖\uuuu new\uuuu
即可存储每个对象的缓存版本:
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
嗯。。。为什么不x=y=C(0)
?或x=C(0);y=x
?但为什么x=y=C(0)?我想,当两个物体在数学上相等时,它们共享同一个记忆位置。假设x=C(0)被定义为一个函数f的局部变量,y=C(0)被定义为另一个函数g的局部变量。您是否考虑按需复制;比如当您使用fork()
时会发生什么?另外,测试x==y
意义上的相等性是\uuuu eq\uuuu
的目的,我正在寻找一种方法来记忆构造函数。请解释记忆构造函数
谢谢你的回答!但是,这个解决方案在以下意义上不是通用的:您必须为每个类定义一个函数A_getter。此外,我认为将此函数定义为C的静态方法或类方法更为准确。这也很有效,但我认为遗憾的是,(1)必须通过构造函数构造对象,(2)必须为任何类定义相同的静态(或类)方法。@SamueleGiraudo我使其更为通用,但它仍然不能满足你所有的标准。这是一个很好的解决方案,即使它确实不能满足我所有的标准(对不起,我不能投票,因为我没有足够的声誉)。也许,可以通过将参数num
替换为*args
来稍微改进它,以涵盖构造函数需要任何参数的情况。@SamueleGiraudo我想到了这一点,但在python 2.x中,这要求\u class\u archive
在*args
之前,然后在调用函数时,必须为\u class\u archive
提供一个参数,这完全违背了函数的目的。这是一个很好的解决方案,但问题是decorator@lru\u cache
将符号C
转换为函数标识符。有没有类似的解决方案可以保留C
是类这一事实?你是对的。我刚刚编辑了我的答案以显示另一种方式,它将名称C
保留为一个类。谢谢,这看起来非常棒!尽管如此,拥有一个类装饰器来获得相同的行为还是非常有用的。类装饰器从输入类创建一个新类。这个新类丢失了第一个类的一些属性,比如它的doctstring。我认为一个优秀的解决方案不是创建并返回一个新类,而是向输入类添加一个\uuuuunew\uuuuuu
的记忆版本。您认为这是可行的吗?重写方法(在本例中是新的)只有通过继承才能实现。您对类docstring的担忧很容易得到解决——只需将\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu属性定义为父类的属性即可。如果您还想要一些漂亮的字符串表示,指向