Почему Python не поддерживает тип записи? (т.е. изменяемый namedtuple)

Почему Python изначально не поддерживает тип записи? Это вопрос наличия изменяемой версии namedtuple.

Я мог бы использовать namedtuple._replace. Но мне нужно, чтобы эти записи были в коллекции, а поскольку namedtuple._replace создает другой экземпляр, мне также нужно изменить коллекцию, которая быстро становится беспорядочной.

Предыстория: у меня есть устройство, атрибуты которого мне нужно получить, опрашивая его по TCP/IP. то есть его представление является изменяемым объектом.

Изменить: у меня есть набор устройств, для которых мне нужно опросить.

Изменить: мне нужно перебрать объект, отображающий его атрибуты, с помощью PyQt. Я знаю, что могу добавить специальные методы, такие как __getitem__ и __iter__, но я хочу знать, есть ли более простой способ.

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


person Salil    schedule 08.03.2011    source источник
comment
Создайте класс или используйте dict. Оба изменяемы, оба позволяют вам получить доступ к значениям внутри них по имени.   -  person dappawit    schedule 08.03.2011
comment
@dappawit, это правильно. Но с dict у меня не будет возможности использовать атрибуты в качестве полей. Я избегал класса, потому что мне нужно перебирать объект, рассматривая его как набор атрибутов. Я отредактирую свой пост, чтобы указать это требование. Я знаю, что всегда могу добавить специальные методы, чтобы обращаться с ним как с коллекцией. Но мне интересно, есть ли более простой способ.   -  person Salil    schedule 08.03.2011
comment
Экземпляр класса имеет атрибут __dict__, который является словарем. Вы можете повторить это. Посмотрите на ответ Кэмерона и комментарий Криса Лутца.   -  person dappawit    schedule 08.03.2011
comment
Спасибо, даппавит. Я хотел использовать существующий тип без имитации типа коллекции с помощью специальных методов. Но да, мне придется это сделать.   -  person Salil    schedule 08.03.2011


Ответы (11)


Питон ‹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("__")).

person tzot    schedule 30.03.2011
comment
Это отлично. Ценю ваш ответ. Просто я надеялся на встроенную/стандартную структуру данных библиотеки. - person Salil; 31.03.2011
comment
SimpleNamespace удовлетворяет потребность. Спасибо. - person Salil; 17.03.2013
comment
втф! Python продолжает портить логику своих модулей... почему этого нет в коллекциях? - person u0b34a0f6ae; 30.03.2013
comment
@ u0b34a0f6ae Глядя на то, как реализовано SimpleNamespace, имеет смысл поместить его в types. Если вы посмотрите на множество классов в модуле types, вы заметите, что все они получены с помощью функции type для извлечения типа, поддерживающего определенное значение (например, LambdaType = type(lambda: None)). Угадайте, как реализовано SimpleNamespace? SimpleNamespace = type(sys.implementation). - person new123456; 18.06.2013
comment
@ u0b34a0f6ae Поскольку это не коллекция, точно так же пустой класс, такой как class X(): pass, не является коллекцией. Самое главное, он не имеет понятия об итерации или размере. Как вы думаете, почему это должно быть в collections? - person l4mpi; 09.08.2013
comment
Обратите внимание, что list(r.__dict__) будет возвращать определенные пользователем имена атрибутов без необходимости __ фильтрации. - person EquipDev; 15.02.2015

Есть ли причина, по которой вы не можете использовать обычный словарь? Кажется, что атрибуты не имеют определенного порядка в вашей конкретной ситуации.

В качестве альтернативы вы также можете использовать экземпляр класса (который имеет хороший синтаксис доступа к атрибутам). Вы можете использовать __slots__, если хотите избежать создания __dict__ для каждого экземпляра.

Я также только что нашел рецепт для "записей", которые описываются как изменяемые именованные кортежи. Они реализуются с помощью классов.

Обновлять:

Поскольку вы говорите, что порядок важен для вашего сценария (и вы хотите перебрать все атрибуты), 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']
person Cameron    schedule 08.03.2011
comment
Вы даже можете создать подкласс класса dict и сделать (IIRC) __getattr__ и __setattr__ синонимами __getitem__ и __setitem__ соответственно. - person Chris Lutz; 08.03.2011
comment
@Chris: Думаю, мне больше всего нравится этот метод (и ваша память верна, это правильные методы) - person Cameron; 08.03.2011
comment
+1 за указание, что slots создается один раз для каждого класса, а dict создается для каждого экземпляра. Я отредактировал свой вопрос, включив в него необходимость заказа. Кроме того, я знаю об этом рецепте записи; однако мне интересно, почему сообщество Python считает, что нет необходимости в стандартном типе записи. - person Salil; 08.03.2011
comment
Если вам нужен заказ, попробуйте OrderedDict. Я считаю, что это также в модуле коллекций. - person dappawit; 08.03.2011
comment
Спасибо, даппавит. Я бы предпочел тип, атрибуты которого фиксированы (как в моем устройстве), но могут изменяться. Я думаю, что мне, возможно, придется выбрать тип записи, указанный Кэмероном. - person Salil; 08.03.2011
comment
Я бы посоветовал использовать Tuple в названии примера здесь, когда он не действует как кортеж. Лучше назвать это рекордом или как-то иначе. - person Kylotan; 05.11.2011
comment
Я играл с рецептом типа записи, на который вы ссылаетесь от Джорджа Саккиса, и, похоже, он работает довольно хорошо для меня, хотя и с небольшой ценой производительности для указания всего класса вручную. - person TimothyAWiseman; 19.09.2012
comment
@ChrisLutz @Cameron Использование getattr звучит интересно, но я не рекомендую этого делать, поскольку имена полей будут конфликтовать с общедоступными методами dict, такими как get или items. Это может привести к забавным ошибкам. - person Kos; 03.01.2013

Это можно сделать с помощью пустого класса и его экземпляров, например:

>>> class a(): pass
... 
>>> ainstance = a()
>>> ainstance.b = 'We want Moshiach Now'
>>> ainstance.b
'We want Moshiach Now'
>>> 
person Abbafei    schedule 08.03.2011
comment
Также см. этот ответ на этот вопрос stackoverflow для получения дополнительной информации. - person Abbafei; 08.03.2011
comment
Спасибо Абафей. Я бы предпочел тип, атрибуты которого фиксированы (как в моем устройстве), но могут изменяться. Я обновил вопрос соответственно. - person Salil; 08.03.2011

Существует библиотека, похожая на namedtuple, но изменяемая, которая называется recordtype.

Домашняя страница пакета: 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)
person Leif Bork    schedule 15.01.2013
comment
namedlist — это обновленный пакет от того же автора, который поддерживает Python 3 и активно развивается как 2014 года. - person simonzack; 28.07.2014

Этот ответ дублирует другой ответ. Существует изменяемая альтернатива collections.namedtuplerecordclass.

Он имеет тот же 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)

Существует более полный пример (это также включает сравнение производительности).

person intellimath    schedule 30.04.2015

Этот вопрос старый, но для полноты картины в Python 3.7 есть классы данных которые в значительной степени являются рекордами.

>>> from dataclasses import dataclass
>>>
>>> @dataclass
... class MyRecord:
...     name: str
...     age: int = -1
...
>>> rec = MyRecord('me')
>>> rec.age = 127
>>> print(rec)
MyRecord(name='me', age=127)

Сторонняя библиотека attrs предоставляет больше функций как для Python 2, так и для Python 3. Нет ничего плохого в зависимостях от поставщиков, если требования больше связаны с вещами, которые вы не можете хранить локально, а не только с использованием stdlib. У dephell есть хороший помощник для этого.

person Daniel    schedule 23.08.2020
comment
Это заслуживает большего количества голосов. - person Grilse; 19.12.2020

В тесно связанном вопросе Существование изменяемого именованного кортежа в Python? тесты 13 используются для сравнения 6 изменяемых альтернатив namedtuple.

Последний namedlist 1.7 проходит все эти тесты как для Python 2.7, так и для Python 3.5 по состоянию на 11 января 2016 года. Это чистая реализация Python.

Вторым лучшим кандидатом в соответствии с этими тестами является recordclass, который является расширением C. Конечно, это зависит от ваших требований, предпочтительнее ли расширение C или нет.

Дополнительные сведения, особенно для тестов, см. в разделе Существование изменяемого именованного кортежа в Python?

person Ali    schedule 11.01.2016

Основываясь на нескольких полезных приемах, собранных с течением времени, этот декоратор «замороженного класса» делает практически все необходимое: http://pastebin.com/fsuVyM45

Поскольку этот код более чем на 70% состоит из документации и тестов, я не буду здесь больше говорить.

person BobC    schedule 10.12.2013

Вот полный изменяемый 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})
person Architektor    schedule 10.05.2015

Вы можете сделать что-то вроде thisdictsubclass, который является собственным __dict__. Основная концепция та же, что и у рецепта ActiveState AttrDict, но реализация проще. В результате получается нечто более изменчивое, чем вам нужно, поскольку изменяются как атрибуты экземпляра, так и их значения. Хотя атрибуты не упорядочены, вы можете перебирать текущие атрибуты и/или их значения.

class Record(dict):
    def __init__(self, *args, **kwargs):
        super(Record, self).__init__(*args, **kwargs)
        self.__dict__ = self
person martineau    schedule 16.03.2013

Как указано tzot , поскольку Python ≥3.3, Python имеет изменяемую версию namedtuple: types.SimpleNamespace.

Эти вещи очень похожи на новые Записи C# 9.

Вот несколько примеров использования:

Аргументы позиционного конструктора

>>> import types
>>>
>>> class Location(types.SimpleNamespace):
...   def __init__(self, lat=0, long=0):
...     super().__init__(lat=lat, long=long)
...
>>> loc_1 = Location(49.4, 8.7)

Довольно репр

>>> loc_1
Location(lat=49.4, long=8.7)

Изменяемый

>>> loc_2 = Location()
>>> loc_2
Location(lat=0, long=0)
>>> loc_2.lat = 49.4
>>> loc_2
Location(lat=49.4, long=0)

Семантика значений для равенства

>>> loc_2 == loc_1
False
>>> loc_2.long = 8.7
>>> loc_2 == loc_1
True

Можно добавлять атрибуты во время выполнения

>>> loc_2.city = 'Heidelberg'
>>> loc_2
person Grilse    schedule 02.12.2020