Python 列表上的属性设置器

问题描述 投票:0回答:3

我正在尝试利用 Python @property 来修改类属性,其类型为

List
。 大多数在线示例都假设 @property 修饰的属性是奇异值,而不是可以通过 setter 扩展的列表。

稍微澄清一下问题:我不仅仅是想为 property 分配一个值(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 属性Python 用列表装饰属性设置器

但这都不适合我的情况:

__setitem__
可以修改列表的元素,但我想扩展列表。

对于我当前的任务来说,使用全局属性是不可接受的。

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

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

@Samuel Dion-Girardeau 建议

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

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

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

python-3.x oop properties
3个回答
10
投票

尝试在这里总结并举例说明我的评论。希望这有帮助:

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

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

.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]

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


0
投票

试试这个:

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()

-1
投票

你需要写 c.s = 5 而不是 c.s(5)

© www.soinside.com 2019 - 2024. All rights reserved.