我试图构建一个允许实例指向另一个类的类,但我希望这些类最终形成一个循环(例如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
答案 0 :(得分:7)
您需要先创建对象,然后链接它们。
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
答案 1 :(得分:1)
阅读关于chepner答案的评论,看起来你想要一种懒惰的方法来进行绑定。请注意,允许延迟分配" 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'
但是这种方法相当幼稚 - 如果你将yur isntances放入列表或其他数据结构中,除非你有一个很好的时机触发属性渲染成真正的引用,否则它将无法工作 - 此时不管怎样,最好允许延迟分配给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]
正如您所看到的,执行此工作的复杂性正在迅速升级,并且可能是您项目中的缺陷来源 - 但如果考虑到所有细微差别,仍然可以完成。 (例如,我在全局对象索引上使用弱参数)