gpt4 book ai didi

使用属性装饰器的 Python 只读列表

转载 作者:太空狗 更新时间:2023-10-29 18:31:33 24 4
gpt4 key购买 nike

精简版

我可以使用 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 有点不符合 Pythonic,如果该类有其他列表成员,我将不得不为那些 (eww) 编写 getter 和 setter,或者创建接口(interface)不一致。 (我认为稍微不一致的界面可能是所有弊端中最少的。)

我还缺少其他解决方案吗?可以使用 Pyton 的属性系统制作一个只读列表吗?

编辑:解

两个主要的建议似乎是使用元组作为只读列表,或者将列表子类化。我喜欢这两种方法。从 getter 返回一个元组,或者首先使用一个元组,可以防止使用 += 运算符,这可能是一个有用的运算符,并且还可以通过调用 setter 来触发簿记机制。然而,返回一个元组是一个单行更改,如果您想进行防御性编程但认为将整个其他类添加到您的脚本可能会不必要地复杂,这很好。 (有时保持极简主义并假设您不会需要它可能是一件好事。)

这是脚本的更新版本,它说明了这两种方法,供任何通过 Google 找到它的人使用。

import collections


def main():

foo = Foo()
print('foo.myList:', foo.myList)

try:
foo.myList.append(4)
except RuntimeError:
print('Appending prevented.')

# Note that this triggers the bookkeeping, as we would like.
foo.myList += [3.14]
print('foo.myList:', foo.myList)

try:
foo.readOnlySequence.append(8)
except AttributeError:
print('Appending prevented.')
print('foo.readOnlySequence:', foo.readOnlySequence)


class UnappendableList(collections.UserList):
def __init__(self, *args, **kwargs):
data = kwargs.pop('data')
super().__init__(self, *args, **kwargs)
self.data = data

def append(self, item):
raise RuntimeError('No appending allowed.')


class Foo:
def __init__(self):
self._myList = [1, 2, 3]
self._readOnlySequence = [5, 6, 7]

@property
def myList(self):
return UnappendableList(data=self._myList)

@myList.setter
def myList(self, rhs):
print('Insert bookkeeping here')
self._myList = rhs

@property
def readOnlySequence(self):
# or just use a tuple in the first place
return tuple(self._readOnlySequence)


if __name__ == '__main__':
main()

输出:

foo.myList: [1, 2, 3]
Appending prevented.
Insert bookkeeping here
foo.myList: [1, 2, 3, 3.14]
Appending prevented.
foo.readOnlySequence: (5, 6, 7)

谢谢大家。

最佳答案

您可以让方法返回原始列表的包装器——collections.Sequence 可能有助于编写它。或者,您可以返回一个元组——将列表复制到元组中的开销通常可以忽略不计。

但最终,如果用户想要更改基础列表,他们可以,而且您实际上无法阻止他们。 (毕竟,如果需要,他们可以直接访问 self._myList)。

我认为执行此类操作的 pythonic 方法是记录他们不应该更改列表,如果,那么这是他们的错当他们的程序崩溃和燃烧。

关于使用属性装饰器的 Python 只读列表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23474648/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com