问题:Python中类似C的结构

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

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

Is there a way to conveniently define a C-like structure in Python? I’m tired of writing stuff like:

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

回答 0

使用命名的tuple,它已添加到Python 2.6的标准库的collections模块中。如果您需要支持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")

Use a named tuple, which was added to the collections module in the standard library in Python 2.6. It’s also possible to use Raymond Hettinger’s named tuple recipe if you need to support Python 2.4.

It’s nice for your basic example, but also covers a bunch of edge cases you might run into later as well. Your fragment above would be written as:

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

The newly created type can be used like this:

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

You can also use named arguments:

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

回答 1

更新:数据类

通过引入数据类的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开始,只要您可以忍受不变性,它就会变得非常简单和美观(IMHO)。

一个声明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'))

Update: Data Classes

With the introduction of Data Classes in Python 3.7 we get very close.

The following example is similar to the NamedTuple example below, but the resulting object is mutable and it allows for default values.

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)

This plays nicely with the new typing module in case you want to use more specific type annotations.

I’ve been waiting desperately for this! If you ask me, Data Classes and the new NamedTuple declaration, combined with the typing module are a godsend!

Improved NamedTuple declaration

Since Python 3.6 it became quite simple and beautiful (IMHO), as long as you can live with immutability.

A new way of declaring NamedTuples was introduced, which allows for type annotations as well:

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

回答 2

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

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

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

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

You can use a tuple for a lot of things where you would use a struct in C (something like x,y coordinates or RGB colors for example).

For everything else you can use dictionary, or a utility class like this one:

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

I think the “definitive” discussion is here, in the published version of the Python Cookbook.


回答 3

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

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

Perhaps you are looking for Structs without constructors:

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

回答 4

字典怎么样?

像这样:

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

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

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

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

How about a dictionary?

Something like this:

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

Then you can use this to manipulate values:

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

And the values don’t have to be strings. They can be pretty much any other object.


回答 5

dF:太酷了……我不知道我可以使用dict访问类中的字段。

马克:我希望我遇到的情况恰好是当我想要一个元组,却又没有字典那么重的时候。

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

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

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

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.

You can access the fields of a class using a dictionary because the fields of a class, its methods and all its properties are stored internally using dicts (at least in CPython).

…Which leads us to your second comment. Believing that Python dicts are “heavy” is an extremely non-pythonistic concept. And reading such comments kills my Python Zen. That’s not good.

You see, when you declare a class you are actually creating a pretty complex wrapper around a dictionary – so, if anything, you are adding more overhead than by using a simple dictionary. An overhead which, by the way, is meaningless in any case. If you are working on performance critical applications, use C or something.


回答 6

您可以将标准库中可用的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
>>>

You can subclass the C structure that is available in the standard library. The ctypes module provides a Structure class. The example from the docs:

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

回答 7

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

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’

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

I would also like to add a solution that uses slots:

class Point:
    __slots__ = ["x", "y"]
    def __init__(self, x, y):
        self.x = x
        self.y = y

Definitely check the documentation for slots but a quick explanation of slots is that it is python’s way of saying: “If you can lock these attributes and only these attributes into the class such that you commit that you will not add any new attributes once the class is instantiated (yes you can add new attributes to a class instance, see example below) then I will do away with the large memory allocation that allows for adding new attributes to a class instance and use just what I need for these slotted attributes”.

Example of adding attributes to class instance (thus not using slots):

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

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

Output: 8

Example of trying to add attributes to class instance where slots was used:

class Point:
    __slots__ = ["x", "y"]
    def __init__(self, x, y):
        self.x = x
        self.y = y

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

Output: AttributeError: ‘Point’ object has no attribute ‘z’

This can effectively works as a struct and uses less memory than a class (like a struct would, although I have not researched exactly how much). It is recommended to use slots if you will be creating a large amount of instances of the object and do not need to add attributes. A point object is a good example of this as it is likely that one may instantiate many points to describe a dataset.


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

You can also pass the init parameters to the instance variables by position

# 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结构!)时,我就会想到这个可爱的技巧:

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

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

Whenever I need an “instant data object that also behaves like a dictionary” (I don’t think of C structs!), I think of this cute hack:

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

Now you can just say:

struct = Map(field1='foo', field2='bar', field3=42)

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

Perfectly handy for those times when you need a “data bag that’s NOT a class”, and for when namedtuples are incomprehensible…


回答 10

您可以通过以下方式在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,请定义结构的成员变量。

You access C-Style struct in python in following way.

class cstruct:
    var_i = 0
    var_f = 0.0
    var_str = ""

if you just want use object of 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)

if you want to create an array of objects of 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)

Note: instead of ‘cstruct’ name, please use your struct name instead of var_i, var_f, var_str, please define your structure’s member variable.


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

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

Some the answers here are massively elaborate. The simplest option I’ve found is (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)

Initialising:

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

adding more:

>>> options.cat = "dog"
>>> options.cat
dog

edit: Sorry didn’t see this example already further down.


回答 12

这可能有点晚了,但是我使用Python Meta-Classes(也是下面的装饰器版本)提出了一个解决方案。

什么时候 __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
>>> 

This might be a bit late but I made a solution using Python Meta-Classes (decorator version below too).

When __init__ is called during run time, it grabs each of the arguments and their value and assigns them as instance variables to your class. This way you can make a struct-like class without having to assign every value manually.

My example has no error checking so it is easier to follow.

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 

Here it is in action.

>>> class MyClass(object):
    __metaclass__ = MyStruct
    def __init__(self, a, b, c):
        pass


>>> my_instance = MyClass(1, 2, 3)
>>> my_instance.a
1
>>> 

I posted it on reddit and /u/matchu posted a decorator version which is cleaner. I’d encourage you to use it unless you want to expand the metaclass version.

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

回答 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。然后self,我打印所有3个引用,以显示在输入方法之前已正确分配了它们。

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__

I wrote a decorator which you can use on any method to make it so that all of the arguments passed in, or any defaults, are assigned to the instance.

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 quick demonstration. Note that I use a positional argument a, use the default value for b, and a named argument c. I then print all 3 referencing self, to show that they’ve been properly assigned before the method is entered.

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

Note that my decorator should work with any method, not just __init__.


回答 14

我在这里看不到这个答案,因此我想添加一下,因为我现在倾向于使用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会成功。仍然可以。

I don’t see this answer here, so I figure I’ll add it since I’m leaning Python right now and just discovered it. The Python tutorial (Python 2 in this case) gives the following simple and effective example:

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

That is, an empty class object is created, then instantiated, and the fields are added dynamically.

The up-side to this is its really simple. The downside is it isn’t particularly self-documenting (the intended members aren’t listed anywhere in the class “definition”), and unset fields can cause problems when accessed. Those two problems can be solved by:

class Employee:
    def __init__ (self):
        self.name = None # or whatever
        self.dept = None
        self.salary = None

Now at a glance you can at least see what fields the program will be expecting.

Both are prone to typos, john.slarly = 1000 will succeed. Still, it works.


回答 15

这是一个使用类(从未实例化)保存数据的解决方案。我喜欢这种方式,几乎不需要打字,也不需要任何其他软件包

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

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

myStruct.field3 = 3

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

>>> myStruct.field1
'one'

Here is a solution which uses a class (never instantiated) to hold data. I like that this way involves very little typing and does not require any additional packages etc.

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

You can add more fields later, as needed:

myStruct.field3 = 3

To get the values, the fields are accessed as usual:

>>> myStruct.field1
'one'

回答 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}

Personally, I like this variant too. It extends @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__)

It supports two modes of initialization (that can be blended):

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

Also, it prints nicer:

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

回答 17

以下对结构的解决方案的灵感来自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'

The following solution to a struct is inspired by the namedtuple implementation and some of the previous answers. However, unlike the namedtuple it is mutable, in it’s values, but like the c-style struct immutable in the names/attributes, which a normal class or dict isn’t.

_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

Usage:

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

有一个专门用于此目的的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

There is a python package exactly for this purpose. see cstruct2py

cstruct2py is a pure python library for generate python classes from C code and use them to pack and unpack data. The library can parse C headres (structs, unions, enums, and arrays declarations) and emulate them in python. The generated pythonic classes can parse and pack the data.

For example:

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

First we need to generate the pythonic structs:

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

Now we can import all names from the C code:

parser.update_globals(globals())

We can also do that directly:

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

Using types and defines from the C code

a = A()
a.x = 45
print a
buf = a.packed
b = A(buf)
print b
c = A('aaaa11112222', 2)
print c
print repr(c)

The output will be:

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

Clone

For clone cstruct2py run:

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

回答 19

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

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

I think Python structure dictionary is suitable for this requirement.

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

回答 20

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

https://stackoverflow.com/a/32448434/159695 does not work in Python3.

https://stackoverflow.com/a/35993/159695 works in Python3.

And I extends it to add default values.

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

回答 21

如果@dataclass没有3.7,并且需要可变性,则以下代码可能对您有用。它具有很好的自我说明性和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)

If you don’t have a 3.7 for @dataclass and need mutability, the following code might work for you. It’s quite self-documenting and IDE-friendly (auto-complete), prevents writing things twice, is easily extendable and it is very simple to test that all instance variables are completely initialized:

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)

回答 22

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

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

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

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

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

Here is a quick and dirty trick:

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

How does it works? It just re-use the builtin class Warning (derived from Exception) and use it as it was you own defined class.

The good points are that you do not need to import or define anything first, that “Warning” is a short name, and that it also makes clear you are doing something dirty which should not be used elsewhere than a small script of yours.

By the way, I tried to find something even simpler like ms = object() but could not (this last exemple is not working). If you have one, I am interested.


回答 23

我发现做到这一点的最佳方法是使用自定义词典类,如本文中所述: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内的值:

print(my_struct.com1.inst)

=>[5]

The best way I found to do this was to use a custom dictionary class as explained in this post: https://stackoverflow.com/a/14620633/8484485

If iPython autocompletion support is needed, simply define the dir() function like this:

class AttrDict(dict):
    def __init__(self, *args, **kwargs):
        super(AttrDict, self).__init__(*args, **kwargs)
        self.__dict__ = self
    def __dir__(self):
        return self.keys()

You then define your pseudo struct like so: (this one is nested)

my_struct=AttrDict ({
    'com1':AttrDict ({
        'inst':[0x05],
        'numbytes':2,
        'canpayload':False,
        'payload':None
    })
})

You can then access the values inside my_struct like this:

print(my_struct.com1.inst)

=>[5]


回答 24

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

NamedTuple is comfortable. but there no one shares the performance and storage.

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)

If your __dict__ is not using, please choose between __slots__ (higher performance and storage) and NamedTuple (clear for reading and use)

You can review this link(Usage of slots ) to get more __slots__ information.


声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。