簡體   English   中英

Python對象的唯一表示形式

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM