【问题标题】:Circularly Dependent Class Attributes and Code Locality循环依赖的类属性和代码局部性
【发布时间】:2019-06-23 22:49:35
【问题描述】:

当您有两个类需要具有相互引用的属性时

# DOESN'T WORK
class A:
    b = B()

class B:
    a = A()
# -> ERROR: B is not defined

standardanswers表示使用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

这最终会违反 DRY(因为我在两个地方编写代码)和/或将相关代码移动到我模块的两端,因为我不能将 A.b = B() 放在与之相关的类中。

有没有更好的方法来允许在 python 中循环依赖类属性,而不涉及将相关代码分散到模块的经常远离的部分?

【问题讨论】:

  • 是的,更好的方法是拥有一个中间资源,这样您就没有循环依赖,即重新审视您的设计决策

标签: python python-3.x circular-reference


【解决方案1】:

经过一些试验,我找到了一种方法来(大部分)做我想做的事。

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()

这会生成与运行代码完全相同的类 AB

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():
        ...

除了关闭警告之外,我还没有找到完全消除警告的方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-02
    • 2011-02-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多