Python 3 - 引用类作为深度不可变(可嵌入)值的替代品

时间:2015-09-13 20:35:51

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

有没有办法:

  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
    
  3. @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-sub配置对象。这允许config用户在本地缓存和更新pub上的缓存值。这具有额外的好处,即无需锁定即可实现并发安全 - 发布消息始终可以在内部保持一致。 (作文需要一些注意,但并不是那么糟糕。)
    4. 我已经为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<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()
          
      4. 这些都不是不可克服的,但好的库对你很好。我真的想解决这个问题。特别是对于标量完全是工人需要的情况 - 比如Flask greenlet或其他东西 - 标量是顶级对象,我们不应该将它包装在dict中。

        其他问题

        • IDK。思考?有更好的方法吗?

1 个答案:

答案 0 :(得分:2)

  

以一种不会扼杀Python程序员的方式覆盖作业&#39;期望?

不,你根本不能将普通作业改为裸名。正如您在问题中稍后提到的那样,您需要使用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

当然,在与其他类型进行比较时,您可能需要考虑一下您希望行为的内容,以及其他类型&#34;。在这里,我只是比较&#34;存储的值&#34; Foo对象的直接与另一个值的关系,如果其他值也不是Foo。