Установщик свойств Python в списке Python - PullRequest
0 голосов
/ 04 февраля 2019

Я пытаюсь использовать в Python @property для изменения атрибута класса, который имеет тип List.В большинстве примеров в Интернете предполагается, что атрибут, оформленный @property, является единственным значением, а не списком, который может быть расширен сеттером.

Чтобы прояснить вопрос: я не просто хочу присвоить значение (значениесписка int) для свойства s, скорее, мне нужно изменить его (добавить новый 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__ может изменить элемент списка, но я хочу расширитьlist.

Использование глобального атрибута неприемлемо для моей текущей задачи.

В этом отношении, что является лучшим решением?(Или я не должен ожидать @property для изменяемой структуры данных с самого начала?) Спасибо!

------ отредактировано -----

@ Сэмюэль Дион-Жирардо предложилв

список подклассов и определить его вызов магический метод

, но я не уверен, что понимаю решение.Должен ли я сделать что-то вроде этого:

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

Ответы [ 2 ]

0 голосов
/ 17 июля 2019

попробуйте это:

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()
0 голосов
/ 05 февраля 2019

Пытаясь обобщить и проиллюстрировать мои комментарии здесь.Надеюсь, что это поможет:

Решение № 1: Используйте обычный атрибут, и .append() напрямую

Целью установщика не является расширение значения атрибута, это заменить это.В этом отношении список ничем не отличается от 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(...), как предполагал ваш вопросчтобы вызывать сеттер, но когда я назначаю c.s, с c.s = ....

Также обратите внимание, что пользователи этого класса изменят _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]

Пожалуйста, не делайте этого, и если вы действительно убедитесь, что это не прослеживается мне:)

...