Python 3 - 参考 Class 作为深度不可变(可散列)值的直接替换
Python 3 - Reference Class as Drop-In Replacement for Deeply Immutable (Hashable) Values
有什么办法可以:
以一种不仅会扼杀 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)
覆盖丰富的比较器,使二进制比较运算符的参数顺序无关紧要?
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()
为什么我想要这个:
- 我想要一个动态 HTTP 服务器进程配置对象。 PUT 和 POST 请求到某个 URL 的某个控制端口应该更新配置对象,并让它传播。
- 我认为轮询配置对象不是一个好主意,尤其是在并发上下文中。对于可组合性,你在哪里锁定而不会有死锁的风险?如果配置对象共享一个全局锁(这对于配置的预期变异率很好),则每次读取仍会获取该锁。如果每个
logging
消息都需要设置 Handler
级别,那显然是不好的。
- 因此,我想要一个发布-订阅配置对象。这允许配置用户在本地缓存和更新 pub 上的缓存值。这具有无锁并发安全的额外好处 - 发布消息始终可以在内部保持一致。 (构图需要一些注意,但还不错。)
我已经为 list
和 dict
容器配置类型编写了代码。我使用 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 进程级别的配置特别有用。该进程可以将其配置拆分,重组为特定于每个组成部分的新根配置,并将其交给它们订阅的部分。
从较低级别的消息组成较高级别的消息的安全性只需要本地锁,而不需要全局锁。
问题是什么:
list
和 dict
作为容器类型,本质上是引用类型的一种形式——这就是使它们可变的原因。我的 Config<Container>
对象可以从外部读取,就像你 __getitem__
在任何东西上一样。他们扩展 collections.abc
东西,所以他们工作。你也可以用同样的方式给他们写信,不用任何时髦的 self.set(value)
符号。
但深度不可变标量 (Hashable
) 却不是。这包括 int
、str
,甚至 NoneType
。这些不能直接替换:
我不能覆盖赋值,这是有充分理由的(这太疯狂了)。
int_a = ConfigInt()
int_a = 2
# swap out what the name 'int_a' refers to, or modify the internal contents?
# obviously crazy
无论如何我都需要支持set(value)
,所以不管怎样1.
。
如果不对内置类型进行子类化,我无法进行比较。
- 扩展内置是一个谎言 - 对每种类型的期望是它们是不可变的。
此外,对于左右交换问题,没有虚拟子类来扩展相等性或比较:
port = ConfigInt()
port > 40 # True
40 < port # TypeError()
None 这是无法克服的,但是好的库对你很好。我真的很想解决这个问题。特别是对于那些工作人员只需要标量的情况——比如 Flask greenlet 或其他东西——标量是顶级对象,我们不应该将它包装在 dict
.
中
其他问题
- IDK。想法?有没有更好的方法?
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。
有什么办法可以:
以一种不仅会扼杀 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)
覆盖丰富的比较器,使二进制比较运算符的参数顺序无关紧要?
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()
为什么我想要这个:
- 我想要一个动态 HTTP 服务器进程配置对象。 PUT 和 POST 请求到某个 URL 的某个控制端口应该更新配置对象,并让它传播。
- 我认为轮询配置对象不是一个好主意,尤其是在并发上下文中。对于可组合性,你在哪里锁定而不会有死锁的风险?如果配置对象共享一个全局锁(这对于配置的预期变异率很好),则每次读取仍会获取该锁。如果每个
logging
消息都需要设置Handler
级别,那显然是不好的。 - 因此,我想要一个发布-订阅配置对象。这允许配置用户在本地缓存和更新 pub 上的缓存值。这具有无锁并发安全的额外好处 - 发布消息始终可以在内部保持一致。 (构图需要一些注意,但还不错。)
我已经为 list
和 dict
容器配置类型编写了代码。我使用 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 进程级别的配置特别有用。该进程可以将其配置拆分,重组为特定于每个组成部分的新根配置,并将其交给它们订阅的部分。
从较低级别的消息组成较高级别的消息的安全性只需要本地锁,而不需要全局锁。
问题是什么:
list
和 dict
作为容器类型,本质上是引用类型的一种形式——这就是使它们可变的原因。我的 Config<Container>
对象可以从外部读取,就像你 __getitem__
在任何东西上一样。他们扩展 collections.abc
东西,所以他们工作。你也可以用同样的方式给他们写信,不用任何时髦的 self.set(value)
符号。
但深度不可变标量 (Hashable
) 却不是。这包括 int
、str
,甚至 NoneType
。这些不能直接替换:
我不能覆盖赋值,这是有充分理由的(这太疯狂了)。
int_a = ConfigInt() int_a = 2 # swap out what the name 'int_a' refers to, or modify the internal contents? # obviously crazy
无论如何我都需要支持
set(value)
,所以不管怎样1.
。如果不对内置类型进行子类化,我无法进行比较。
- 扩展内置是一个谎言 - 对每种类型的期望是它们是不可变的。
此外,对于左右交换问题,没有虚拟子类来扩展相等性或比较:
port = ConfigInt() port > 40 # True 40 < port # TypeError()
None 这是无法克服的,但是好的库对你很好。我真的很想解决这个问题。特别是对于那些工作人员只需要标量的情况——比如 Flask greenlet 或其他东西——标量是顶级对象,我们不应该将它包装在 dict
.
其他问题
- IDK。想法?有没有更好的方法?
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。