Python 中的类 C 结构

Posted

技术标签:

【中文标题】Python 中的类 C 结构【英文标题】:C-like structures in Python 【发布时间】:2010-09-07 08:25:47 【问题描述】:

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

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

【问题讨论】:

半相关地,代数数据类型绝对很棒,但是要很好地使用它们,您通常需要模式匹配。 这个方法除了写起来乏味之外还有什么问题吗? 您可能会发现 dstruct 很有用:github.com/dorkitude/dstruct @levesque 比MyStruct = namedtuple("MyStruct", "field1 field2 field3") 更难在没有拼写错误的情况下重构,更难在略读代码时一目了然 跳至 2018 年答案:***.com/a/45426493/703382 【参考方案1】:

更新:数据类

随着 Python 3.7Data Classes 的引入,我们非常接近。

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

这与新的 typing 模块很好地配合,以防您想使用更具体的类型注释。

我一直在拼命地等待这个!如果你问我,Data Classes 和新的 NamedTuple 声明,结合 typing 模块是天赐之物!

改进的 NamedTuple 声明

自从 Python 3.6 以来,它变得非常简单和美观(恕我直言),只要您可以忍受 不变性

引入了new way of declaring NamedTuples,它也允许type annotations:

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 dataclass 模块是 Python 3.7 中的新模块,但您可以使用 pip install dataclasses。它是 Python 3.6 的后向端口。 pypi.org/project/dataclasses/#description +1 用于改进的 NamedTuple 声明。如果您有多个变量,那么旧的方式读起来真的很不愉快...... @PurpleIce 是 PEP 557 的实现,数据类 @dataclass 详细信息在这里:pypi.org/project/dataclasses/#description 即使使用类型化的字段,您也可以将任何类型分配给类中的任何变量。例如,这有效:my_item = MyStruct(123, 123, 123, 123) 并且 my_item 中的每个字段都是一个值为 123 的整数。数据类也是如此【参考方案2】:

使用named tuple,它是在 Python 2.6 的标准库中添加到 collections module 的。如果您需要支持 Python 2.4,也可以使用 Raymond Hettinger 的 named tuple 配方。

这很适合您的基本示例,但也涵盖了您以后可能会遇到的一堆边缘情况。您上面的片段将写为:

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 - 就我而言,这只是一个加号。 不错的解决方案。你将如何遍历这些元组的数组?我假设字段 1-3 在元组对象中必须具有相同的名称。 namedtuple 最多可以有四个参数,所以我们如何将结构与更多数据成员映射到相应的 namedtuple @Kapil - namedtuple 的第二个参数应该是成员名称的列表。该列表可以是任意长度。【参考方案3】:

您可以将元组用于很多需要在 C 中使用结构的事情(例如 x、y 坐标或 RGB 颜色)。

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

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

我认为“权威”讨论是here,在 Python Cookbook 的已发布版本中。

【讨论】:

空班也会做同样的事情吗? 注意如果你是 python 新手:元组是只读的,与 C 结构不同 @KurtLiu 不,它可能会说TypeError: this constructor takes no arguments 这使用一个对象,内部有一个字典__dict__(嗯,像所有对象一样,除非你使用__slots__)。那么为什么不直接使用 dict 呢? mystruct = 'field1': value1, 'field2': value2。 TL;DR:这里您创建一个对象只是为了使用其内部字典 object.__dict__,因此从一开始就简单地使用字典 b>. ...特别是因为如果您喜欢带有关键字的函数调用语法比常规 dict 语法更好,那么您可以只使用 a = dict(foo=123, bar=456) 来制作该 dict,而且 str()/repr() 是比仅仅提供对象 ID 更有用。【参考方案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

【讨论】:

您在此处所做的工作在技术上是可行的,但许多用户可能不会立即明白为什么它有效。您在class Sample: 下的声明不会立即执行任何操作;他们设置类属性。这些总是可以访问,例如Sample.name. 实际上所做的是在运行时向对象s1s2 添加实例属性。除非另有禁止,您可以随时在任何类的任何实例上添加或修改name 属性,无论该类是否具有name 属性。这样做最大的功能问题可能是同一类的不同实例的行为会有所不同,具体取决于您是否设置了name。如果您更新Sample.name,任何没有明确设置name 属性的对象都将返回新的name 这与结构非常接近 - 没有方法的短“类”,具有默认值的“字段”(我知道是类属性)。只要它不是可变类型(dict,list),就可以了。当然,您可以使用 PEP-8 或“友好”IDE 检查,例如 PyCharm 的“类没有 init 方法”。 我试验了 Channing Moore 描述的副作用。如果您问我,不值得使用几个 self 关键字和构造函数行。如果 Jose 可以编辑他的答案以添加有关在实例间意外共享值的风险的警告消息,我将不胜感激。 @ChanningMoore:我试图重现您所描述的问题,但失败了。您能否提供一个出现问题的最小工作示例?【参考方案5】:

字典怎么样?

类似这样的:

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

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

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

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

【讨论】:

这也是我的方法,但我觉得这很危险,因为字典可以接受任何键值。如果在我打算设置 myStruct["field"] 时设置 myStruct["ffield"],则不会出现错误。当我稍后使用或重新使用 myStruct["field"] 时,问题可能(或可能不会)变得明显。我喜欢 PabloG 的方法。 PabloG 也存在同样的问题。尝试将以下代码添加到他的:pt3.w = 1 print pt3.w 在带有 dicts 的语言中,最好使用它们,特别是对于正在序列化的对象,因为您可以自动使用 import json 来保存它们和其他序列化库,只要您不这样做'你的字典里没有奇怪的东西。 dicts 是保持数据和逻辑分离的解决方案,对于不想编写自定义序列化和反序列化函数并且不想使用非便携式序列化程序(如 pickle)的人来说,它比结构更好。【参考方案6】:

dF:那太酷了……我没有 知道我可以访问 使用字典的类。

马克:我希望我有的情况 这正是我想要一个元组的时候 但没有什么比 字典。

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

...这将我们引向您的第二条评论。相信 Python dicts 是“沉重的”是一个非常非 Python 的概念。阅读这样的 cmets 会杀死我的 Python Zen。这不好。

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

【讨论】:

#1,赛通!= CPython。我想你在谈论 CPython,用 C 编写的 Python 的实现,而不是 Cython,一个将 Python 代码交叉编译成 C 代码的项目。我编辑了你的答案来解决这个问题。 #2,我认为当他说 dicts 很重时,他指的是语法。 self['member']self.member 长 3 个字符,而且这些字符都比较不适合手腕。【参考方案7】:

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

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 对我来说是新的信息【参考方案8】:

您可以对标准库中可用的 C 结构进行子类化。 ctypes 模块提供Structure class。文档中的示例:

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

【讨论】:

【参考方案9】:

你也可以通过位置将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

【讨论】:

按位置更新会忽略属性的声明顺序,而是使用它们的字母排序。因此,如果您更改 Point3dStruct 声明中的行顺序,Point3dStruct(5, 6) 将无法按预期工作。奇怪的是,6 年来没有人写过这篇文章。 可以为您的精彩代码添加 Python 3 版本吗?做得好!我喜欢你把一些抽象的东西用第二个特定的类明确化。这应该有利于错误处理/捕获。对于 Python 3,只需更改 print > print()attrs[n] > next(attrs)(过滤器现在是它自己的可迭代对象,需要 next)。【参考方案10】:

每当我需要一个“行为也像字典一样的即时数据对象”(我想到 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'])

当您需要“不是类的数据包”时,以及当命名元组难以理解时,非常方便......

【讨论】:

我使用 pandas.Series(a=42) ;-)【参考方案11】:

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

编辑:抱歉没有看到这个例子已经更远了。

【讨论】:

缺少 imo 重要的 __repr__() 方法 同意!可以把它扔进去,是的。【参考方案12】:

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

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。

【讨论】:

这与***.com/a/3761729/1877426 中的内容有什么不同吗?【参考方案13】:

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

我posted it on 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
>>> 

【讨论】:

该死的——我今天花了两个小时编写自己的装饰器来做这件事,然后我发现了这个。无论如何,发布我的,因为它处理默认值,而你的没有。 ***.com/a/32448434/901641 +1 用于提及 func_code。开始朝那个方向挖掘,在那里发现了很多有趣的东西。【参考方案14】:

我写了一个装饰器,你可以在任何方法上使用它,以便将所有传入的参数或任何默认值分配给实例。

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__

【讨论】:

【参考方案15】:

我在这里看不到这个答案,所以我想我会添加它,因为我现在正在学习 Python 并且刚刚发现它。 Python tutorial(本例中为 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】:

这是一个使用类(从未实例化)来保存数据的解决方案。我喜欢这种方式只需要很少的打字并且不需要任何额外的包等等。

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

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

myStruct.field3 = 3

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

>>> myStruct.field1
'one'

【讨论】:

它有点工作,但不知道如何打印出来myStruct Out[5]: __main__.myStruct 你为什么不实例化这个类呢?如果想对不同的值使用相同的结构怎么办? s1 = myStruct; s2 = myStruct; s1.field1 = "two" 修改 s2。这取决于用例,但我认为实际实例化结构通常更好/更安全:s1 myStruct(); s2 = myStruct() @StephenBoesch 是的,这是一种非常基本的方法。我有时将它用于临时脚本,因为它易于键入,但对于需要更多功能的代码,我会使用更成熟的解决方案,如数据类。 @normanius 当然,如果您需要复制“结构”,则需要更加聪明,我同意实例化可以在需要时为您创建副本。【参考方案17】:

就个人而言,我也喜欢这种变体。它扩展了@dF's answer。

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

【讨论】:

【参考方案18】:

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

cstruct2py 是一个纯 python 库,用于从 C 代码生成 python 类并使用它们来打包和解包数据。该库可以解析 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"

如何使用

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

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

【讨论】:

【参考方案19】:

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

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

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

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

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

【讨论】:

【参考方案20】:

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(便于阅读和使用)之间进行选择

您可以查看此链接 (Usage of slots ) 以获取更多 __slots__ 信息。

【讨论】:

【参考方案21】:

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

https://***.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__【参考方案22】:

以下结构的解决方案受到 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'

【讨论】:

【参考方案23】:

如果您没有 3.7 for @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)

【讨论】:

【参考方案24】:

我发现这样做的最佳方法是使用自定义字典类,如本文所述:https://***.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 中的值:

print(my_struct.com1.inst)

=>[5]

【讨论】:

【参考方案25】:

我觉得Python结构字典很适合这个需求。

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

【讨论】:

以上是关于Python 中的类 C 结构的主要内容,如果未能解决你的问题,请参考以下文章

使用C语言为python编写动态模块--在C中实现python中的类

C ++中模板类型中的类层次结构

Python C api遍历模块中的类

Python 中的类定义循环引用

python的类和对象中的super函数的问题

python如何调用C++外部库中的类?