python w/o 子类中用户定义的不可变

User defined immutables in python w/o subclassing

我知道你显然不是 应该 能够在 Python 中定义你自己的不可变对象 classes(为什么不?),但我有充分的理由想要这样做。

这个对象需要具有任意属性(也就是说,它的属性是在实例化时指定的,唯一的要求是它们的值是可散列的)而且 需要是不可变的,因为我的体系结构要求它用作字典键(如果有人关心的话,可以查找回调函数)。

这是我目前得到的:

class InputEvent:
    """Instances of this class represent a discrete input event, such as a keyboard button being depressed or released,
    the mouse moving, or the OS asking us to terminate. These are immutable. The .eventtype attr is a string with a
    machine name identifying what caused the event; other parameters may be present dependent on the event type.

    Additional parameters may be passed in to the constructor as keyword arguments. All parameters must be hashable."""


    def __new__(cls,eventtype,**kwargs):
        newevent=super().__new__(cls)
        newevent.eventtype=eventtype
        for attr,value in kwargs.items():
            try:
                hash(value)
            except TypeError:
                raise TypeError("Tried to add a {0} instance, which is unhashable, as a parameter of an InputEvent".format(value.__class__))
            setattr(newevent,attr,value)
        newevent.__setattr__=newevent.__ro_setattr__
        return newevent

    def __hash__(self):
        return sum(hash(theval) for theval in self.__dict__.values())

    def __eq__(self, other):
        return hash(self)==hash(other)

    def __ro_setattr__(self, key, value):
        raise AttributeError("'{0}' object attribute '{1}' is read-only".format(self.__class__.__name__,key))

对于那些被认为是不可能的事情,它的效果非常好;唯一的问题是 newevent.__setattr__=newevent.__ro_setattr__ 没有效果;如果我将它定义为 __setattr__ 而没有“ro_”,它会产生预期的效果,但这会产生令人不快的副作用,即我也无法在 __new__() 中设置属性。

我知道 Python 是在同意的成年人之间,但另一方面,错误经常发生。所以我想扼杀那些特别偷偷摸摸的东西,比如在他们浪费我几天的时间之前不小心在萌芽状态改变了字典键的值。是的,我可以屈服并 subclass string,但那将是 wroooong~。如果调用者是 InputEvent.__new__,我也可以修改 __setattr__ 以展开堆栈并不会出错,但从正确性的角度来看,这很丑陋,可以说 甚至更糟 , and 那时我开始担心性能 - 这是一个视频游戏,输入需要 fast!

那么我怎样才能堵住这最后一个漏洞呢?我怎样才能使我假定的不可变实际上拒绝从除它自己的所有东西写入的属性 class' __new__() 而不求助于丑陋的堆栈 hax?

在我看来,您应该能够使用 __slots__ 并且 @property 装饰器应该做得很好。

In [1]: class Foo(object):
...         __slots__ = ['__thisattr', '__thatattr']
...         def __init__(self, **kwargs):
...             for name,val in kwargs.items():
...                setattr(self, "__"+name, val)
...         @property
...         def thisattr(self):
...             return self.__thisattr
...         @property
...         def thatattr(self):
...             return self.__thatattr

In [2]: f = Foo(thisattr="this", thatattr="that")

In [3]: f.thisattr
Out[3]: this

In [4]: f.thatattr
Out[4]: that

In [5]: f.thisattr = "Something Else"
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-5-160d2a000ade> in <module>()
----> 1 f.thisattr = "something else"

AttributeError: can't set attribute

您当然仍然可以 f._Foo__thatattr = "Something else",但在这一点上您是在故意违反安全条款,不是吗?这不是真的 "consenting adults" 如果你四处走动试图破坏东西!

这不能满足问题,但为了完整起见,我现在使用的代码如下:

    def __setattr__(self, key, value):
        if not sys._getframe(1).f_code.co_name=="__new__":
            raise AttributeError("'{0}' object attribute '{1}' is read-only".format(self.__class__.__name__,key))
        else:
            object.__setattr__(self,key,value)

这只是查看调用它的函数是否被命名为 __new__;这很可能会在未来导致并发症,但它确实有效。不过,我不确定每次访问属性时进行检查的性能特征是什么。