Python 强制相同类的类似实例具有相同的引用

Python 强制相同类的类似实例具有相同的引用,python,reference,Python,Reference,有没有一种方法可以使一个用户定义的类像int那样运行,因为任何相同的实例都有相同的referent 例如: 但是对于这样一个用户定义的类: class Variable: def __init__(self, letter, index): self.letter = letter self.index = int(index) def __str__(self): return self.letter + '_' + str(self.index) 我们有以下几点:

有没有一种方法可以使一个用户定义的类像
int
那样运行,因为任何相同的实例都有相同的referent

例如:

但是对于这样一个用户定义的类:

class Variable:
def __init__(self, letter, index):
    self.letter = letter
    self.index = int(index)

def __str__(self):
    return self.letter + '_' + str(self.index)
我们有以下几点:

>>> a = Variable('x',1)
>>> b = Variable('x',1)
>>> a == b
True
>>> a is b
False
a = Variable('x', 0)
b = Variable('x', 0)
a.letter = 'y'
有没有一种方法可以让用户定义的类像int一样运行,因为任何相等的实例都有相同的referent

首先,只有有限数量的整数有这种行为;出于性能和内存效率的考虑,小整数被保留(请参阅)

您所要求的是如何确保您自己的实例被拘留,因为对于给定的“值”,一个实例只有一个副本。您可以通过控制创建新实例的时间,实现您自己的:

对于给定的
字母
索引
组合,只创建一个实例:

>>> a = Variable('a', 1)
>>> b = Variable('a', 1)
>>> a
<__main__.Variable object at 0x10858ceb8>
>>> b
<__main__.Variable object at 0x10858ceb8>
>>> a is b
True
>a=变量('a',1)
>>>b=变量('a',1)
>>>a
>>>b
>>>a是b
真的
这也是integer interning的工作原理

有没有一种方法可以让用户定义的类像int一样运行,因为任何相等的实例都有相同的referent

首先,只有有限数量的整数有这种行为;出于性能和内存效率的考虑,小整数被保留(请参阅)

您所要求的是如何确保您自己的实例被拘留,因为对于给定的“值”,一个实例只有一个副本。您可以通过控制创建新实例的时间,实现您自己的:

对于给定的
字母
索引
组合,只创建一个实例:

>>> a = Variable('a', 1)
>>> b = Variable('a', 1)
>>> a
<__main__.Variable object at 0x10858ceb8>
>>> b
<__main__.Variable object at 0x10858ceb8>
>>> a is b
True
>a=变量('a',1)
>>>b=变量('a',1)
>>>a
>>>b
>>>a是b
真的
这也是integer interning的基本工作原理。

'答案很接近于一个对实际用途有用的答案(我投了赞成票),但我对'关于易变性的观点很感兴趣。例如,使用Martijn的解决方案,以下操作失败:

a = Variable('x', 0)
b = Variable('x', 0)
c = Variable('y', 0)
a.letter = c.letter
assert(a is c)
我们希望相同的实例总是引用内存中的同一对象。这是非常棘手的,需要一些黑魔法,永远不应该在实际应用中使用,但在某种意义上是可能的。所以,如果你是为了好玩,就一起去兜风吧

我的第一个想法是,我们需要为变量重载_setattr _uuu,以便在属性更改时,创建具有适当属性值的新实例,并更新对原始实例的所有引用(脚注1)以指向此新实例。这是可能的,但事实证明并没有给我们提供完全正确的解决方案。如果我们采取以下措施:

>>> a = Variable('x',1)
>>> b = Variable('x',1)
>>> a == b
True
>>> a is b
False
a = Variable('x', 0)
b = Variable('x', 0)
a.letter = 'y'
在最后一次赋值的过程中,更新对被称为
a
的对象的所有引用,然后
b
也将以
b结束。字母=='y'
,因为
a
b
(显然)引用相同的实例

因此,这不是更新变量实例的所有引用的问题。这是一个更新我们刚刚更改的引用的问题。也就是说,对于调用属性赋值的名称空间,我们需要更新局部变量以指向新实例。这并不简单,但这里有一个方法可以用于我能想到的所有测试。请注意,这段代码并没有太多的代码气味,而是一具满是尸体的尸体在壁橱里呆了三天,代码散发着臭味。同样,不要将其用于任何严重的情况:

import inspect
import dis

class MutableVariable(object):
    __slots__ = ('letter', 'index')  # Prevent access through __dict__
    previously_created = {}

    def __new__(cls, letter, index):
        if (letter, index) in cls.previously_created:
            return cls.previously_created[(letter, index)]
        else:
            return super().__new__(cls)

    def __setattr__(self, name, value):
        letter = self.letter
        index = self.index
        if name == "letter":
            letter = value
        elif name == "index":
            index = int(value)
        # Get bytecode for frame in which attribute assignment occurred
        frame = inspect.currentframe()
        bcode = dis.Bytecode(frame.f_back.f_code)
        # Get index of last executed instruction
        last_inst = frame.f_back.f_lasti
        # Get locals dictionary from namespace in which assignment occurred
        call_locals = frame.f_back.f_locals
        assign_name = []
        attribute_name = []
        for instr in bcode:
            if instr.offset > last_inst:  # Only go to last executed instruction
                break
            if instr.opname == "POP_TOP":  # Clear if popping stack
                assign_name = []
                attribute_name = []
            elif instr.opname == "LOAD_NAME":  # Keep track of name loading on stack
                assign_name.append(instr.argrepr)
            elif instr.opname == "LOAD_ATTR":  # Keep track of attribute loading on stack
                attribute_name.append(instr.argrepr)
            last_instr = instr.opname  # Opname of last executed instruction
        try:
            name_index = assign_name.index('setattr') + 1  # Check for setattr call
        except ValueError:
            if last_instr == 'STORE_ATTR':  # Check for direct attr assignment
                name_index = -1
            else:  # __setattr__ called directly
                name_index = 0
        assign_name = assign_name[name_index]
        # Handle case where we are assigning to attribute of an attribute

        try:
            attributes = attribute_name[attribute_name.index(name) + 1: -1]
            attribute_name = attribute_name[-1]
        except (IndexError, ValueError):
            attributes = []
        if len(attributes):
            obj = call_locals[assign_name]
            for attribute_ in attributes:
                obj = getattr(obj, attribute_)
            setattr(obj, attribute_name, MutableVariable(letter, index))
        else:
            call_locals[assign_name] = MutableVariable(letter, index)

    def __init__(self, letter, index):
        super().__setattr__("letter", letter)  # Use parent's setattr on instance initialization
        super().__setattr__("index", index)
        self.previously_created[(letter, index)] = self

    def __str__(self):
        return self.letter + '_' + str(self.index)

# And now to test it all out...
if __name__ == "__main__":
    a = MutableVariable('x', 0)
    b = MutableVariable('x', 0)
    c = MutableVariable('y', 0)
    assert(a == b)
    assert(a is b)
    assert(a != c)
    assert(a is not c)

    a.letter = c.letter
    assert(a != b)
    assert(a is not b)
    assert(a == c)
    assert(a is c)

    setattr(a, 'letter', b.letter)
    assert(a == b)
    assert(a is b)
    assert(a != c)
    assert(a is not c)

    a.__setattr__('letter', c.letter)
    assert(a != b)
    assert(a is not b)
    assert(a == c)
    assert(a is c)

    def x():
        pass

    def y():
        pass

    def z():
        pass

    x.testz = z
    x.testz.testy = y
    x.testz.testy.testb = b
    x.testz.testy.testb.letter = c.letter
    assert(x.testz.testy.testb != b)
    assert(x.testz.testy.testb is not b)
    assert(x.testz.testy.testb == c)
    assert(x.testz.testy.testb is c)
因此,基本上我们在这里所做的是用来分析赋值发生的帧的字节码(如所报告的)。使用此方法,我们提取引用正在进行属性分配的MutableVariable实例的变量的名称,并更新相应命名空间的locals字典,以便该变量引用新的MutableVariable实例。这些都不是好主意

这里显示的代码几乎可以肯定是特定于实现的,可能是我所写过的最脆弱的代码,但它在标准CPython 3.5.2上确实有效

脚注1:请注意,这里我使用的不是形式(例如C++)意义上的引用(因为),而是引用内存中特定对象的变量。i、 e.在“参考计数”的意义上,而不是“指针与参考。”

'答案很接近您将得到一个对实际用途有用的答案(得到我的赞成票),但我感兴趣的是'关于可变性的观点。例如,使用Martijn的解决方案,以下操作失败:

a = Variable('x', 0)
b = Variable('x', 0)
c = Variable('y', 0)
a.letter = c.letter
assert(a is c)
我们希望相同的实例总是引用内存中的同一对象。这是非常棘手的,需要一些黑魔法,永远不应该在实际应用中使用,但在某种意义上是可能的。所以,如果你是为了好玩,就一起去兜风吧

我的第一个想法是,我们需要为变量重载_setattr _uuu,以便在属性更改时,创建具有适当属性值的新实例,并更新对原始实例的所有引用(脚注1)以指向此新实例。这是可能的,但事实证明并没有给我们提供完全正确的解决方案。如果我们采取以下措施:

>>> a = Variable('x',1)
>>> b = Variable('x',1)
>>> a == b
True
>>> a is b
False
a = Variable('x', 0)
b = Variable('x', 0)
a.letter = 'y'
在最后一次赋值的过程中,更新对被称为
a
的对象的所有引用,然后
b
也将以
b结束。字母=='y'
,因为
a
b
(显然)引用相同的实例

因此,这不是更新变量实例的所有引用的问题。这是一个更新我们刚刚更改的引用的问题。也就是说,对于调用属性赋值的名称空间,我们需要更新局部变量以指向新实例。这并不简单,但这里有一个方法可以用于我能想到的所有测试。请注意,这段代码不像c中的尸体那样有代码味道