关于Python中的struct:C类结构

关于Python中的struct:C类结构

C-like structures in Python

有没有一种方法可以在Python中方便地定义类似C的结构? 我讨厌写这样的东西:

1
2
3
4
5
class MyStruct():
    def __init__(self, field1, field2, field3):
        self.field1 = field1
        self.field2 = field2
        self.field3 = field3

使用命名元组,该元组已添加到Python 2.6的标准库中的collections模块中。如果您需要支持Python 2.4,也可以使用Raymond Hettinger的命名元组配方。

这对于您的基本示例很好,但是也涵盖了以后可能会遇到的许多极端情况。您上面的片段将写为:

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

新创建的类型可以这样使用:

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

您还可以使用命名参数:

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

更新:数据类

随着Python 3.7中数据类的引入,我们变得非常接近。

下面的示例类似于下面的NamedTuple示例,但是结果对象是可变的,并且允许使用默认值。

1
2
3
4
5
6
7
8
9
10
11
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开始,它变得非常简单漂亮(IMHO),只要您可以忍受不变性即可。

引入了一种声明NamedTuples的新方法,该方法还允许类型注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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'))

您可以在很多情况下使用元组,而在C中使用结构(例如x,y坐标或RGB颜色)。

对于其他所有内容,您都可以使用字典或类似这样的实用程序类:

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

我认为"确定性"讨论在此处,即Python Cookbook的发行版本。


也许您正在寻找没有构造函数的Structs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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


字典呢?

像这样:

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

然后,您可以使用它来操纵值:

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

并且值不必是字符串。它们几乎可以是任何其他对象。


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.

您可以使用字典访问类的字段,因为类的字段,其方法及其所有属性都使用dict在内部存储(至少在CPython中)。

...这将引导我们提出您的第二条评论。相信Python字典是"沉重的"是一个极端的非Python的概念。阅读此类评论会杀死我的Python Zen。这不好。

您会看到,在声明一个类时,实际上是在围绕字典创建一个非常复杂的包装器-因此,如果有的话,与使用简单的字典相比,您将增加更多的开销。顺便说一句,开销在任何情况下都是没有意义的。如果您正在处理性能关键型应用程序,请使用C或其他语言。


我还想添加一个使用插槽的解决方案:

1
2
3
4
5
class Point:
    __slots__ = ["x","y"]
    def __init__(self, x, y):
        self.x = x
        self.y = y

一定要查看文档中的插槽,但是插槽的快速说明是python的一种说法:"如果您可以将这些属性以及仅这些属性锁定到类中,以致您承诺一旦该类就不会添加任何新属性实例化(是的,您可以向类实例添加新属性,请参见下面的示例),然后我将取消大的内存分配,该内存分配允许向类实例添加新属性,并仅使用我需要的这些插槽化属性。"

向类实例添加属性的示例(因此不使用插槽):

1
2
3
4
5
6
7
8
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

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

输出8

尝试向使用插槽的类实例添加属性的示例:

1
2
3
4
5
6
7
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'

这可以有效地用作结构,并且比类使用更少的内存(就像结构一样,尽管我没有确切研究多少)。如果要创建大量对象实例并且不需要添加属性,建议使用插槽。一个点对象就是一个很好的例子,因为可能实例化许多点来描述一个数据集。


您还可以按位置将init参数传递给实例变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 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

您可以将标准库中可用的C结构子类化。 ctypes模块提供了一个Structure类。来自文档的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
>>> 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
>>>


每当我需要"行为也像字典的即时数据对象"(我不认为C结构!)时,我都会想到这个可爱的技巧:

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

现在您可以说:

1
2
3
4
struct = Map(field1='foo', field2='bar', field3=42)

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

当您需要一个"不是类的数据包"以及namedtuple难以理解时,非常方便。


您可以通过以下方式在python中访问C-Style结构。

1
2
3
4
class cstruct:
    var_i = 0
    var_f = 0.0
    var_str =""

如果您只想使用cstruct的对象

1
2
3
4
5
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的对象数组

1
2
3
4
5
6
7
8
9
10
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,请定义结构的成员变量。


这里的一些答案非常详尽。我找到的最简单的选项是(来自:http://norvig.com/python-iaq.html):

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

初始化:

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

增加更多:

1
2
3
>>> options.cat ="dog"
>>> options.cat
dog

编辑:对不起,没有看到这个例子。


我编写了一个装饰器,可以将其用于任何方法,以便将传入的所有参数或任何默认值分配给该实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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,以表明在输入方法之前已正确分配了它们。

1
2
3
4
5
6
7
8
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 Meta-Classes(也是下面的装饰器版本)提出了一个解决方案。

在运行时调用__init__时,它将获取每个参数及其值,并将其作为实例变量分配给您的类。这样,您可以制作类似结构的类,而不必手动分配每个值。

我的示例没有错误检查,因此更容易理解。

1
2
3
4
5
6
7
8
9
10
11
12
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

它在起作用。

1
2
3
4
5
6
7
8
9
10
>>> 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发布了一个更干净的装饰器版本。除非您要扩展元类版本,否则我建议您使用它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> 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
>>>


我在这里看不到这个答案,因此我想添加一下,因为我现在倾向于使用Python并发现了它。 Python教程(在本例中为Python 2)给出了以下简单有效的示例:

1
2
3
4
5
6
7
8
9
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

即,创建一个空的类对象,然后实例化该字段,并动态添加字段。

这样做的好处是非常简单。缺点是它不是特别自我记录(预期的成员未在"定义"类中的任何位置列出),并且未设置的字段在访问时会引起问题。这两个问题可以通过以下方法解决:

1
2
3
4
5
class Employee:
    def __init__ (self):
        self.name = None # or whatever
        self.dept = None
        self.salary = None

现在,您至少可以一眼看出程序将期望哪些字段。

两者都容易出现错别字,john.slarly = 1000将成功。仍然可以。


我个人也喜欢这个变体。它扩展了@dF的答案。

1
2
3
4
5
6
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__)

它支持两种初始化模式(可以混合使用):

1
2
3
4
# 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)

此外,它的打印效果更好:

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

以下对结构的解决方案的灵感来自namedtuple实现和一些先前的答案。但是,与namedtuple不同,它的值是可变的,但是就像名称/属性中不可变的c样式结构一样,而普通的类或dict则不是。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
_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

用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
2
3
class myStruct:
    field1 ="one"
    field2 ="2"

您以后可以根据需要添加更多字段:

1
myStruct.field3 = 3

要获取值,请照常访问这些字段:

1
2
>>> myStruct.field1
'one'

有一个专门用于此目的的python包。见cstruct2py

cstruct2py是一个纯python库,用于从C代码生成python类,并使用它们来打包和解压缩数据。该库可以解析C头(结构,联合,枚举和数组声明),并在python中对其进行仿真。生成的pythonic类可以解析和打包数据。

例如:

1
2
3
4
5
6
7
8
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

首先,我们需要生成pythonic结构:

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

现在我们可以从C代码导入所有名称:

1
parser.update_globals(globals())

我们也可以直接这样做:

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

从C代码使用类型和定义

1
2
3
4
5
6
7
8
9
a = A()
a.x = 45
print a
buf = a.packed
b = A(buf)
print b
c = A('aaaa11112222', 2)
print c
print repr(c)

输出将是:

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

Clone

对于克隆cstruct2py运行:

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


https://stackoverflow.com/a/32448434/159695在Python3中不起作用。

https://stackoverflow.com/a/35993/159695可在Python3中使用。

我将其扩展为添加默认值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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')

如果@dataclass没有3.7,并且需要可变性,则以下代码可能对您有用。它具有很好的自我说明性和IDE友好性(自动完成),可以防止编写两次内容,易于扩展,并且测试所有实例变量是否已完全初始化非常简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)

我认为Python结构字典适合此要求。

1
2
3
4
d = dict{}
d[field1] = field1
d[field2] = field2
d[field2] = field3

这是一个快速而肮脏的把戏:

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

如何运作?它只是重复使用内置类Warning(从Exception派生)并按原样使用它,因为它是您自己定义的类。

优点是您不需要首先导入或定义任何内容,"警告"是一个简短的名称,并且还可以清楚地表明您正在做一些肮脏的事情,除了您的小型脚本之外,不应在其他地方使用。

顺便说一句,我试图找到一些更简单的东西,例如ms = object(),但是没有(最后一个例子不起作用)。如果您有一个,我很感兴趣。


推荐阅读