【问题标题】:Create circular references between class instances?在类实例之间创建循环引用?
【发布时间】:2017-06-02 19:51:34
【问题描述】:

我正在尝试构建一个允许实例指向另一个类的类,但我希望它们最终形成一个循环(因此实例 A → 实例 B → 实例 C → 实例 A)

我尝试了以下方法,但得到了NameError

class CyclicClass:
    def __init__(self, name, next_item):
        self.name = name
        self.next_item = next_item

    def print_next(self):
        print(self.next_item)

item_a = CyclicClass("Item A", item_b)
item_b = CyclicClass("Item B", item_a)

这是 Python 中不合适的模式吗?如果是这样,实现这一点的正确方法是什么?这似乎与以下类似但不一样,因为类定义本身不是循环的:Circular dependency between python classes

【问题讨论】:

  • 这将返回一个错误,因为在您定义item_a时没有定义item_b
  • 你几乎想要树节点。
  • 类实际上没有什么是循环的......只有当你正确链接它们时,它们才会循环。你所拥有的看起来更像是一个链表。

标签: python


【解决方案1】:

您需要先创建对象,然后链接它们。

item_a = CyclicClass("Item A", None)
item_b = CyclicClass("Item B", item_a)
item_a.next_item = item_b

CyclicClass 的第二个参数视为方便,而不是链接两个对象的主要方式。您可以通过将None 设为默认参数值来强调这一点。

class CyclicClass:
    def __init__(self, name, next_item=None):
        self.name = name
        self.next_item = next_item

    def print_next(self):
        print(self.next_item)

item_a = CyclicClass("Item A")
# item_b = CyclicClass("Item B")
# item_b.next_item = item_a
item_b = CyclicClass("Item B", item_a)
item_a.next_item = item_b

【讨论】:

  • 似乎这是他们可以通过 python 解释器改进的东西?
  • @AllenWang 他们怎么可能?这将如何运作?您希望解释器为尚未定义的名称提供某种“通配符”值吗?
  • @AllenWang 它的工作方式相同。尝试在定义函数之前调用它,或者以任何方式引用它。
  • @AllenWang 如果foo 调用bar,您不必在定义foo 之前定义 bar,但您当然必须定义@ 987654329@ 在您致电 foo之前。
  • @AllenWang 我认为可能令人困惑的部分是函数体直到函数被调用才真正被执行。所以你可以在函数体中有未定义的名字,只要它们是在函数被调用之前定义的。
【解决方案2】:

阅读 chepner 的回答中的 cmets,您似乎想要一种能够进行绑定的惰性方法。请注意,在另一个答案中允许延迟分配“next_item”仍然是“正确的做法”。

这很容易做到,但是它们,您仍然依赖硬编码的其他实例名称。例如,一些 ORM 框架,因为它们允许定义类之间的相互关系,所以允许您将其他类作为字符串而不是实际的类对象插入。

但是字符串对于在模块顶层定义的对象会很好用,因为它们可以作为全局变量获取 - 但如果您在函数或方法中使用循环类,则不会工作。将返回具有实例名称的非局部变量的可调用对象可以工作:

from types import FunctionType

class CyclicClass:
    def __init__(self, name, next_item=None):
        self.name = name
        self.next_item = next_item

    def print_next(self):
        print(self.next_item)


    @property
    def next_item(self):
        if isinstance(self._next_item, FunctionType):
            self._next_item = self._next_item()
        return self._next_item
    @next_item.setter

    def next_item(self, value):
        self._next_item = value

并在交互式解释器上进行测试:

In [23]: def test():
    ...:     inst1 = CyclicClass("inst1", lambda: inst2)
    ...:     inst2 = CyclicClass("inst2", inst1)
    ...:     return inst1, inst2
    ...: 

In [24]: i1, i2 = test()

In [25]: i1.next_item.name
Out[25]: 'inst2'

但是这种方法相当幼稚-如果您将您的实例放入列表或其他数据结构中,则不会起作用,除非您有一个很好的时机将属性渲染触发为真正的参考-此时它是最好还是允许延迟分配给 next_item。

并不是说如果“名称”属性是唯一的,您可以修改上面的代码以拥有所有实例的全局注册表,并传递一个字符串来标识您的实例。这可能符合您的需求 - 但仍然会比允许后期设置 next_item 增加更多复杂性

cyclic_names = {}

class CyclicClass:
    ...

    @property
    def next_item(self):
        if isinstance(self._next_item, str):
            self._next_item = cyclic_names[self._next_item]
        return self._next_item
    @next_item.setter

    def next_item(self, value):
        self._next_item = value

    @property
    def name(self):
        return self._name

    @name.setter(self)
    def name(self, value):
        if hasattr(self, "_name"):
            del cyclic_names[value]
        if value in cyclic_names:
            raise ValueError("An object with this name already exists")

        cyclic_names[value] = self

    def __del__(self):
        del cyclic_names[self._name]

如您所见,正确完成这项工作的复杂性迅速增加,这可能是您项目中缺陷的来源 - 但如果考虑到所有细微差别,仍然可以完成。 (例如,我会在全局对象索引上使用弱引用)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多