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

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

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

Ответ 1

Используйте named tuple, который был добавлен в модуль коллекций в стандартной библиотеке в Python 2.6. Также можно использовать рецепт Raymond Hettinger named tuple, если вам нужно поддерживать 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")

Ответ 2

Обновление: классы данных

С введением классов данных в 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'))

Ответ 3

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

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

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

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

Ответ 4

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

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

Ответ 5

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

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

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

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

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

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

Ответ 6

dF: это довольно круто... Я не знайте, что я могу получить доступ к полям в класс с использованием dict.

Отметьте: ситуации, которые, как мне бы хотелось, это именно то, когда я хочу кортеж но ничто как "тяжелое", как словарь.

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

... Это приводит нас к вашему второму комментарию. Полагая, что Python dicts являются "тяжелыми", это чрезвычайно непитонистская концепция. И чтение таких комментариев убивает мой Python Zen. Это нехорошо.

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

Ответ 7

Я также хотел бы добавить решение, которое использует слоты:

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'

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

Ответ 8

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

# 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

Ответ 9

Вы можете подклассифицировать структуру 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
>>>

Ответ 10

Всякий раз, когда мне нужен "мгновенный объект данных, который также ведет себя как словарь" (я не думаю о 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'])

Отлично подходит для тех случаев, когда вам нужен "пакет данных, который НЕ является классом", а также, когда namedtuples непонятны...

Ответ 11

Вы получаете доступ к структуре 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, определите переменную члена структуры.

Ответ 12

Некоторые ответы здесь очень тщательно продуманы. Простейший вариант, который я нашел (от: 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

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

Ответ 13

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

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__.

Ответ 14

Это может быть немного поздно, но я сделал решение, используя мета-классы 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
>>> 

I разместил его на 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
>>> 

Ответ 15

Я не вижу здесь этого ответа, поэтому я полагаю, что добавлю его с тех пор, как я прямо сейчас опишу 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 будет успешным. Тем не менее, он работает.

Ответ 16

Лично мне тоже нравится этот вариант. Он расширяет @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}

Ответ 17

Следующее решение для структуры вдохновлено реализацией namedtuple и некоторыми из предыдущих ответов. Однако, в отличие от namedtuple, он является изменяемым, в нем значения, но как структура c-style неизменяемая в именах/атрибутах, что не является нормальным классом или 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'

Ответ 18

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

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

Вы можете добавить больше полей позже, по мере необходимости:

myStruct.field3 = 3

Чтобы получить значения, поля доступны как обычно:

>>> myStruct.field1
'one'

Ответ 19

Для этого есть пакет python. смотри cstruct2py

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

Например:

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"

Как пользоваться

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

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)

клон

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

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

Ответ 20

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

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

Ответ 21

fooobar.com/questions/25416/... не работает в Python3.

fooobar.com/questions/25416/... работает в 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')

Ответ 22

Если у вас нет 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)

Ответ 23

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

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

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

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

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