7

我正在解决一个问题,我正在实例化一个对象的许多实例。大多数情况下,实例化的对象是相同的。为了减少内存开销,我想让所有相同的对象指向同一个地址。但是,当我修改对象时,我希望创建一个新实例——本质上是写时复制行为。在 Python 中实现这一目标的最佳方法是什么?

享元模式接近。一个例子(来自http://codesnipers.com/?q=python-flyweights):

import weakref

class Card(object):
    _CardPool = weakref.WeakValueDictionary()
    def __new__(cls, value, suit):
        obj = Card._CardPool.get(value + suit, None)
        if not obj:
            obj = object.__new__(cls)
            Card._CardPool[value + suit] = obj
            obj.value, obj.suit = value, suit
        return obj

其行为如下:

>>> c1 = Card('10', 'd')
>>> c2 = Card('10', 'd')
>>> id(c1) == id(c2)
True
>>> c2.suit = 's'
>>> c1.suit
's'
>>> id(c1) == id(c2)
True

期望的行为是:

>>> c1 = Card('10', 'd')
>>> c2 = Card('10', 'd')
>>> id(c1) == id(c2)
True
>>> c2.suit = 's'
>>> c1.suit
'd'
>>> id(c1) == id(c2)
False

更新:我遇到了享元模式,它似乎几乎符合要求。但是,我对其他方法持开放态度。

4

3 回答 3

6

您是否需要id(c1)==id(c2)完全相同,或者这只是一个演示,真正的目标是避免创建重复的对象?

一种方法是让每个对象都是不同的,但像上面一样保持对“真实”对象的内部引用。然后,在任何__setattr__呼叫中,更改内部参考。

我以前从未做过任何__setattr__事情,但我认为它看起来像这样:

class MyObj:
    def __init__(self, value, suit):
        self._internal = Card(value, suit)

    def __setattr__(self, name, new_value):
        if name == 'suit':
            self._internal = Card(value, new_value)
        else:
            self._internal = Card(new_value, suit)

同样,通过 . 公开属性getattr

您仍然有很多重复的对象,但它们后面只有一个“真实”支持对象的副本。因此,如果每个物体都是巨大的,这会有所帮助,如果它们是轻量级的,这将无济于事,但你有数百万个。

于 2012-09-10T21:18:57.653 回答
3

不可能的。

id(c1) == id(c2)

这么说c1并且c2是对完全相同的对象的引用。所以

c2.suit = 's'和说的完全一样c1.suit = 's'

Python 无法区分这两者(除非您允许对先前的调用帧进行自省,这会导致恶意破解。)

由于这两个赋值是相同的,Python 无法知道这c2.suit = 's'应该导致名称 c2引用不同的对象。


为了让您了解肮脏的黑客会是什么样子,

import traceback
import re
import sys
import weakref

class Card(object):
    _CardPool = weakref.WeakValueDictionary()
    def __new__(cls, value, suit):
        obj = Card._CardPool.get(value + suit, None)
        if not obj:
            obj = object.__new__(cls)
            Card._CardPool[value + suit] = obj
            obj._value, obj._suit = value, suit
        return obj
    @property
    def suit(self):
        return self._suit
    @suit.setter
    def suit(self, suit):
        filename,line_number,function_name,text=traceback.extract_stack()[-2]
        name = text[:text.find('.suit')]
        setattr(sys.modules['__main__'], name, Card(self._value, suit))

c1 = Card('10', 'd')
c2 = Card('10', 'd')
assert id(c1) == id(c2)

c2.suit = 's'
print(c1.suit)
# 'd'

assert id(c1) != id(c2)

这种对回溯的使用仅适用于那些使用框架的 Python 实现,例如 CPython,但不适用于 Jython 或 IronPython。

另一个问题是

name = text[:text.find('.suit')]

非常脆弱,并且会搞砸,例如,如果分配看起来像

if True: c2.suit = 's'

或者

c2.suit = (
    's')

或者

setattr(c2, 'suit', 's')

还有一个问题是它假定名称c2是全局的。它可以很容易地成为局部变量(例如,在函数内部)或属性 ( obj.c2.suit = 's')。

我不知道一种方法来解决可以进行分配的所有方式。

在任何这些情况下,肮脏的黑客都会失败。

结论:不要使用它。:)

于 2012-09-10T21:14:24.470 回答
0

这在您目前的形式中是不可能的。名称(c1c2您的示例中)一个引用,您不能简单地使用 更改引用__setattr__,更不用说对同一对象的所有其他引用。

这可能的唯一方法是这样的:

c1 = c1.changesuit("s")

Wherec1.changesuit返回对(新创建的)对象的引用。但这仅在每个对象仅由一个名称引用时才有效。或者,您也许可以用locals()类似的东西做一些魔术,但是请-不要。

于 2012-09-10T21:15:01.903 回答