C-подобные структуры в Python

Есть ли способ удобно определить C-подобную структуру в Python? Я устал писать такие вещи, как:

class MyStruct():
    def __init__(self, field1, field2, field3):
        self.field1 = field1
        self.field2 = field2
        self.field3 = field3

С другой стороны, алгебраические типы данных были бы просто замечательными, но для их правильного использования обычно требуется сопоставление с образцом.

Edward Z. Yang 06.09.2010 07:03

Что-то не так с этим методом, кроме того, что его утомительно писать?

levesque 15.09.2010 01:32

Вы можете найти dstruct полезным: github.com/dorkitude/dstruct

Kyle Wild 21.08.2014 00:58

@levesque сложнее пересчитать без опечаток, сложнее прочитать сразу при просмотре кода, чем MyStruct = namedtuple("MyStruct", "field1 field2 field3")

sam boosalis 03.09.2014 02:05

Хорошо namedtuple генерирует класс, так в чем именно разница?

bx2 26.01.2015 22:44

pandas.Series(a=42).a должен это сделать, если вы специалист по данным ...

Mark Horvath 05.03.2015 04:43

Переходите к ответу 2018 года: stackoverflow.com/a/45426493/703382

Navin 13.05.2018 03:13

Сам ваш вопрос имеет правильный ответ на «C-подобные структуры в Python» или «Как создать структуру в Python». Изучив все ответы ниже, я бы сказал, что использование класса, как вы это сделали, - лучший способ сделать это.Использование словаря - альтернатива и, возможно, второй лучший способ сделать это..

Gabriel Staples 02.12.2018 04:19

Если вас беспокоит производительность (ОЗУ / ЦП), тогда ничто не сравнится с настоящим кортежем.

Basj 09.12.2020 12:08
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
501
9
599 328
25

Ответы 25

Как насчет словаря?

Что-то вроде этого:

myStruct = {'field1': 'some val', 'field2': 'some val'}

Затем вы можете использовать это для управления значениями:

print myStruct['field1']
myStruct['field2'] = 'some other values'

И значения не обязательно должны быть строками. Они могут быть практически любым другим объектом.

Это был мой подход, но я считаю, что это опасно именно потому, что словарь может принимать что угодно в качестве ключа. Не будет ошибки, если я установлю myStruct ["ffield"], когда я хотел установить myStruct ["field"]. Проблема может (или не может) стать очевидной, когда я использую или повторно использую myStruct ["field"] позже. Мне нравится подход PabloG.

mobabo 13.02.2014 04:30

Та же проблема существует с PabloG's. Попробуйте добавить к нему следующий код: pt3.w = 1 print pt3.w На языке с dicts лучше использовать их, особенно для сериализуемых объектов, поскольку вы можете автоматически использовать import json для их сохранения и другие библиотеки сериализации, если у вас нет странные вещи внутри вашего dict. Dicts - это решение для разделения данных и логики, и они лучше, чем структуры, для людей, которые не хотят писать собственные функции сериализации и десериализации и не хотят использовать непереносимые сериализаторы, такие как pickle.

Poikilos 09.08.2018 20:16

Вы можете использовать кортеж для многих вещей, где вы использовали бы структуру в C (например, что-то вроде координат x, y или цветов RGB).

Для всего остального вы можете использовать словарь или служебный класс, например Вот этот:

>>> class Bunch:
...     def __init__(self, **kwds):
...         self.__dict__.update(kwds)
...
>>> mystruct = Bunch(field1=value1, field2=value2)

Я думаю, что «окончательное» обсуждение - это здесь в опубликованной версии Python Cookbook.

Будет ли пустой класс делать то же самое?

Kurt Liu 20.07.2011 22:01

Обратите внимание, если вы новичок в python: кортежи создаются только для чтения после создания, в отличие от структур C

LeBleu 30.10.2011 22:28

@KurtLiu Нет, наверное, TypeError: this constructor takes no arguments

Evgeni Sergeev 23.05.2015 08:15

Здесь используется объект с внутренним dict __dict__ (ну, как и все объекты, за исключением случаев, когда вы используете __slots__). Так почему бы не использовать dict напрямую? mystruct = {'field1': value1, 'field2': value2}. TL; DR: здесь вы создаете объект просто с целью использования его внутреннего dictobject.__dict__, поэтому просто используйте диктовку с самого начала будет менее сложным.

Basj 06.12.2020 01:14

... тем более, что вы можете просто сделать a = dict(foo=123, bar=456), чтобы сделать этот dict, если вам нравится синтаксис вызова функции с ключевыми словами лучше, чем обычный синтаксис dict, а также str() / repr() несколько более полезны, чем просто предоставление идентификатора объекта.

ilkkachu 17.02.2021 13:29

Используйте названный кортеж, который был добавлен в модуль коллекций в стандартной библиотеке Python 2.6. Также можно использовать рецепт названный кортеж Раймонда Хеттингера, если вам нужна поддержка Python 2.4.

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

from collections import namedtuple
MyStruct = namedtuple("MyStruct", "field1 field2 field3")

Вновь созданный тип можно использовать так:

m = MyStruct("foo", "bar", "baz")

Вы также можете использовать именованные аргументы:

m = MyStruct(field1 = "foo", field2 = "bar", field3 = "baz")

... но namedtuple неизменен. Пример в OP изменчив.

mhowison 17.01.2013 21:46

@mhowison - В моем случае это просто плюс.

ArtOfWarfare 12.08.2014 15:29

Хорошее решение. Как бы вы перебрали массив этих кортежей? Я предполагаю, что поля 1-3 должны иметь одинаковые имена во всех объектах кортежа.

Michael Smith 13.01.2015 21:57

namedtuple может иметь не более четырех аргументов, поэтому как мы можем сопоставить структуру с большим количеством элементов данных с соответствующим namedtuple

PapaDiHatti 19.07.2017 13:52

@Kapil Для какой версии Python это применимо?

Rotareti 01.08.2017 01:47

@ArtOfWarfare знает это.

Rotareti 14.10.2017 05:24

@Kapil - вторым аргументом namedtuple должен быть список имен членов. Этот список может быть любой длины.

ArtOfWarfare 14.10.2017 14:10

Существует изменяемый вариант namedtuple - recordclass (bitbucket.org/intellimath/recordclass/src/default/README.md‌)

intellimath 11.12.2018 17:18

dF: that's pretty cool... I didn't know that I could access the fields in a class using dict.

Mark: the situations that I wish I had this are precisely when I want a tuple but nothing as "heavy" as a dictionary.

Вы можете получить доступ к полям класса с помощью словаря, потому что поля класса, его методы и все его свойства хранятся внутри с помощью dicts (по крайней мере, в CPython).

... Это подводит нас к вашему второму комментарию. Вера в то, что диктовки Python «тяжелые» - это крайне непифонистская концепция. И чтение таких комментариев убивает мой Python Zen. Это не хорошо.

Видите ли, когда вы объявляете класс, вы фактически создаете довольно сложную оболочку вокруг словаря - так что, во всяком случае, вы добавляете больше накладных расходов, чем при использовании простого словаря. Накладные расходы, которые, кстати, в любом случае бессмысленны. Если вы работаете над приложениями, критичными к производительности, используйте C или что-то в этом роде.

# 1, Cython! = CPython. Я думаю, вы говорили о CPython, реализации Python, написанном на C, а не Cython, проекте кросс-компиляции кода Python в код C. Я отредактировал ваш ответ, чтобы исправить это. №2, я думаю, когда он сказал, что диктовки тяжелые, он имел в виду синтаксис. self['member'] на 3 символа длиннее, чем self.member, и все эти символы относительно неудобны для запястий.

ArtOfWarfare 08.09.2015 05:16

Вы также можете передать параметры инициализации в переменные экземпляра по позиции

# Abstract struct class       
class Struct:
    def __init__ (self, *argv, **argd):
        if len(argd):
            # Update by dictionary
            self.__dict__.update (argd)
        else:
            # Update by position
            attrs = filter (lambda x: x[0:2] != "__", dir(self))
            for n in range(len(argv)):
                setattr(self, attrs[n], argv[n])

# Specific class
class Point3dStruct (Struct):
    x = 0
    y = 0
    z = 0

pt1 = Point3dStruct()
pt1.x = 10

print pt1.x
print "-"*10

pt2 = Point3dStruct(5, 6)

print pt2.x, pt2.y
print "-"*10

pt3 = Point3dStruct (x=1, y=2, z=3)
print pt3.x, pt3.y, pt3.z
print "-"*10

Обновление по позиции игнорирует порядок объявления атрибутов и вместо этого использует их алфавитную сортировку. Поэтому, если вы измените порядок строк в декларации Point3dStruct, Point3dStruct(5, 6) не будет работать должным образом. Странно, что этого никто не написал за все 6 лет.

lapis 25.06.2014 01:50

Могли бы вы добавить версию Python 3 в свой потрясающий код? Отличная работа! Мне нравится, что вы берете что-то абстрактное и делаете это явным во втором конкретном классе. Это должно быть хорошо для обработки / отлова ошибок. Для Python 3 просто измените print> print() и attrs[n]> next(attrs) (теперь фильтр является отдельным итерируемым объектом и требует next).

Jonathan Komar 17.05.2017 14:36

Возможно, вы ищете Структуры без конструкторов:

class Sample:
  name = ''
  average = 0.0
  values = None # list cannot be initialized here!


s1 = Sample()
s1.name = "sample 1"
s1.values = []
s1.values.append(1)
s1.values.append(2)
s1.values.append(3)

s2 = Sample()
s2.name = "sample 2"
s2.values = []
s2.values.append(4)

for v in s1.values:   # prints 1,2,3 --> OK.
  print v
print "***"
for v in s2.values:   # prints 4 --> OK.
  print v

То, что вы здесь делаете, технически работает, но для многих пользователей, вероятно, не сразу очевидно Почему, что это работает. Ваши заявления в соответствии с class Sample: ничего не делают немедленно; они устанавливают атрибуты класса. К ним всегда можно получить доступ, например, Sample.name.

Channing Moore 05.06.2014 02:59

То, что вы делаете фактически, - это добавление свойств экземпляра к объектам s1 и s2 во время выполнения. Если не запрещено иное, вы можете добавить или изменить атрибут name в любом экземпляре любого класса в любое время, независимо от того, имеет ли класс атрибут name. Вероятно, самая большая функциональная проблема с этим заключается в том, что разные экземпляры одного и того же класса будут вести себя по-разному в зависимости от того, установлен ли name. Если вы обновите Sample.name, любые объекты без явно заданного свойства name вернут новый name.

Channing Moore 05.06.2014 03:56

Это максимально близко к структуре - короткий «класс» без методов, «поля» (атрибуты класса, я знаю) со значениями по умолчанию. Пока это не изменяемый тип (dict, list), все в порядке. Конечно, вы можете столкнуться с PEP-8 или «дружественными» проверками IDE, такими как «класс PyCharm не имеет метода в этом».

Tomasz Gandor 05.04.2016 02:39

Я экспериментировал с побочным эффектом, описанным Ченнингом Муром. Не стоит экономии нескольких ключевых слов self и строки конструктора, если вы спросите меня. Я был бы признателен, если бы Хосе мог отредактировать свой ответ, чтобы добавить предупреждающее сообщение о риске случайного обмена значениями между экземплярами.

Stéphane C. 15.11.2016 13:05

@ChanningMoore: Я попытался воссоздать проблему, которую вы описывали, но не смог. Не могли бы вы представить минимальный рабочий пример, в котором возникает проблема?

gebbissimo 28.01.2019 12:17

Итак, вы определяете переменные класса, но затем фактически заслоняете их переменными экземпляров, верно? Потому что s1.__class__.name = '' даже после установки s1.name = "sample1". Это кажется странным. Более того, такая опечатка, как s1.nname = "test", не вызовет никаких ошибок ...

gebbissimo 28.01.2019 13:16

@gebissimo, вы столкнетесь с проблемами, если используете изменяемые типы, такие как list, dict, set. Это то, что скрыто комментарием Хосе «список не может быть инициализирован здесь»: вы может инициализируете список, но вы рискуете обновить его. Это тот же риск, что и использование списка в качестве значения kwarg по умолчанию в определении функции. docs.quantifiedcode.com/python-anti-patterns/correctness/…

Channing Moore 29.05.2019 21:19

@gebissimo, ты совершенно прав, что он следит за атрибутами класса. С тех пор, как я написал этот комментарий, я узнал о двух лучших вариантах: именованные кортежи и буферы протокола, которые больше похожи на структуры C.

Channing Moore 29.05.2019 21:25

Всякий раз, когда мне нужен «объект мгновенных данных, который также ведет себя как словарь» (я, не, думаю о структурах C!), Я думаю об этом симпатичном приеме:

class Map(dict):
    def __init__(self, **kwargs):
        super(Map, self).__init__(**kwargs)
        self.__dict__ = self

Теперь вы можете просто сказать:

struct = Map(field1='foo', field2='bar', field3=42)

self.assertEquals('bar', struct.field2)
self.assertEquals(42, struct['field3'])

Идеально подходит для тех случаев, когда вам нужен «пакет данных, который НЕ является классом», и когда именованные кортежи непонятны ...

Я использую pandas.Series (a = 42) ;-)

Mark Horvath 05.03.2015 04:39

Вы получаете доступ к структуре C-Style в Python следующим образом.

class cstruct:
    var_i = 0
    var_f = 0.0
    var_str = ""

если вы просто хотите использовать объект cstruct

obj = cstruct()
obj.var_i = 50
obj.var_f = 50.00
obj.var_str = "fifty"
print "cstruct: obj i=%d f=%f s=%s" %(obj.var_i, obj.var_f, obj.var_str)

если вы хотите создать массив объектов cstruct

obj_array = [cstruct() for i in range(10)]
obj_array[0].var_i = 10
obj_array[0].var_f = 10.00
obj_array[0].var_str = "ten"

#go ahead and fill rest of array instaces of struct

#print all the value
for i in range(10):
    print "cstruct: obj_array i=%d f=%f s=%s" %(obj_array[i].var_i, obj_array[i].var_f, obj_array[i].var_str)

Примечание: вместо имени 'cstruct' используйте свое имя структуры вместо var_i, var_f, var_str определите переменную-член вашей структуры.

Это чем-то отличается от того, что находится в stackoverflow.com/a/3761729/1877426?

lagweezle 10.02.2015 21:56

Это может быть немного поздно, но я сделал решение, используя метаклассы Python (версия декоратора также ниже).

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

В моем примере нет проверки ошибок, поэтому ему легче следовать.

class MyStruct(type):
    def __call__(cls, *args, **kwargs):
        names = cls.__init__.func_code.co_varnames[1:]

        self = type.__call__(cls, *args, **kwargs)

        for name, value in zip(names, args):
            setattr(self , name, value)

        for name, value in kwargs.iteritems():
            setattr(self , name, value)
        return self 

Вот оно в действии.

>>> class MyClass(object):
    __metaclass__ = MyStruct
    def __init__(self, a, b, c):
        pass


>>> my_instance = MyClass(1, 2, 3)
>>> my_instance.a
1
>>> 

Я опубликовал это на Reddit и / u / matchu опубликовал более чистую версию декоратора. Я рекомендую вам использовать его, если вы не хотите расширять версию метакласса.

>>> def init_all_args(fn):
    @wraps(fn)
    def wrapped_init(self, *args, **kwargs):
        names = fn.func_code.co_varnames[1:]

        for name, value in zip(names, args):
            setattr(self, name, value)

        for name, value in kwargs.iteritems():
            setattr(self, name, value)

    return wrapped_init

>>> class Test(object):
    @init_all_args
    def __init__(self, a, b):
        pass


>>> a = Test(1, 2)
>>> a.a
1
>>> 

Черт побери, я потратил два часа на то, чтобы написать свой собственный декоратор, а потом нашел вот это. В любом случае, опубликовать мой, потому что он обрабатывает значения по умолчанию, а ваш - нет. stackoverflow.com/a/32448434/901641

ArtOfWarfare 08.09.2015 05:39

+1 за упоминание func_code. Начал копать в том направлении и нашел там много интересного.

wombatonfire 28.03.2016 22:31

Вы можете создать подкласс структуры C, доступной в стандартной библиотеке. Модуль ctypes предоставляет Структурный класс. Пример из документов:

>>> from ctypes import *
>>> class POINT(Structure):
...     _fields_ = [("x", c_int),
...                 ("y", c_int)]
...
>>> point = POINT(10, 20)
>>> print point.x, point.y
10 20
>>> point = POINT(y=5)
>>> print point.x, point.y
0 5
>>> POINT(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ValueError: too many initializers
>>>
>>> class RECT(Structure):
...     _fields_ = [("upperleft", POINT),
...                 ("lowerright", POINT)]
...
>>> rc = RECT(point)
>>> print rc.upperleft.x, rc.upperleft.y
0 5
>>> print rc.lowerright.x, rc.lowerright.y
0 0
>>>

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

def argumentsToAttributes(method):
    argumentNames = method.func_code.co_varnames[1:]

    # Generate a dictionary of default values:
    defaultsDict = {}
    defaults = method.func_defaults if method.func_defaults else ()
    for i, default in enumerate(defaults, start = len(argumentNames) - len(defaults)):
        defaultsDict[argumentNames[i]] = default

    def newMethod(self, *args, **kwargs):
        # Use the positional arguments.
        for name, value in zip(argumentNames, args):
            setattr(self, name, value)

        # Add the key word arguments. If anything is missing, use the default.
        for name in argumentNames[len(args):]:
            setattr(self, name, kwargs.get(name, defaultsDict[name]))

        # Run whatever else the method needs to do.
        method(self, *args, **kwargs)

    return newMethod

Быстрая демонстрация. Обратите внимание, что я использую позиционный аргумент a, использую значение по умолчанию для b и именованный аргумент c. Затем я распечатываю все 3 ссылки self, чтобы показать, что они были правильно назначены до ввода метода.

class A(object):
    @argumentsToAttributes
    def __init__(self, a, b = 'Invisible', c = 'Hello'):
        print(self.a)
        print(self.b)
        print(self.c)

A('Why', c = 'Nothing')

Обратите внимание, что мой декоратор должен работать с любым методом, а не только с __init__.

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

class Employee:
    pass

john = Employee()  # Create an empty employee record

# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000

То есть создается пустой объект класса, затем создается его экземпляр, а поля добавляются динамически.

Плюс в том, что это действительно просто. Обратной стороной является отсутствие самодокументирования (предполагаемые члены нигде не указаны в «определении» класса), а неустановленные поля могут вызвать проблемы при доступе. Эти две проблемы можно решить следующим образом:

class Employee:
    def __init__ (self):
        self.name = None # or whatever
        self.dept = None
        self.salary = None

Теперь вы можете сразу увидеть, какие поля ожидает программа.

Оба подвержены опечаткам, john.slarly = 1000 будет успешным. Тем не менее, это работает.

Я думаю, что словарь структур Python подходит для этого требования.

d = dict{}
d[field1] = field1
d[field2] = field2
d[field2] = field3

Обновлять: классы данных

С введением Классы данных в Python 3.7 мы очень близко подошли.

Следующий пример похож на пример NamedTuple ниже, но результирующий объект - изменчивый, и он допускает значения по умолчанию.

from dataclasses import dataclass


@dataclass
class Point:
    x: float
    y: float
    z: float = 0.0


p = Point(1.5, 2.5)

print(p)  # Point(x=1.5, y=2.5, z=0.0)

Это хорошо сочетается с новым модулем набор текста, если вы хотите использовать более конкретные аннотации типов.

Я так долго этого ждал! Если вы спросите меня, Классы данных и новое объявление NamedTuple в сочетании с модулем набор текста - это находка!

Улучшенное объявление NamedTuple

С Python 3.6 стало довольно просто и красиво (ИМХО), пока можно жить с неизменность.

Был введен новый способ объявления NamedTuples, который также позволяет использовать аннотации типов:

from typing import NamedTuple


class User(NamedTuple):
    name: str


class MyStruct(NamedTuple):
    foo: str
    bar: int
    baz: list
    qux: User


my_item = MyStruct('foo', 0, ['baz'], User('peter'))

print(my_item) # MyStruct(foo='foo', bar=0, baz=['baz'], qux=User(name='peter'))

Приятель, ты только что сделал мне день - неизменные наречия - спасибо: D

Dmitry Arkhipenko 11.12.2017 20:00

Модуль dataclass является новым в Python 3.7, но вы можете использовать pip install dataclasses. Это бэкпорт на Python 3.6. pypi.org/project/dataclasses/#description

Lavande 22.05.2018 02:50

+1 за улучшенное объявление NamedTuple. Старый способ было действительно неприятно читать, если у вас было несколько переменных ...

gebbissimo 28.01.2019 14:07

@Lavande Могу ли я узнать, какие критические изменения произошли между 3.6 и 3.7, которые вам нужно перенести обратно на одну минорную версию ...?

Purple Ice 17.02.2019 21:36

@PurpleIce Это была реализация PEP 557, классы данных @dataclass. Подробности здесь: pypi.org/project/dataclasses/#description

Lavande 19.02.2019 07:30

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

StephenBoesch 20.12.2020 19:50

Даже с типизированными полями вы можете присвоить любой тип любой переменной в классе. Например, это работает: my_item = MyStruct(123, 123, 123, 123), и каждое поле в my_item будет целым числом со значением 123. То же самое и для класса данных.

foolo 08.01.2021 21:04

@foolo Сам Python не проверяет аннотации вашего типа. Но вы можете использовать для этого средства проверки статического типа, такие как mypy. Это также включает поддержку проверки типов в вашем редакторе. Например. вы можете указать VSCode использовать mypy.

Rotareti 08.01.2021 21:24

Некоторые ответы здесь очень сложны. Самый простой вариант, который я нашел (от: http://norvig.com/python-iaq.html):

class Struct:
    "A structure that can have any fields defined."
    def __init__(self, **entries): self.__dict__.update(entries)

Инициализация:

>>> options = Struct(answer=42, linelen=80, font='courier')
>>> options.answer
42

добавление еще:

>>> options.cat = "dog"
>>> options.cat
dog

редактировать: Извините, я не видел этого примера ниже.

отсутствует метод __repr__(), который важен для imo

StephenBoesch 26.12.2020 17:11

Согласовано! Могу добавить, да.

w_jay 08.01.2021 13:14

https://stackoverflow.com/a/32448434/159695 не работает в Python3.

https://stackoverflow.com/a/35993/159695 работает на Python3.

И я расширяю его, добавляя значения по умолчанию.

class myStruct:
    def __init__(self, **kwds):
        self.x=0
        self.__dict__.update(kwds) # Must be last to accept assigned member variable.
    def __repr__(self):
        args = ['%s=%s' % (k, repr(v)) for (k,v) in vars(self).items()]
        return '%s(%s)' % ( self.__class__.__qualname__, ', '.join(args) )

a=myStruct()
b=myStruct(x=3,y='test')
c=myStruct(x='str')

>>> a
myStruct(x=0)
>>> b
myStruct(x=3, y='test')
>>> c
myStruct(x='str')

Отличная работа, включая новый для меня self.__class__.__qualname__

StephenBoesch 20.12.2020 19:46

Лично мне этот вариант тоже нравится. Он расширяет @ dF ответ.

class struct:
    def __init__(self, *sequential, **named):
        fields = dict(zip(sequential, [None]*len(sequential)), **named)
        self.__dict__.update(fields)
    def __repr__(self):
        return str(self.__dict__)

Он поддерживает два режима инициализации (которые можно комбинировать):

# Struct with field1, field2, field3 that are initialized to None.
mystruct1 = struct("field1", "field2", "field3") 
# Struct with field1, field2, field3 that are initialized according to arguments.
mystruct2 = struct(field1=1, field2=2, field3=3)

Кроме того, он лучше печатает:

print(mystruct2)
# Prints: {'field3': 3, 'field1': 1, 'field2': 2}

Следующее решение для структуры основано на реализации namedtuple и некоторых предыдущих ответах. Однако, в отличие от namedtuple, он является изменяемым по своим значениям, но, как и структура в стиле c, неизменяема в именах / атрибутах, чего нет в обычном классе или dict.

_class_template = """\
class {typename}:
def __init__(self, *args, **kwargs):
    fields = {field_names!r}

    for x in fields:
        setattr(self, x, None)            

    for name, value in zip(fields, args):
        setattr(self, name, value)

    for name, value in kwargs.items():
        setattr(self, name, value)            

def __repr__(self):
    return str(vars(self))

def __setattr__(self, name, value):
    if name not in {field_names!r}:
        raise KeyError("invalid name: %s" % name)
    object.__setattr__(self, name, value)            
"""

def struct(typename, field_names):

    class_definition = _class_template.format(
        typename = typename,
        field_names = field_names)

    namespace = dict(__name__='struct_%s' % typename)
    exec(class_definition, namespace)
    result = namespace[typename]
    result._source = class_definition

    return result

Использование:

Person = struct('Person', ['firstname','lastname'])
generic = Person()
michael = Person('Michael')
jones = Person(lastname = 'Jones')


In [168]: michael.middlename = 'ben'
Traceback (most recent call last):

  File "<ipython-input-168-b31c393c0d67>", line 1, in <module>
michael.middlename = 'ben'

  File "<string>", line 19, in __setattr__

KeyError: 'invalid name: middlename'

Еще хотелось бы добавить решение, использующее слоты:

class Point:
    __slots__ = ["x", "y"]
    def __init__(self, x, y):
        self.x = x
        self.y = y

Определенно проверьте документацию на наличие слотов, но быстрое объяснение слотов заключается в том, что это способ Python сказать: «Если вы можете заблокировать эти атрибуты и только эти атрибуты в классе, так что вы фиксируете, что вы не добавите никаких новых атрибутов после того, как класс создается экземпляр (да, вы можете добавлять новые атрибуты в экземпляр класса, см. пример ниже), тогда я покончу с выделением большого объема памяти, которое позволяет добавлять новые атрибуты в экземпляр класса, и буду использовать только то, что мне нужно для этих атрибутов прорезанный ».

Пример добавления атрибутов к экземпляру класса (без использования слотов):

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p1 = Point(3,5)
p1.z = 8
print(p1.z)

Выход: 8

Пример попытки добавить атрибуты к экземпляру класса, в котором использовались слоты:

class Point:
    __slots__ = ["x", "y"]
    def __init__(self, x, y):
        self.x = x
        self.y = y

p1 = Point(3,5)
p1.z = 8

Вывод: AttributeError: объект 'Point' не имеет атрибута 'z'

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

Информация о новых для меня slots

StephenBoesch 20.12.2020 19:47

Именно для этого есть пакет python. см. cstruct2py

cstruct2py - это чистая библиотека Python для создания классов Python из кода C и использования их для упаковки и распаковки данных. Библиотека может анализировать заголовки C (объявления структур, объединений, перечислений и массивов) и эмулировать их в Python. Сгенерированные питонические классы могут анализировать и упаковывать данные.

Например:

typedef struct {
  int x;
  int y;
} Point;

after generating pythonic class...
p = Point(x=0x1234, y=0x5678)
p.packed == "\x34\x12\x00\x00\x78\x56\x00\x00"

How to use

Сначала нам нужно сгенерировать питонические структуры:

import cstruct2py
parser = cstruct2py.c2py.Parser()
parser.parse_file('examples/example.h')

Теперь мы можем импортировать все имена из кода C:

parser.update_globals(globals())

Мы также можем сделать это напрямую:

A = parser.parse_string('struct A { int x; int y;};')

Использование типов и определений из кода C

a = A()
a.x = 45
print a
buf = a.packed
b = A(buf)
print b
c = A('aaaa11112222', 2)
print c
print repr(c)

Результатом будет:

{'x':0x2d, 'y':0x0}
{'x':0x2d, 'y':0x0}
{'x':0x31316161, 'y':0x32323131}
A('aa111122', x=0x31316161, y=0x32323131)

Clone

Для клона cstruct2py запустите:

git clone https://github.com/st0ky/cstruct2py.git --recursive

Если у вас нет версии 3.7 для @dataclass и вам нужна изменчивость, следующий код может сработать для вас. Он довольно самодокументируется и удобен для IDE (автозаполнение), предотвращает повторную запись, легко расширяется и очень просто проверить, что все переменные экземпляра полностью инициализированы:

class Params():
    def __init__(self):
        self.var1 : int = None
        self.var2 : str = None

    def are_all_defined(self):
        for key, value in self.__dict__.items():
            assert (value is not None), "instance variable {} is still None".format(key)
        return True


params = Params()
params.var1 = 2
params.var2 = 'hello'
assert(params.are_all_defined)

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

class myStruct:
    field1 = "one"
    field2 = "2"

При необходимости вы можете добавить дополнительные поля позже:

myStruct.field3 = 3

Для получения значений доступ к полям осуществляется как обычно:

>>> myStruct.field1
'one'

Он вроде работает, но не умеет распечатывать myStruct Out[5]: __main__.myStruct

StephenBoesch 20.12.2020 19:40

И почему бы вам не создать экземпляр класса? Что, если кто-то хочет использовать одну и ту же структуру для разных значений? s1 = myStruct; s2 = myStruct; s1.field1 = "two" изменяет s2. Это зависит от варианта использования, но я думаю, что в целом лучше / безопаснее создать экземпляр структуры: s1 myStruct(); s2 = myStruct()

normanius 20.12.2020 20:52

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

jochen 26.12.2020 16:45

@normanius Конечно, если вам нужно скопировать свои «структуры», вам нужно быть более умным, и я согласен с тем, что создание экземпляров может создавать копии для вас, если это необходимо.

jochen 26.12.2020 16:46

Вот быстрый и грязный трюк:

>>> ms = Warning()
>>> ms.foo = 123
>>> ms.bar = 'akafrit'

Как это работает? Он просто повторно использует встроенный класс Warning (производный от Exception) и использует его как собственный определенный класс.

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

Кстати, я пытался найти что-то еще более простое, например ms = object(), но не смог (последний пример не работает). Если он у вас есть, мне интересно.

Нет голосов? Это хорошо

StephenBoesch 20.12.2020 19:38

Лучший способ, который я нашел для этого, - использовать собственный класс словаря, как описано в этом сообщении: https://stackoverflow.com/a/14620633/8484485

Если требуется поддержка автозаполнения iPython, просто определите функцию реж () следующим образом:

class AttrDict(dict):
    def __init__(self, *args, **kwargs):
        super(AttrDict, self).__init__(*args, **kwargs)
        self.__dict__ = self
    def __dir__(self):
        return self.keys()

Затем вы определяете свою псевдоструктуру следующим образом: (эта вложенная)

my_struct=AttrDict ({
    'com1':AttrDict ({
        'inst':[0x05],
        'numbytes':2,
        'canpayload':False,
        'payload':None
    })
})

Затем вы можете получить доступ к значениям внутри my_struct следующим образом:

print(my_struct.com1.inst)

=> [5]

NamedTuple удобен. но там никто не делится производительностью и хранилищем.

from typing import NamedTuple
import guppy  # pip install guppy
import timeit


class User:
    def __init__(self, name: str, uid: int):
        self.name = name
        self.uid = uid


class UserSlot:
    __slots__ = ('name', 'uid')

    def __init__(self, name: str, uid: int):
        self.name = name
        self.uid = uid


class UserTuple(NamedTuple):
    # __slots__ = ()  # AttributeError: Cannot overwrite NamedTuple attribute __slots__
    name: str
    uid: int


def get_fn(obj, attr_name: str):
    def get():
        getattr(obj, attr_name)
    return get
if 'memory test':
    obj = [User('Carson', 1) for _ in range(1000000)]      # Cumulative: 189138883
    obj_slot = [UserSlot('Carson', 1) for _ in range(1000000)]          # 77718299  <-- winner
    obj_namedtuple = [UserTuple('Carson', 1) for _ in range(1000000)]   # 85718297
    print(guppy.hpy().heap())  # Run this function individually. 
    """
    Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0 1000000    24 112000000 34 112000000  34 dict of __main__.User
     1 1000000    24 64000000  19 176000000  53 __main__.UserTuple
     2 1000000    24 56000000  17 232000000  70 __main__.User
     3 1000000    24 56000000  17 288000000  87 __main__.UserSlot
     ...
    """

if 'performance test':
    obj = User('Carson', 1)
    obj_slot = UserSlot('Carson', 1)
    obj_tuple = UserTuple('Carson', 1)

    time_normal = min(timeit.repeat(get_fn(obj, 'name'), repeat=20))
    print(time_normal)  # 0.12550550000000005

    time_slot = min(timeit.repeat(get_fn(obj_slot, 'name'), repeat=20))
    print(time_slot)  # 0.1368690000000008

    time_tuple = min(timeit.repeat(get_fn(obj_tuple, 'name'), repeat=20))
    print(time_tuple)  # 0.16006120000000124

    print(time_tuple/time_slot)  # 1.1694481584580898  # The slot is almost 17% faster than NamedTuple on Windows. (Python 3.7.7)

Если ваш __dict__ не используется, выберите между __slots__ (более высокая производительность и хранилище) или NamedTuple (доступно для чтения и использования)

Вы можете просмотреть эту ссылку (Использование слоты), чтобы получить дополнительную информацию о __slots__.

Другие вопросы по теме