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

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

383 голосов
/ 30 августа 2008

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

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

Ответы [ 22 ]

7 голосов
/ 09 ноября 2014

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

6 голосов
/ 05 августа 2017

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

edit: Извините, но не видел этот пример ниже.

4 голосов
/ 08 сентября 2015

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

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

3 голосов
/ 23 марта 2015

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

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

1 голос
/ 11 декабря 2018

Для этого есть пакет 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
1 голос
/ 09 апреля 2019

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

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

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

myStruct.field3 = 3

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

>>> myStruct.field1
'one'
1 голос
/ 29 марта 2018

Следующее решение для структуры вдохновлено реализацией 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'
1 голос
/ 06 ноября 2017

Лично мне тоже нравится этот вариант. Это расширяет ответ @ 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}
0 голосов
/ 28 января 2019

Если у вас нет 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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...