Улучшение класса `namedlist`, вдохновленного`namedtuple`

Фон

Мне нужен тип, похожий на namedtuple, но с изменяемыми полями. namedlist должен вести себя следующим образом:

Something = namedlist('Something', ['a', 'b'])
st = Something(123, 456)
print st                 # Something(a=123, b=456)
a, b = st
print a, b, st.a, st.b   # 123 456 123 456

st.a = 777
print st                 # Something(a=777, b=456)
st.b = 888
print st                 # Something(a=777, b=888)

Изучив немного о дескрипторах здесь и там, я получил следующий код, работающий, как описано выше:

class IndexAccessor(object):
    def __init__(self, index):
        self.index = index
    def __get__(self, instance, owner):
        return list.__getitem__(instance, self.index)
    def __set__(self, instance, value):
        list.__setitem__(instance, self.index, value)

def namedlist(classname, fields):
    def init(self, *args):
        list.__init__(self, args)
    def repr(self):
        return '%s(%s)' % (self.__class__.__name__, ', '.join(['%s=%s' % (k,list.__getitem__(self, i)) for i, k in enumerate(self._fields)]))
    attrs = {'_fields': tuple(fields),
             '__init__': init,
             '__repr__': repr
             }        
    for index, field in enumerate(fields):
        attrs[field] = IndexAccessor(index)
    return type(classname, (list,), attrs)

Вопрос

IndexAccessor Для меня класс немного напоминает шаблон, поэтому мне интересно, можно ли улучшить код, чтобы избежать этого.

Я пытался заменить for index, field in enumerate(fields) цикл с этим

for index, field in enumerate(fields):
    def get(self): 
        return list.__getitem__(self, index)
    def set(self, v):
        list.__setitem__(self, index, v)
    attrs[field] = property(get, set, None, 'Property "%s" mapped to item %d' % (field, index))

... но это вызвало print a, b, st.a, st.b заявление для производства 123 456 456 456 вместо 123 456 123 456 Я подозреваю, что это как-то связано с index не работает, как требуется.

Бонусные баллы за ответы, которые также могут предоставить краткий код, позволяющий присваивать имена в конструкторе, как в

Something = namedlist('Something', ['a', 'b'])
st = Something(a=123, b=456)

2 ответа

Решение

Причина, по которой цикл for не работает, состоит в том, что index переменная, указанная в get а также set функции не копируются. Это ссылка на index переменная, которая устанавливается в цикле for. Таким образом, все доступы к атрибутам получают последнее значение. Это может быть решено путем явного сохранения индекса в качестве параметра по умолчанию.

Фиксированный код:

for index, field in enumerate(fields):
    def get(self,index=index): 
        return list.__getitem__(self, index)
    def set(self, v,index=index):
        list.__setitem__(self, index, v)
    attrs[field] = property(get, set, None, 'Property "%s" mapped to item %d' % (field, index))

И чтобы разрешить аргументы ключевых слов, измените __init__ функция к следующему:

def init(self, *args, **kwargs):
    kwargs.update(zip(self._fields, args))
    args = [kwargs[k] for k in self._fields]
    list.__init__(self, args)

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

class Struct:
    def __init__(self):
        pass
    def __getattribute__(self, key):
        return super().__getattribute__(key)
    def __setattribute__(self, key, value):
        super().__setattribute__(key, value)
Другие вопросы по тегам