简短版
我能否使用Python的属性系统创建只读列表?
详细版
我创建了一个Python类,其中包含一个列表成员。我希望在列表修改时执行某些操作。如果这是C ++,我将创建getter和setter,以便在调用setter时执行我的记账操作,并且我将使getter返回const
引用,以便编译器在我试图通过getter修改列表时发出警告。在Python中,我们有属性系统,因此不必为每个数据成员编写普通的getter和setter(幸好)。
然而,请考虑以下脚本:
def main():
foo = Foo()
print('foo.myList:', foo.myList)
# Here, I'm modifying the list without doing any bookkeeping.
foo.myList.append(4)
print('foo.myList:', foo.myList)
# Here, I'm modifying my "read-only" list.
foo.readOnlyList.append(8)
print('foo.readOnlyList:', foo.readOnlyList)
class Foo:
def __init__(self):
self._myList = [1, 2, 3]
self._readOnlyList = [5, 6, 7]
@property
def myList(self):
return self._myList
@myList.setter
def myList(self, rhs):
print("Insert bookkeeping here")
self._myList = rhs
@property
def readOnlyList(self):
return self._readOnlyList
if __name__ == '__main__':
main()
输出:
foo.myList: [1, 2, 3]
# Note there's no "Insert bookkeeping here" message.
foo.myList: [1, 2, 3, 4]
foo.readOnlyList: [5, 6, 7, 8]
这说明Python中没有const
概念,通过使用append()
方法可以修改列表,尽管已将其设置为属性。这可能会绕过保管机制(_myList
),或者用于修改只需要读取的列表(_readOnlyList
)。
一个解决方法是在getter方法中返回列表的深拷贝(即return self._myList[:]
)。如果列表很大或复制在内部循环中进行,则可能需要进行大量的额外复制。(但是过早的优化是万恶之源。)此外,虽然深拷贝可以防止绕过保管机制,但如果有人调用.myList.append()
,则他们的更改将被静默丢弃,这可能会产生一些痛苦的调试。如果引发异常那将很好,这样他们就知道了他们正在违背类的设计。
解决最后一个问题的方法是不使用属性系统,而是制定“普通”的getter和setter方法:
def myList(self):
# No property decorator.
return self._myList[:]
def setMyList(self, myList):
print('Insert bookkeeping here')
self._myList = myList
如果用户尝试调用append()
,它将看起来像foo.myList().append(8)
,这些额外的括号会提示他们可能得到一个副本,而不是对内部列表数据的引用。这种方法的缺点是编写getter和setter时有点不符合Python的风格,如果类有其他列表成员,我要么必须为它们编写getter和setter(呕吐),要么使接口不一致。(我认为稍微不一致的接口可能是所有问题中最小的。) 是否还有其他解决方案?可以使用Python的属性系统创建只读列表吗?
tuple()
;那将是一个只读序列。 - Martijn Pieters