【问题标题】:Python property setter on a python listPython 列表中的 Python 属性设置器
【发布时间】:2019-06-27 18:40:43
【问题描述】:

我正在尝试利用 Python @property 来修改 List 类型的类属性。网上的大部分例子都假设@property修饰的属性是一个奇异值,而不是一个可以被setter扩展的列表。

为了澄清一点问题:我不只是想为属性 s 分配一个值(一个 int 列表的值),而是我需要修改它(将一个新的 int 附加到当前列表)。

我的目的是预期有:

c = C()
c.s # [1,2,3] is the default value when instance c initiated.
c.s(5)

c.s # [1,2,3,5]

C 的实现如下:

class C:
    def __init__(self):
        self._s = [1,2,3]
    @property
    def s(self):
        return self._s
    @s.setter
    def s(self, val):
        self._s.append(val)

现在如果我做c.s(5),我会得到

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-99-767d6971e8f3> in <module>()
----> 1 c.s(5)

TypeError: 'list' object is not callable

我已阅读最相关的帖子: Python property on a listPython decorating property setter with list

但都不适合我的情况: __setitem__ 可以修改列表的元素,但我想扩展列表。

我当前的任务不接受使用全局属性。

在这方面,最好的解决方案是什么? (或者我不应该从一开始就期望@property 在可变数据结构上?)谢谢!

-----已编辑-----

@Samuel Dion-Girardeau 建议

子类列表并定义其调用魔术方法

,但我不确定我是否理解解决方案。我应该这样做吗:

class C(list):
     # somewhere when implementing class C
     def __call__(self):
         # even not sure what i need to do here

【问题讨论】:

  • 您是否有明确的理由回避c.s = 5
  • 您说“大多数在线示例都假定@property 修饰的属性是一个奇异值,而不是可以由setter 扩展的列表”,但是在这里您永远不会调用setter,这将是通过为c.s 赋值来完成,例如c.s = 1。在这里,您尝试调用 c.s 的值,因此出现错误。
  • 因为s 是一个列表。您可以直接附加到它,例如c.s.append(1)。如果您出于某种原因绝对需要带括号的调用,则需要将list 子类化并定义其__call__ 魔术方法以附加到列表中。
  • @SamuelDion-Girardeau 是的,我同意使用c.s.append(1) 是将新元素附加到当前列表的最佳方式。我阅读了stackoverflow.com/questions/37564798/python-property-on-a-list 的帖子,其中指出“修改属性中的元素,您将获得该属性,然后设置相应的元素。”所以我认为我的 @s.setter 是这样做的:即获取 _s 是一个列表的属性,然后通过 self._s.append(val) 修改它。但是我在这里犯了一个错误并得到了错误。有什么建议吗?谢谢^^
  • @JacquelineP。在您的代码中,您实际上从未使用过 setter,我认为这就是造成混乱的原因。您实际上可以通过从函数中引发错误来测试它,您将永远不会看到错误!使用 setter 并不意味着调用c.s(5),它意味着分配一个值,例如c.s = 5。如果属性被更明确地命名,我认为这会更明显。例如。假设我们将属性命名为“启用”。吸气剂被称为例如如果你是print(my_object.enabled),如果你是my_object.enabled = True,则是setter,如果你是my_object.enabled(True)不是

标签: python-3.x oop properties


【解决方案1】:

在这里尝试总结和举例说明我的 cmets。希望这会有所帮助:

解决方案#1:使用常规属性,直接.append()

setter 的目的不是扩展属性的值,而是替换它。在这方面,列表与 int 或字符串没有什么不同。在您的情况下,由于该值是一个可变列表,您可以直接在其上调用 .append() 方法。

class C():
    def __init__(self):
        self.s = [1, 2, 3]


>>> c = C()
>>> c.s
[1, 2, 3]
>>> c.s.append(1)
>>> c.s
[1, 2, 3, 1]
>>> c.s = [0, 0]
>>> c.s
[0, 0]

解决方案#2:使用属性,直接使用.append()

如果在获取/设置s 时无需检查,则上述解决方案有效。但是,如果您出于某种原因需要使用属性(例如,您需要进行一些计算或检查,并且想要阻止用户将他们想要的任何内容设置为 s),您可以使用属性来执行此操作。

在这种情况下,我将防止列表中出现负数作为验证示例。

class C():
    def __init__(self):
        self._s = [1, 2, 3]
    @property
    def s(self):
        return self._s
    @s.setter
    def s(self, val):
        if any(x < 0 for x in val):
            raise ValueError('No negative numbers here!')
        self._s = val

>>> c = C()
>>> c.s
[1, 2, 3]
>>> c.s.append(1)
>>> c.s
[1, 2, 3, 1]
>>> c.s = [0, 0]
>>> c.s
[0, 0]
>>> c.s = [0, -1]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 10, in s
ValueError: No negative numbers here!

请注意此处引发错误的位置:不是我调用 c.s(...)(您的问题假定它是调用 setter),而是当我使用 c.s = ... 分配给 c.s 时。

还要注意这个类的用户会通过setter间接修改_s

解决方案 #3:子类 list 以允许属性可调用

我绝对不推荐,因为它打破了这个类的用户的所有期望,我只是把它作为一个琐事提供,因为它允许你最初要求的行为。

class CallableList(list):
    # This is the method that actually gets called when you `c.s(...)`!
    def __call__(self, *args):
        self.append(*args)

class C():
    def __init__(self):
        self._s = CallableList([1,2,3])
    @property
    def s(self):
        return self._s
    @s.setter
    def s(self, val):
        self._s = CallableList(val)

>>> c = C()
>>> c.s
[1, 2, 3]
>>> c.s(1)
>>> c.s
[1, 2, 3, 1]
>>> c.s = [0, 0]
>>> c.s
[0, 0]
>>> c.s(1337)
>>> c.s
[0, 0, 1337]

请不要这样做,如果你这样做了,请确保它无法追溯到我 :)

【讨论】:

  • 非常感谢您的详细解释!看来解决方案 3 最适合我的任务!
【解决方案2】:

试试这个:

class C:
def __init__(self):
    self._s = [1, 2, 3]

@property
def s(self): return tuple(self._s)
@s.setter
def s(self, list_val):
    self._s = list_val

def add(self, *args):
    for arg in args:
        self._s.append(arg)

def print_c():
print('my list_props')
print('-'*5)
for p in c.s:
    print(p)
print('-' * 5)
print()


c = C()
print_c()
c.s = [8, 9, 10, 11]
print_c()
c.s = []
print_c()
c.add(100, 200, 300)
print_c()
c.add(400, 500, 600)
print_c()
c.s = []
c.add(*[i for i in range(100)])
print_c()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-04-22
    • 2013-04-29
    • 2021-05-28
    • 1970-01-01
    • 2016-09-30
    • 2012-06-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多