【问题标题】:immutable objects in Python that can have weak referencesPython 中可以有弱引用的不可变对象
【发布时间】:2018-04-17 09:25:55
【问题描述】:

几年来,我一直在继承tuple 或使用namedtuple,但现在我有一个用例,我需要一个可以用作弱引用的类。今天我学到了tuples don't support weak references

还有其他方法可以在 Python 中创建具有固定属性集的不可变对象吗?我不需要元组的数字索引或可变宽度。

class SimpleThingWithMethods(object):
    def __init__(self, n, x):
        # I just need to store n and x as read-only attributes 
    ... ??? ...

我想这引发了一个显而易见的问题,即为什么不可变; “Pythonic”代码通常只是假设我们在这里都是成年人,如果有破坏类不变量的风险,没有人会进入一个类并弄乱它的值。就我而言,我在库中有一个类,我担心最终用户会意外修改对象。与我一起工作的人有时会对我的代码做出错误的假设并开始做我没想到的事情,所以如果他们不小心修改了我的代码,我可以引发错误会更干净。

我并不担心防弹的不变性;如果有人真的很邪恶,想去修改东西,好吧,好吧,他们靠自己。我只是想防止意外修改我的对象。

【问题讨论】:

  • Raymond(在邮件中)建议的解决方法有什么问题?
  • "使用 has-a 关系而不是 is-a 关系创建自定义类。" -- (1) 我想用纯 Python 来做,(2),has-a 暗示自定义类包含我的真实类,但是自定义类不是不可变的,因为鸡蛋和鸡蛋 -- 如果我可以创建一个不可变的类,然后我就去做。
  • 我想我也可以使用一个容器类来覆盖__eq____hash__ 并委托给包含的对象。不确定这是否适用于弱引用。
  • 很有趣,但这在我的代码中依赖于 Cython,我想避免它。

标签: python python-2.7 oop tuples


【解决方案1】:

对参数(getter?)使用封装和抽象怎么样:

class SimpleThingWithMethods(object):
    def __init__(self, n, x):
        self._n = n
        self._x = x

    def x(self):
      return self._x

    def n(self):
      return self._n

    SimpleThingWithMethods(2,3).x()

=> 3

【讨论】:

  • 不是一成不变的。请不要告诉我我有一个 XY 问题并且不可变类不是 Pythonic。 (附言你可以使用@property 使其语法更简洁)
  • @JasonS 我不会说任何关于 XY 的事情,有时你会被绑定到一个工具(就像你是 python),所以,在这种情况下,你必须打开一点头脑并尝试一些“不是工具”或“不是 pyhonic”的东西,我的意思是,你正在努力实现一个目标,如果你想要那个,你要么不使用 python,或者你可以尝试并行思考,Cython 选项是并行的方式也很有趣
  • 好的,很公平。我在这个网站上被讨厌的 Soup Nazis 咬了,他们尖叫着“XY 问题!”太多了,所以不幸的是,当有人发布的答案与我所说的假设不同时,我很快就会得出结论。
  • @JasonS 哈哈 完全不用担心,我不像这里的大多数程序员或用户,我可能有点天真,可能是软,可能试着善良哈哈,反正...我看到你自己的答案我会评论它
【解决方案2】:

好吧,这不是一个很好的答案,但看起来我可以修改https://stackoverflow.com/a/4828492/44330 中的答案——基本上覆盖__setattr____delattr__ 以满足我的需求,至少可以防止意外修改。 (但不如子类化tuple

class Point(object):
    __slots__ = ('x','y','__weakref__')
    def __init__(self, x, y):
        object.__setattr__(self, "x", x)
        object.__setattr__(self, "y", y)
    def __setattr__(self, *args):
        raise TypeError
    def __delattr__(self, *args):
        raise TypeError
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y
    def __hash__(self):
        return self.x.__hash__() * 31 + self.y.__hash__()

实现@Elazar 的想法:

class Point(object):
    __slots__ = ('x','y','__weakref__')
    def __new__(cls, x, y):
        thing = object.__new__(cls) 
        object.__setattr__(thing, "x", x)
        object.__setattr__(thing, "y", y)
        return thing
    def __setattr__(self, *args):
        raise TypeError
    def __delattr__(self, *args):
        raise TypeError
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y
    def __hash__(self):
        return self.x.__hash__() * 31 + self.y.__hash__()    

【讨论】:

  • 可能会覆盖__new__。防止调用p.__init__(1, 2)
  • 哦...这很晦涩,黑魔法哈哈...很多人可能会因为这种异端邪说而想把你烧成灰烬。我非常喜欢它,这就是我所说的“并行思维”,有时你必须大胆;)。顺便说一句,这是一个加一
  • Elazar 有什么办法做到这一点?
  • 只需添加def __new__(cls, x, y): ,在其中创建一个空对象并对其进行初始化。那么你不需要__init__ 做任何事情。
  • 注意:如果你定义__eq__,你也应该定义__ne__(在Python 2上——这里的问题有一个python-2.7标签)。
【解决方案3】:

如果你不担心isinstance检查,可以加强你的回答:

def Point(x, y):
    class Point(object):
        __slots__ = ('x','y','__weakref__')
        def __setattr__(self, *args):
            raise TypeError
        def __delattr__(self, *args):
            raise TypeError
        def __eq__(self, other):
            return x == other.x and y == other.y
        def __hash__(self):
            return x.__hash__() * 31 + y.__hash__()
    p = Point()
    object.__setattr__(p, "x", x)
    object.__setattr__(p, "y", y)
    return p

我真的不推荐它(每次调用都会创建一个类!),只是想说明这种可能性。

也可以一直使用javascript,并提供将访问局部变量的__getattr__。但这也会减慢访问速度,除了创建。现在我们根本不需要这些插槽:

class MetaImmutable:
    def __setattr__(self, name, val):
        raise TypeError

def Point(x, y):
    class Point(object):
        __metaclass__ = MetaImmutable
        __slots__ = ('__weakref__',)
        def __getattr__(self, name):
            if name == 'x': return x
            if name == 'y': return y
            raise TypeError
        @property
        def x(self): return x
        @property
        def y(self): return y
        def __eq__(self, other):
            return x == other.x and y == other.y
        def __hash__(self):
            return x.__hash__() * 31 + y.__hash__()
    return Point()

测试一下:

>>> p = Point(1, 2)
>>> p.y
2
>>> p.z
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in __getattr__
TypeError
>>> p.z = 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute 'z'
>>> object.__setattr__(p, 'z', 5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute 'z'
>>> from weakref import ref
>>> ref(p)().x
1
>>> type(p).x = property(lambda self: 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in __setattr__
TypeError

最后,你仍然可以打破它:

>>> type.__setattr__(type(p), 'x', property(lambda self: 5))
>>> p.x
5

同样,这里不推荐任何内容。使用@Jasons 实现。

【讨论】:

  • 我不明白包装器的意义是什么。也许您试图阻止重复的__init__ 呼叫,但任何想这样做的人都可能只是呼叫object.__setattr__。 (至少对我来说,首先想到的是object.__setattr__。)另外,你可以实现__new__,就像你评论杰森的回答一样。
  • (1) 对__init__ 的调用比object.__setattr__ 更常见。有些人不认为这是一种黑客行为。 (2) 包装器导致这些调用使对象行为不端,这可能会给他们上一课:P (3) 严重的是,如果您添加 __getattr__ 这将变得非常防弹
  • 注意:如果你定义__eq__,你也应该定义__ne__(在Python 2上——这里的问题有一个python-2.7标签)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-10
  • 1970-01-01
相关资源
最近更新 更多