当您有两个需要具有互相引用属性的类时
# DOESN'T WORK
class A:
b = B()
class B:
a = A()
# -> ERROR: B is not defined
standard answers说使用python是动态的事实,即。
class A:
pass
class B:
a = A()
A.b = B()
从技术上解决了这个问题。但是,当存在三个或更多相互依赖的类时,或者这些类的长度超过几行时,此方法将导致难以导航的意大利面条式代码。例如,我发现自己正在编写如下代码:
class A:
<50 lines>
# a = B() but its set later
<200 more lines>
class B:
<50 lines>
a = A()
<100 lines>
A.b = B() # to allow for circular referencing
由于我无法将A.b = B()
放入与之相关的类中,因此这最终违反了DRY(因为我在两个地方都写了代码)和/或将相关的代码移到了模块的相对两端。
是否有更好的方法允许在python中使用循环依赖的类属性,而该方法不涉及将相关代码分散到模块的通常较远的部分?
答案 0 :(得分:0)
经过一些实验,我找到了一种(主要是)做我想做的方法。
class DeferredAttribute:
""" A single attribute that has had its resolution deferred """
def __init__(self, fn):
"""fn - when this attribute is resolved, it will be set to fn() """
self.fn = fn
def __set_name__(self, owner, name):
DeferredAttribute.DEFERRED_ATTRIBUTES.add((owner, name, self))
@classmethod
def resolve_all(cls):
""" Resolves all deferred attributes """
for owner, name, da in cls.DEFERRED_ATTRIBUTES:
setattr(owner, name, da.fn())
cls.DEFERRED_ATTRIBUTES.clear()
使用这个习惯用语是
class A:
@DeferredAttribute
def b():
return B()
class B:
a = A()
DeferredAttribute.resolve_all()
这将产生与您运行代码完全相同的类A
和B
class A:
pass
class B:
a = A()
A.b = B()
结论:从好的方面来说,这可以避免重复和本地化相关代码,从而有助于代码组织。
不利的一面是,它使人们对动态编程产生了一些期望。在调用resolve_deferred_attributes
之前,值A.b
将是一个特殊值,而不是B
的实例。通过向DeferredAttribute
添加适当的方法来部分似乎可以解决此问题,但我看不出有什么方法可以使其完美。
编辑器注意:上面的代码使我的IDE(PyCharm)错误地对我大喊,说def b():
应该带有一个参数(尽管可以正常运行)。如果需要,可以通过更改代码将错误更改为警告:
In the resolve_all method, change:
setattr(owner, name, da.fn())
->
fn = da.fn
if isinstance(fn, staticmethod):
setattr(owner, name, fn.__func__())
else:
setattr(owner, name, fn())
And in the use code, change:
@defer_attribute
def b():
...
->
@defer_attribute
@staticmethod
def b():
...
除了关闭警告之外,我还没有找到一种完全消除警告的方法。