在Python中如何存储对引用的引用?

6
使用Python,是否有任何方法可以存储对引用的引用,以便我可以在另一个上下文中更改该引用所指向的内容?例如,假设我有以下类:
class Foo:
   def __init__(self):
      self.standalone = 3
      self.lst = [4, 5, 6]

我希望创建类似以下内容的东西:
class Reassigner:
   def __init__(self, target):
      self.target = target
   def reassign(self, value):
      # not sure what to do here, but reassigns the reference given by target to value

下面的代码:

f = Foo()
rStandalone = Reassigner(f.standalone) # presumably this syntax might change
rIndex = Reassigner(f.lst[1])
rStandalone.reassign(7)
rIndex.reassign(9)

这会导致f.standalone等于7f.lst等于[4, 9, 6]。实际上,这相当于指向指针的指针。

我认为引用的整个意义在于你不应该只是这样做。 - Dek Dekku
你打算如何使用这个? - FogleBird
2
如果你想要C++,你知道在哪里找到它... - pydsigner
@FogleBird 计划是使用命名引用的延迟赋值。一堆对象从xml文件中加载,这些对象可能通过名称引用其他对象。如果您引用尚不存在的名称,则会获得一个占位符对象,并陷入具有悬空引用的事物表中。如果稍后遇到该名称的定义,则会遍历悬挂引用表中的所有适当条目,并将占位符值重新分配给新创建的对象。有一些间接层的解决方案,但我希望有直接的解决方案。 - professional_yet_not_trackable
@professional_yet_not_trackable 这正是我遇到这个问题并想出了解决方案的时候所做的事情(除了 XML)。这证实了我的理论,即这种情况极少发生。 - user395760
我通常会为这种情况进行两次加载。首先,加载所有对象(并使用字典将ID => 对象映射),然后再进行另一次遍历以建立对象之间的链接。 - FogleBird
4个回答

6
简而言之,这是不可能的。完全不行。最接近的等价物是存储一个对要重新分配其成员/项的对象的引用,加上属性名称/索引/键,然后使用 setattr/setitem。但是,这会产生非常不同的语法,并且您必须区分两者:
class AttributeReassigner:
    def __init__(self, obj, attr):
        # use your imagination
    def reassign(self, val):
        setattr(self.obj, self.attr, val)

class ItemReassigner:
    def __init__(self, obj, key):
        # use your imagination
    def reassign(self, val):
        self.obj[self.key] = val

f = Foo()
rStandalone = AttributeReassigner(f, 'standalone')
rIndex = ItemReassigner(f.lst, 1)
rStandalone.reassign(7)
rIndex.reassign(9)

我实际上使用过非常相似的东西,但有效的使用情况很少。
对于全局变量/模块成员,您可以使用模块对象或globals(),具体取决于您是否在模块内部或外部。本地变量没有等效方法--locals()的结果不能可靠地用于更改本地变量,只能用于检查。

2
简单回答:你不能。
复杂的答案:你可以使用lambda。有点像。
class Reassigner:
    def __init__(self, target):
        self.reassign = target

f = Foo()
rIndex = Reassigner(lambda value: f.lst.__setitem__(1, value))
rStandalone = Reassigner(lambda value: setattr(f, 'strandalone', value))
rF = Reassigner(lambda value: locals().__setitem__('f', value)

为什么要使用__setitem__而不是直接使用[]进行项目访问? - Eric
1
咳咳,因为那不会是一个 Lambda。 - Eric
2
正如Eric所意识到的那样:),f.lst[1]=value不是一个表达式,但是f.lst.__setitem__(1, value)是。 - chepner

1
如果您需要推迟分配; 您可以使用functools.partial(或仅使用lambda):
from functools import partial

set_standalone = partial(setattr, f, "standalone")
set_item = partial(f.lst.__setitem__, 1)
set_standalone(7)
set_item(9)

如果只有重新分配操作,您不需要新的类。 在Python中,函数是一等公民:您可以将它们分配给变量,在列表中存储,作为参数传递等。

operator.setitem 使项目大小写对称于属性大小写,并且看起来更好。 - user395760
@delnan:我考虑过这个问题,但我想展示partial也适用于方法(不仅仅是函数),而且Python中还有特殊的方法。否则,operator.setitem可能更可取,因为它推迟了__setitem__查找(异常在调用者处处理)。 - jfs

0

这适用于容器对象的内容。如果您不介意在变量中添加一级间接性(无论如何,在 C 指向指针的情况下,您都需要这样做),您可以:

class Container(object):

    def __init__(self, val):
        self.val = val

class Foo(object):

    def __init__(self, target):
        self.standalone = Container(3)
        self.lst = [Container(4), Container(5), Container(6)]

而且你其实根本不需要重新分配对象。

Class Reassigner(object):

    def __init__(self, target):
        self.target = target
    def reassign(self, value):
        self.target.val = value

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接