Почему Python не поддерживает тип записи, т.е. Изменяемый namedtuple
Почему Python не поддерживает тип записи изначально? Это вопрос наличия изменяемой версии namedtuple.
Я мог бы использовать namedtuple._replace
. Но мне нужно иметь эти записи в коллекции, и поскольку namedtuple._replace
создает другой экземпляр, мне также нужно изменить коллекцию, которая быстро становится беспорядочной.
Фон:
У меня есть устройство, чьи атрибуты мне нужно получить, опросив его по TCP/IP. то есть его представление является изменчивым объектом.
Изменить:
У меня есть набор устройств, для которых мне нужно опросить.
Изменить:
Мне нужно выполнить итерацию объекта, отображающего его атрибуты с помощью PyQt. Я знаю, что могу добавить специальные методы, такие как __getitem__
и __iter__
, но мне интересно, есть ли более простой способ.
Изменить:
Я бы предпочел тип, атрибут которого фиксирован (точно так же, как они есть на моем устройстве), но меняются.
Ответы
Ответ 1
Python < 3.3
Вы имеете в виду что-то вроде этого?
class Record(object):
__slots__= "attribute1", "attribute2", "attribute3",
def items(self):
"dict style items"
return [
(field_name, getattr(self, field_name))
for field_name in self.__slots__]
def __iter__(self):
"iterate over fields tuple/list style"
for field_name in self.__slots__:
yield getattr(self, field_name)
def __getitem__(self, index):
"tuple/list style getitem"
return getattr(self, self.__slots__[index])
>>> r= Record()
>>> r.attribute1= "hello"
>>> r.attribute2= "there"
>>> r.attribute3= 3.14
>>> print r.items()
[('attribute1', 'hello'), ('attribute2', 'there'), ('attribute3', 3.1400000000000001)]
>>> print tuple(r)
('hello', 'there', 3.1400000000000001)
Обратите внимание, что предоставленные методы - всего лишь образец возможных методов.
Обновление Python ≥3.3
Вы можете использовать types.SimpleNamespace
:
>>> import types
>>> r= types.SimpleNamespace()
>>> r.attribute1= "hello"
>>> r.attribute2= "there"
>>> r.attribute3= 3.14
dir(r)
предоставит вам имена атрибутов (конечно, отформатируйте все .startswith("__")
).
Ответ 2
Есть ли причина, по которой вы не можете использовать обычный словарь? Похоже, что в вашей конкретной ситуации атрибуты не имеют определенного порядка.
В качестве альтернативы вы также можете использовать экземпляр класса (который имеет хороший синтаксис доступа к атрибутам). Вы можете использовать __slots__
, если хотите избежать создания __dict__
для каждого экземпляра.
Я также нашел рецепт для "записей" , которые описываются как изменяемые именованные кортежи. Они реализуются с использованием классов.
Update:
Поскольку вы говорите, что порядок важен для вашего сценария (и вы хотите итерации по всем атрибутам), кажется, что путь OrderedDict
- это путь. Это часть стандартного модуля collections
с Python 2.7; существуют другие реализации, плавающие вокруг Интернета для Python < 2.7.
Чтобы добавить доступ в стиле атрибута, вы можете подклассифицировать его так:
from collections import OrderedDict
class MutableNamedTuple(OrderedDict):
def __init__(self, *args, **kwargs):
super(MutableNamedTuple, self).__init__(*args, **kwargs)
self._initialized = True
def __getattr__(self, name):
try:
return self[name]
except KeyError:
raise AttributeError(name)
def __setattr__(self, name, value):
if hasattr(self, '_initialized'):
super(MutableNamedTuple, self).__setitem__(name, value)
else:
super(MutableNamedTuple, self).__setattr__(name, value)
Затем вы можете сделать:
>>> t = MutableNamedTuple()
>>> t.foo = u'Crazy camels!'
>>> t.bar = u'Yay, attribute access'
>>> t.foo
u'Crazy camels!'
>>> t.values()
[u'Crazy camels!', u'Yay, attribute access']
Ответ 3
Это можно сделать, используя пустой класс и его экземпляры, например:
>>> class a(): pass
...
>>> ainstance = a()
>>> ainstance.b = 'We want Moshiach Now'
>>> ainstance.b
'We want Moshiach Now'
>>>
Ответ 4
Там есть библиотека, подобная namedtuple, но изменяемая, называемая recordtype.
Пакет home: http://pypi.python.org/pypi/recordtype
Простой пример:
from recordtype import recordtype
Person = recordtype('Person', 'first_name last_name phone_number')
person1 = Person('Trent', 'Steele', '637-3049')
person1.last_name = 'Terrence';
print person1
# Person(first_name=Trent, last_name=Terrence, phone_number=637-3049)
Пример простого значения по умолчанию:
Basis = recordtype('Basis', [('x', 1), ('y', 0)])
Итерации через поля person1
по порядку:
map(person1.__getattribute__, Person._fields)
Ответ 5
Этот ответ дублирует еще один.
Существует альтернатива для collections.namedtuple
- recordclass.
Он имеет те же API и область памяти, что и namedtuple
(на самом деле это также быстрее). Он поддерживает присвоения. Например:
from recordclass import recordclass
Point = recordclass('Point', 'x y')
>>> p = Point(1, 2)
>>> p
Point(x=1, y=2)
>>> print(p.x, p.y)
1 2
>>> p.x += 2; p.y += 3; print(p)
Point(x=3, y=5)
Существует более полное example (оно также включает сравнение производительности).
Ответ 6
В тесном отношении Существование измененного именованного кортежа в Python? заданы вопросы 13 для сравнения 6 изменяемых альтернатив с namedtuple
.
Последний namedlist 1.7 проходит все эти тесты с Python 2.7 и Python 3.5 по состоянию на 11 января 2016. Это чистая реализация python.
Второй лучший кандидат в соответствии с этими тестами - recordclass
, который является расширением C. Конечно, это зависит от ваших требований, является ли предпочтительным расширение C или нет.
Более подробную информацию, особенно для тестов, см. в Существование измененного именованного кортежа в Python?
Ответ 7
Вы можете сделать что-то вроде этого подкласса dict
, который является его собственным __dict__
. Основная концепция такая же, как в ActiveState AttrDict, но реализация проще. Результат является чем-то более изменчивым, чем вам нужно, поскольку атрибуты экземпляра и их значения изменяемы. Хотя атрибуты не упорядочены, вы можете перебирать текущие и/или их значения.
class Record(dict):
def __init__(self, *args, **kwargs):
super(Record, self).__init__(*args, **kwargs)
self.__dict__ = self
Ответ 8
Основываясь на нескольких полезных трюках, собранных с течением времени, этот декоратор "frozenclass" делает практически все необходимое: http://pastebin.com/fsuVyM45
Поскольку этот код содержит более 70% документации и тестов, я больше не буду говорить здесь.
Ответ 9
Вот полный измененный namedtuple, который я сделал, который ведет себя как список и полностью совместим с ним.
class AbstractNamedArray():
"""a mutable collections.namedtuple"""
def __new__(cls, *args, **kwargs):
inst = object.__new__(cls) # to rename the class
inst._list = len(cls._fields)*[None]
inst._mapping = {}
for i, field in enumerate(cls._fields):
inst._mapping[field] = i
return inst
def __init__(self, *args, **kwargs):
if len(kwargs) == 0 and len(args) != 0:
assert len(args) == len(self._fields), 'bad number of arguments'
self._list = list(args)
elif len(args) == 0 and len(kwargs) != 0:
for field, value in kwargs.items():
assert field in self._fields, 'field {} doesn\'t exist'
self._list[self._mapping[field]] = value
else:
raise ValueError("you can't mix args and kwargs")
def __getattr__(self, x):
return object.__getattribute__(self, '_list')[object.__getattribute__(self, '_mapping')[x]]
def __setattr__(self, x, y):
if x in self._fields:
self._list[self._mapping[x]] = y
else:
object.__setattr__(self, x, y)
def __repr__(self):
fields = []
for field, value in zip(self._fields, map(self.__getattr__, self._fields)):
fields.append('{}={}'.format(field, repr(value)))
return '{}({})'.format(self._name, ', '.join(fields))
def __iter__(self):
yield from self._list
def __list__(self):
return self._list[:]
def __len__(self):
return len(self._fields)
def __getitem__(self, x):
return self._list[x]
def __setitem__(self, x, y):
self._list[x] = y
def __contains__(self, x):
return x in self._list
def reverse(self):
self._list.reverse()
def copy(self):
return self._list.copy()
def namedarray(name, fields):
"""used to construct a named array (fixed-length list with named fields)"""
return type(name, (AbstractNamedarray,), {'_name': name, '_fields': fields})