python - 循环依赖的类属性和代码局部性
问题描述
当您有两个类需要具有相互引用的属性时
# DOESN'T WORK
class A:
b = B()
class B:
a = A()
# -> ERROR: B is not defined
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
这最终会违反 DRY(因为我在两个地方编写代码)和/或将相关代码移动到模块的两端,因为我无法放入A.b = B()
与之相关的类。
是否有更好的方法来允许在 python 中循环依赖类属性,而不涉及将相关代码分散到模块的通常较远的部分?
解决方案
经过一些实验,我找到了一种(主要)做我想做的事情的方法。
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()
结论:从好的方面来说,这有助于通过避免重复和本地化相关代码来组织代码。
不利的一面是,它破坏了对动态规划的一些期望;调用untilresolve_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():
...
除了关闭它们之外,我还没有找到完全消除警告的方法。
推荐阅读
- javascript - Rechart.js 减少底部文本之间的空间(月)
- javascript - Discord.js 按钮 v13 的交互处理程序
- javascript - 单击按钮时单行的 Mat-icon 更改
- sql - Oracle 查询缺失的工时数据
- javascript - Next.js 上的 ERR_REQUIRE_ESM 和 Package.json 错误。更新后。怎么修?
- javascript - 使用 HTML & JS 将垂直和水平滚动事件转换为水平滚动
- composer-php - Composer [UnexpectedValueException] 您的 github.com 的 github oauth 令牌包含无效字符:
- java - 如何在 java 中将 OnStart 脚本设置为 CfnNotebookInstanceLifecycleConfigProps
- vue.js - 为什么 `/_nuxt/static/... ` 页面路径会出现在 Google Analytics 上?(Nuxt.js + GA4 + vue-gtag)
- python - 将 html + bootstrap 文件(使用 flex)转换为 pdf - Django?