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中的尸体那样有代码味道