Python 3 - 参考 Class 作为深度不可变(可散列)值的直接替换

Python 3 - Reference Class as Drop-In Replacement for Deeply Immutable (Hashable) Values

有什么办法可以:

  1. 以一种不仅会扼杀 Python 程序员期望的方式重写赋值?

    port = IntContainer(80)
    port = 8080             # set the IntContainer contents to 8080
                            # instead of changing what the port name references
                            # from the IntContainer(80) to int(8080)
    
  2. 覆盖丰富的比较器,使二进制比较运算符的参数顺序无关紧要?

    port = IntContainer(80)
    port < 80               # False
    80 > port               # TypeError
    

@BrenBarn:

Python 是什么版本?我正在使用 3.4.3:

class Foo(object):
    def __lt__(self, other):
        return self.value < other
    def __init__(self, other):
        self.value = other

foo = Foo(1)
foo < 1         # False
1 > foo         # raise TypeError()

为什么我想要这个:

  1. 我想要一个动态 HTTP 服务器进程配置对象。 PUT 和 POST 请求到某个 URL 的某个控制端口应该更新配置对象,并让它传播。
  2. 我认为轮询配置对象不是一个好主意,尤其是在并发上下文中。对于可组合性,你在哪里锁定而不会有死锁的风险?如果配置对象共享一个全局锁(这对于配置的预期变异率很好),则每次读取仍会获取该锁。如果每个 logging 消息都需要设置 Handler 级别,那显然是不好的。
  3. 因此,我想要一个发布-订阅配置对象。这允许配置用户在本地缓存和更新 pub 上的缓存值。这具有无锁并发安全的额外好处 - 发布消息始终可以在内部保持一致。 (构图需要一些注意,但还不错。)

我已经为 listdict 容器配置类型编写了代码。我使用 blinker 库来发送信号。这些对象很聪明;信号从叶子向上传播到包含它们的任何对象(不是树木或森林,因为它们是可重组的)。每个广播到他们的订阅者,其中包括其他容器配置,以及实际的用户订阅者。例如:

def receive_a(signal):
    print('Object 'a' received {} event at path {}; prev: {!r}, curr: {!r}'.format(
        signal.type, signal.path, signal.prev, signal.curr
    ))
config_dict_a = ConfigDict()
config_dict_a.connect(receive_a)

def receive_b(signal):
    print('Object 'b' received {} event at path {}; prev: {!r}, curr: {!r}'.format(
        signal.type.name, signal.path, signal.prev, signal.curr
    ))
config_dict_b = ConfigDict()
config_dict_b.connect(receive_b)

config_dict_a['b'] = config_dict_b
# Object 'a' received INSERT event at path ('b',); prev: None, curr: {}
# Object 'a' received UPDATE event at path (); prev: {}, curr: {'b': {}}
config_dict_b['foo'] = 42
# Object 'b' received INSERT event at path ('foo',); prev: None, curr: 42
# Object 'b' received UPDATE event at path (); prev: {}, curr: {'foo': 42}
# Object 'a' received INSERT event at path ('b', 'foo'); prev: None, curr: 42
# Object 'a' received UPDATE event at path ('b',); prev: {}, curr: {'foo': 42}
# Object 'a' received UPDATE event at path (); prev: {'b': {}}, curr: {'b': {'foo': 42}}
del config_a['b']['foo']
# Object 'b' received DELETE event at path ('foo',); prev: 42, curr: None
# Object 'b' received UPDATE event at path (); prev: {'foo': 42}, curr: {}
# Object 'a' received DELETE event at path ('b', 'foo',); prev: 42, curr: None
# Object 'a' received UPDATE event at path ('b',); prev: {'foo': 42}, curr: {}
# Object 'a' received UPDATE event at path (); prev: {'b': {'foo': 42}}, curr: {'b': {}}

只需订阅你想要的那个,然后聆听你想要的确切路径。当有子服务或工作人员时,这对于 Python 进程级别的配置特别有用。该进程可以将其配置拆分,重组为特定于每个组成部分的新根配置,并将其交给它们订阅的部分。

从较低级别的消息组成较高级别的消息的安全性只需要本地锁,而不需要全局锁。

问题是什么:

listdict 作为容器类型,本质上是引用类型的一种形式——这就是使它们可变的原因。我的 Config<Container> 对象可以从外部读取,就像你 __getitem__ 在任何东西上一样。他们扩展 collections.abc 东西,所以他们工作。你也可以用同样的方式给他们写信,不用任何时髦的 self.set(value) 符号。

但深度不可变标量 (Hashable) 却不是。这包括 intstr,甚至 NoneType。这些不能直接替换:

  1. 我不能覆盖赋值,这是有充分理由的(这太疯狂了)。

    int_a = ConfigInt()
    int_a = 2
    # swap out what the name 'int_a' refers to, or modify the internal contents?
    # obviously crazy
    
  2. 无论如何我都需要支持set(value),所以不管怎样1.

  3. 如果不对内置类型进行子类化,我无法进行比较。

    • 扩展内置是一个谎言 - 对每种类型的期望是它们是不可变的。
    • 此外,对于左右交换问题,没有虚拟子类来扩展相等性或比较:

      port = ConfigInt()
      port > 40               # True
      40 < port               # TypeError()
      

None 这是无法克服的,但是好的库对你很好。我真的很想解决这个问题。特别是对于那些工作人员只需要标量的情况——比如 Flask greenlet 或其他东西——标量是顶级对象,我们不应该将它包装在 dict.

其他问题

Override assignment in a way that doesn't just kill Python programmers' expectations?

不,您根本无法覆盖对裸名的简单赋值。正如您稍后在问题中所建议的那样,您需要使用 set 方法等。

Override rich comparators such that the order of the arguments to the binary comparison operator doesn't matter?

已经有效:

class Foo:
    def __init__(self, val):
        self.val = val

    def __lt__(self, other):
        if isinstance(other, Foo):
            return self.val < other.val
        else:
            return self.val < other

>>> Foo(3) < 10
True
>>> 10 > Foo(3)
True

当然,在与其他类型进行比较时,您可能需要考虑一下您希望的行为是什么,以及什么算作 "other types"。在这里,我只是将 Foo 对象的 "stored value" 直接与另一个值进行比较,如果另一个值不是 Foo。