[英]Unique representations of Python objects
令C為Python類,並假定C的構造函數采用整數作為參數。
現在考慮說明
x = C(0)
y = C(0)
Python的默認行為意味着x和y在內存中占據兩個不同的位置。
是否可以強制x和y共享內存中的相同位置?
如果某些Python裝飾器勝任這項工作,我將非常高興。
[注意]我正在尋找一種記憶構造函數的方法(有關函數的記憶,請參見http://en.wikipedia.org/wiki/Memoization )。
[添加] Sage開源數學軟件通過類UniqueRepresentation
(請參見此處 )為該問題提供了很好的解決方案。 任何類都應從這一類繼承以具有預期的行為。 不過,我想知道是否存在針對此問題的純Python解決方案。
您可能要使用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__
的輸出。
您可以重寫__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
如果您願意讓一個函數為您創建類實例,則可能會起作用。 假設您的類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)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.