我正在研究一个问题,我正在实例化一个对象的许多实例。大多数情况下,实例化的对象是相同的。为了减少内存开销,我想让所有相同的对象指向同一个地址。但是,当我修改对象时,我想要创建一个新实例 - 基本上是写时复制行为。在Python中实现这一目标的最佳方法是什么?
Flyweight模式接近尾声。一个例子(来自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
更新:我遇到了Flyweight模式,它似乎几乎适合该法案。但是,我对其他方法持开放态度。
答案 0 :(得分: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
公开属性。
你仍然有很多重复的对象,但只有一个副本的“真实”支持对象。因此,如果每个对象都很庞大,这将有所帮助,如果它们是轻量级的,那将无济于事,但是你有数百万个。
答案 1 :(得分: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'
)。
我不知道如何解决分配的所有方式。
在任何一种情况下,肮脏的黑客都会失败。
结论:不要使用它。 :)
答案 2 :(得分:0)
目前的形式是不可能的。您的示例中的名称(c1
和c2
) 是引用,您不能只使用__setattr__
更改引用,更不用说所有其他引用同一个对象。
唯一可行的方法是:
c1 = c1.changesuit("s")
c1.changesuit
返回对(新创建的)对象的引用。但这只适用于每个对象仅由一个名称引用的情况。或者你可能能够使用locals()
和类似的东西做一些魔法,但请 - 不要。