有没有办法让用户定义的类像int
那样运行,因为任何相等的实例都具有相同的指示对象?
E.g:
>>> a = 2
>>> b = 2
>>> a == b
True
>>> a is b
True
但是用户定义的类就像这样:
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
答案 0 :(得分:2)
有没有办法让用户定义的类像int一样操作,因为任何相等的实例都具有相同的指示对象?
首先,只有有限数量的整数表现得那样;出于性能和内存效率的原因,小整数被实现(参见"is" operator behaves unexpectedly with integers)。
您要求的是如何确保您自己的实例被实习,因为只有一个实例的副本可用于给定的值'。您可以通过控制何时创建新实例,通过实现自己的__new__
method:
class Variable:
_instances = {}
def __new__(cls, letter, index):
index = int(index)
try:
# return existing instance
return cls._instances[letter, index]
except KeyError:
# no instance yet, create a new one
instance = super().__new__(cls)
instance._letter = letter
instance._index = index
cls._instances[letter, index] = instance
return instance
def __str__(self):
return self._letter + '_' + str(self._index)
对于给定的letter
和index
组合,只创建一个实例:
>>> a = Variable('a', 1)
>>> b = Variable('a', 1)
>>> a
<__main__.Variable object at 0x10858ceb8>
>>> b
<__main__.Variable object at 0x10858ceb8>
>>> a is b
True
这实质上是整数实习的工作方式。
答案 1 :(得分:1)
Martijn Pieters&#39;答案就像你接近得到一个对实际目的有用的答案(得到我的upvote),但我对johnrsharpe关于可变性的观点感兴趣。例如,使用Martijn的解决方案,以下操作失败:
a = Variable('x', 0)
b = Variable('x', 0)
c = Variable('y', 0)
a.letter = c.letter
assert(a is c)
我们希望始终的相等实例引用内存中的同一对象。这非常棘手,需要一些黑魔法,并且永远不会在真实应用程序中使用,但在某种意义上是可能的。所以,如果你因为笑声而来这里,那就来吧。
我的第一个想法是我们需要为变量重载__setattr__,以便在属性更改时,创建具有适当属性值的新实例,并更新原始实例的所有引用(脚注1)以指向此新实例。 pyjack可以实现这一点,但事实证明并没有给我们提供正确的解决方案。如果我们执行以下操作:
a = Variable('x', 0)
b = Variable('x', 0)
a.letter = 'y'
并且在最后一次分配更新所有对称为a
的对象的引用的过程中,b
也将以b.letter == 'y'
结束a
和b
(显然)指的是同一个实例。
因此,它不是更新对Variable实例的所有引用的问题。这是更新我们刚刚更改的一个引用的问题。也就是说,对于调用属性赋值的命名空间,我们需要更新locals以指向新实例。这不是直截了当的,但这里有一个方法可以解决我能想到的所有测试。请注意,这段代码没有太多的代码味道,因为它是关于它的全天候尸体中的三天代码。再次,不将它用于任何严肃的事情:
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)
因此,基本上我们在这里做的是使用dis来分析发生分配的帧的字节码(由inspect报告)。使用它,我们提取引用属性赋值的MutableVariable实例的变量的名称,并更新相应命名空间的locals字典,以便该变量引用一个新的MutableVariable实例。这些都不是一个好主意。
此处显示的代码几乎是当然特定于实现,可能是我编写过的最脆弱的代码段,但它确实适用于标准的CPython 3.5.2。
脚注1:请注意,在这里,我没有使用正式(例如C ++)意义上的引用(因为Python is not pass by reference),而是在引用内存中特定对象的变量意义上。即,&#34;引用计数&#34;不是&#34;指针与参考。&#34;