【问题标题】:Python 3 - Reference Class as Drop-In Replacement for Deeply Immutable (Hashable) ValuesPython 3 - 参考类作为深度不可变(可散列)值的替代品
【发布时间】:2015-09-13 20:35:51
【问题描述】:

有什么办法:

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

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

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&lt;Container&gt; 对象可以从外部读取,就像您在任何东西上__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()
      

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

其他问题

  • IDK。想法?有更好的方法吗?

【问题讨论】:

  • 您的问题有些冗长且令人困惑。我不知道你实际上在问什么。您能否将您的问题简化为直接说明您要实现的内容,最好是使用假代码示例?另外,您在上一个示例中所说的内容不正确;如果您以适当的方式覆盖ConfigInt 上的__eq__,那么port == 8080 == port 都可以工作。
  • 谢谢,@BrenBarn。抱歉,我讨厌没有上下文的问题,因为所需的解决方案通常基于不正确的假设。我会修复的。至于__eq__,我会举一个坏的例子。
  • 无法重现 __eq__ 的东西。我一定是在抽烟。

标签: python python-3.x concurrency publish-subscribe


【解决方案1】:

以一种不会扼杀 Python 程序员期望的方式覆盖赋值?

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

重写丰富的比较器,以便二进制比较运算符的参数顺序无关紧要?

已经可以了:

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

当然,在与其他类型进行比较时,您可能需要考虑一下您想要的行为是什么,以及什么才是“其他类型”。在这里,我只是将Foo 对象的“存储值”直接与另一个值进行比较,如果另一个值也不是 Foo。

【讨论】:

  • 在原始问题中使用代码格式解决了您的问题。你使用的是什么 Python 版本?我在 OSX 上使用 3.4.3 CPython。
  • @alyssackwan:您使用的__lt__ 的实现与我的不同。看看有什么区别。您的版本不适用于比较实际的 Foo 实例。
  • 谢谢!我在测试中修复了它,但仍然看到 TypeError()。这是 OSX 上的 CPython 错误吗?我找到了 PEP 207。有更新的 PEP 吗?
  • @alyssackwan:不,这应该是正确的行为。您是说使用我在答案中展示的完全相同的简单示例不会给出相同的结果吗?
  • 是的。你的环境是什么?也许我有一个奇怪的糟糕构建。我刚刚运行 Homebrew pyenv 并在顶部安装了 pyenv virtualenv。我想这种类型的错误会造成严重破坏。
猜你喜欢
  • 2013-04-09
  • 2013-04-16
  • 2021-09-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多