Python中的类c结构

是否有一种方法可以方便地在Python中定义类c结构?我厌倦了写这样的东西:

class MyStruct():
def __init__(self, field1, field2, field3):
self.field1 = field1
self.field2 = field2
self.field3 = field3
787009 次浏览

一本字典怎么样?

就像这样:

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

然后你可以使用这个来操作值:

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

值不一定是字符串。它们可以是其他任何物体。

你可以用元组来做很多你在C语言中使用结构体的事情(比如x,y坐标或RGB颜色)。

对于其他所有的东西,你可以使用字典,或者像这一个这样的实用程序类:

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

我认为“权威”的讨论是Python Cookbook出版版中的在这里

使用叫元组,它被添加到Python 2.6标准库中的收藏模块中。如果你需要支持Python 2.4,也可以使用Raymond Hettinger的叫元组食谱。

它适用于基本示例,但也适用于稍后可能遇到的一些边缘情况。你上面的片段可以写成:

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

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

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

你也可以使用命名参数:

m = MyStruct(field1="foo", field2="bar", field3="baz")
dF:太酷了…我没有 我知道我可以访问的领域 一个使用dict的类。

马克:我希望我遇到的情况 这正是我需要一个元组的时候 但没有什么比 字典。< / p >

你可以使用字典访问类的字段,因为类的字段、它的方法和它的所有属性都是用字典存储在内部的(至少在CPython中是这样)。

...这就引出了你的第二个评论。相信Python字典是“沉重的”是一个非常非Python主义的概念。读这样的评论简直要了我的Python禅。这可不太好。

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

还可以按位置将初始化参数传递给实例变量

# 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

也许你正在寻找没有构造函数的struct:

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

每当我需要一个“行为像字典一样的即时数据对象”(我想到C结构体!),我就会想到这个可爱的hack:

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'])

当你需要一个“不是类的数据包”的时候,非常方便,当命名元组是不可理解的……

你可以通过以下方式在python中访问C-Style struct。

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)
< p >注意: 请使用你的struct名称,而不是'cstruct'名称 请定义结构的成员变量,而不是var_i, var_f, var_str。< / p >

这可能有点晚了,但我使用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
>>>

您可以子类化标准库中可用的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教程(在本例中是python2)给出了以下简单而有效的示例:

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< em > < / em >数据类的引入,我们已经非常接近了。

下面的示例类似于下面的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'))

这里的一些答案非常详尽。我找到的最简单的选项是(from: 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

编辑:抱歉,在下面没有看到这个例子。

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')

就我个人而言,我也喜欢这种变体。它扩展了@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

'Point'对象没有属性'z'

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

有一个python包正是用于此目的。看到# EYZ0

cstruct2py是一个纯python库,用于从C代码生成python类,并使用它们来打包和解包数据。该库可以解析C头文件(结构体、联合、枚举和数组声明),并在python中模拟它们。生成的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"

如何使用

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

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

如果您没有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'

这里有一个快速而肮脏的技巧:

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

它是如何工作的?它只是重用内置类Warning(派生自Exception),并将其作为您自己定义的类使用。

优点是您不需要首先导入或定义任何东西,“警告”是一个简短的名称,并且它还清楚地表明您正在做一些肮脏的事情,不应该在其他地方使用,而应该在您的小脚本中使用。

顺便说一下,我试图找到一些更简单的东西,如ms = object(),但不能(最后一个例子是不工作的)。如果你有的话,我很感兴趣。

我发现做到这一点的最好方法是使用自定义字典类,如本文所述:https://stackoverflow.com/a/14620633/8484485

如果需要iPython自动补全支持,只需像这样定义dir()函数:

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中的值:

# EYZ0

= > # EYZ0

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__信息