Инициализация значений, которые указывают на непосвященные классы. Python @property для управления настройкой элементов в коллекции - PullRequest
0 голосов
/ 20 января 2012

Класс «Мои персоны» позволяет человеку определять номер, указывающий на друзей. Когда я делаю первого человека, я не могу поставить кого-то из друзей в список людей, потому что эти друзья еще не были созданы как люди.

Таким образом, я инициализирую всех друзей как «Нет»

Однако, как только друг был установлен, я не хочу его менять. Когда кто-то пытается перейти, например, к bob.friends [2] = john, он должен выдать ошибку, если bob.friends [2]! = Нет.

Используя pythons @property decorator, я знаю, как управлять настройкой отдельных переменных. Однако я не знаю, как использовать @property для управления настройкой элементов в массиве. Вот мои попытки до сих пор:

class person1():
    def __init__(self,name):
        self.name=name
        self.friends={1:None,2:None,3:None,4:None}

rob=person1('rob')
if rob.friends[1] is None: #I want this check to be done within the class using a property.
    rob.friends[1]="sarah"

#I just want to go: rob.friends[1]="sarah" and get an error if friends[1] isn't None.


#Solution1 was to make my own dict, however I was hoping to instead use @property instead of needing my own dict:
class robs_dict(dict):
    def __setitem__(self,i,j):
        assert(i in self)
        assert(self[i] is None)
        dict.__setitem__(self,i,j)
class person2():
    def __init__(self,name):
        self.name=name
        self.friends=robs_dict({1:None,2:None,3:None,4:None})

rob=person2('rob')
rob.friends[2]='sarah'
rob.friends[2]='sarah2' #error

#^ solution one achieves what I want, but I want to know if it's possible to use @property instead...


#solution2, unfortunatly isn't scalable if i have many friends..I would have to write many lines of code. Also one could still edit self.friends directly..:
class person3():
    def __init__(self,name):
        self.name=name
        self.friends={1:None,2:None,3:None,4:None}
    @property
    def friend1(self):
        return self.friends[1]
    @friend1.setter
    def friend1(self,value):
        assert(self.friends[1] is None)
        self.friends[1]=value
    @property
    def friend2(self):
        return self.friends[2]
    @friend2.setter
    def friend2(self,value):
        assert(self.friends[2] is None)
        self.friends[2]=value

rob=person3('rob')
rob.friend1='sarah'
rob.friend1='sarah2' #error

#so basically, i need to improve solution 2 so that I can still use rob.friends as in solution 1, but using properties. is this possible?

Приветствуется любая помощь по использованию свойств, или приветствуются любые альтернативные решения, не использующие свойства. Я предполагаю, что это обычная проблема, не позволяющая инициализировать значения при запуске из-за зависимости от других значений, которые не были инициированы.

1 Ответ

1 голос
/ 20 января 2012

Встроенный property Python создает специализированный Атрибут классов - и только это.И что он делает, так это просто предоставляет методы getter (и setter and destroyer) для данного атрибута.

Поскольку вы намереваетесь настроить доступ к элементам словаря (не array Python не имеетрисунок массива PHP) - способ сделать это - настроить метод __setitem__ - так же, как вы сделали это для своего «решения 1».

Я не вижу особого смыслапри создании внутреннего словаря, и его ключи должны быть числами (и с индексированием, начинающимся с 1), чтобы отслеживать друзей.

Что можно сделать, это создать класс, который имеет N атрибутов friend_XX,и сохраняют свои данные во внутреннем словаре - однако для этого не используется property - который настраивает доступ к одному атрибуту - скорее, вы можете использовать __getattribute__ и __setattr__, который позволяет вам настроить доступ к любому члену объекта, который явно не определен иначе.

class Person(object):
    def __init__(self, name):
        self.name = name
        self._friends = {}
    def __getattribute__(self, attr):
        if not attr.startswith("friend") or not attr[len("friend"):].isdigit():
            return super(Person, self).__getattribute__(attr)
        index = int(attr[len("friend"):])
        if index in self._friends:
            return self._friends[index]
        return None
    def __setattr__(self, attr, value):
        if not attr.startswith("friend") or not attr[len("friend"):].isdigit():
            return super(Person, self).__setattr__(attr, value)
        index = int(attr[len("friend"):])
        if index in self._friends:
            raise ValueError("%s already defined for %s " % (attr, self.name))
        if not isinstance(value, Person):
            raise TypeError("Friends have to be persons")
        self._friends[index] = value

Используя этот класс на консоли, вы можетесделать:

>>> p = Person("Nick")
>>> p2 = Person("Paul")
>>> p.friend1 = p2
>>> p.friend1 = p2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 17, in __setattr__
ValueError: friend1 already defined for Nick 
>>> p.friend1.name
'Paul'
>>> 
...