有没有一种方法可以方便地在Python中定义类似C的结构?我厌倦了写这样的东西:
class MyStruct():
def __init__(self, field1, field2, field3):
self.field1 = field1
self.field2 = field2
self.field3 = field3
有没有一种方法可以方便地在Python中定义类似C的结构?我厌倦了写这样的东西:
class MyStruct():
def __init__(self, field1, field2, field3):
self.field1 = field1
self.field2 = field2
self.field3 = field3
随着数据类在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声明,再加上键入模块,简直是天赐良机!
自从Python 3.6以来,它变得非常简单和漂亮(IMHO),只要你能忍受的不变性。
A引入了一种新的声明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 ))
使用命名元组,已添加到集合模块,位于Python 2.6的标准库中。也可以使用Raymond Hettinger的命名元组配方,如果您需要支持Python 2.4。
这对你的基本示例来说很好,但也涵盖了你以后可能遇到的一些边缘案例。你上面的片段会写为:
from collections import namedtuple
MyStruct = namedtuple("MyStruct", "field1 field2 field3")
新创建的类型可以这样使用:
m = MyStruct("foo", "bar", "baz")
您也可以使用命名参数:
m = MyStruct(field1="foo", field2="bar", field3="baz")
也许您正在寻找没有构造函数的结构:
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
一本字典怎么样?
类似这样的内容:
myStruct = { field1 : some val , field2 : some val }
然后您可以使用它来操纵值:
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.
您可以使用字典访问类的字段,因为类的字段、方法及其所有财产都是使用dicts在内部存储的(至少在CPython中是这样)。
…这就引出了你的第二条评论。相信Python dicts是“沉重的”是一个极端非Python主义的概念。阅读这样的评论会扼杀我的Python Zen。这不好。
你看,当你声明一个类时,你实际上是在围绕一个字典创建一个非常复杂的包装器——所以,如果有什么不同的话,那就是你比使用一个简单的字典增加了更多的开销。顺便说一句,这种开销在任何情况下都是毫无意义的。如果您正在处理性能关键型应用程序,请使用C或其他语言。
我还想添加一个使用插槽:
class Point:
__slots__ = ["x", "y"]
def __init__(self, x, y):
self.x = x
self.y = y
一定要查看文档中的slots,但对slots的快速解释是,这是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:点对象没有属性z
这可以有效地作为一个结构工作,并且比一个类使用更少的内存(就像一个结构一样,尽管我还没有研究具体需要多少内存)。如果要创建大量对象实例并且不需要添加属性,则建议使用槽。点对象就是一个很好的例子,因为很可能可以实例化许多点来描述数据集。
您可以对标准库中可用的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
>>>
您还可以通过位置将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
每当我需要一个“行为也像字典的即时数据对象”时(我不要想到C structs!),我就会想到这个可爱的破解:
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 ])
当你需要一个“不是类的数据包”时,以及当命名的元组无法理解时,都非常方便。。。
这里的一些答案非常详细。我找到的最简单的选项是(来自: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
编辑:很抱歉没有看到这个例子。
您可以通过以下方式访问python中的C样式结构。
class cstruct:
var_i = 0
var_f = 0.0
var_str = ""
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)
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.
这可能有点晚,但我使用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发布了一个更干净的decorator版本。我鼓励您使用它,除非您想扩展元类版本。
>>> 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
>>>
我编写了一个装饰器,您可以在任何方法上使用它来制作它,以便将传入的所有参数或任何默认值都分配给实例。
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个引用<code>self</code>的值,以表明在输入方法之前已经正确分配了它们。
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 )
请注意,我的decorator应该使用任何方法,而不仅仅是__init__
。
我在这里看不到这个答案,所以我想我会添加它,因为我现在正在学习Python,并且刚刚发现了它https://docs.python.org/2/tutorial/classes.html“rel=”noreferrer“>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
会成功。尽管如此,它还是有效的。
这里有一个使用类(从未实例化)来保存数据的解决方案。我喜欢这种方式只需要很少的打字,不需要任何额外的软件包等
class myStruct:
field1 = "one"
field2 = "2"
您可以稍后根据需要添加更多字段:
myStruct.field3 = 3
要获取这些值,可以像往常一样访问这些字段:
>>> myStruct.field1
one
就我个人而言,我也喜欢这种变体。它扩展了@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}
有一个python包正是为了这个目的。请参阅结构2
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 == "x34x12x00x00x78x56x00x00"
如何使用
首先,我们需要生成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)
克隆
对于clone<code>cstruct2py</code>运行:
git clone https://github.com/st0ky/cstruct2py.git --recursive
这里有一个快速而肮脏的技巧:
>>> ms = Warning()
>>> ms.foo = 123
>>> ms.bar = akafrit
它是如何工作的?它只是重复使用内置类Warning
(派生自Exception
),并将其用作自己定义的类。
好的一点是,你不需要首先导入或定义任何东西,“警告”是一个简短的名称,它也表明你在做一些肮脏的事情,除了你的小脚本之外,不应该在其他地方使用。
顺便说一句,我试图找到一些更简单的东西,比如ms=object()
,但没有找到(最后一个例子不起作用)。如果你有,我很感兴趣。
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__
未使用,请在__插槽_
(更高的性能和存储空间)和NamedTuple
之间进行选择(清除以供阅读和使用)
You can review this link(Usage of slots
) to get more __slots__
information.
https://stackoverflow.com/a/32448434/159695在Python 3中不起作用。
https://stackoverflow.com/a/35993/159695在Python 3中工作。
我扩展它以添加默认值。
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 )
以下结构解决方案的灵感来自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
如果您没有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)
我发现最好的方法是使用自定义字典类,如本文所述: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中的值,如下所示:
打印(my_struct.com1.inst)
=><代码>[5]代码>
我能想到的最干净的方法是使用类装饰器,它允许您声明一个静态类并将其重写为具有普通命名财产的结构:
from as_struct import struct
@struct
class Product():
name = unknown product
quantity = -1
sku = -
# create instance
p = Product( plush toy , sku= 12-345-6789 )
# check content:
p.name # plush toy
p.quantity # -1
p.sku # 12-345-6789
使用以下装饰器代码:
def struct(struct_class):
# create a new init
def struct_init(self, *args, **kwargs):
i = 0 # we really don t need enumerate() here...
for value in args:
name = member_names[i]
default_value = member_values[i]
setattr(self, name, value if value is not None else default_value)
i += 1 # ...we just need to inc an int
for key,value in kwargs.items():
i = member_names.index(key)
default_value = member_values[i]
setattr(self, key, value if value is not None else default_value)
# extract the struct members
member_names = []
member_values = []
for attr_name in dir(struct_class):
if not attr_name.startswith( _ ):
value = getattr(struct_class, attr_name)
if not callable(value):
member_names.append(attr_name)
member_values.append(value)
# rebind and return
struct_class.init = struct_init
return struct_class
它的工作原理是获取类,提取字段名及其默认值,然后重写类的__init__
函数,以在知道哪个参数索引映射到哪个属性名的基础上设置self-
属性。
我认为Python结构字典适合这个需求。
d = dict{}
d[field1] = field1
d[field2] = field2
d[field2] = field3
扩展@gz。s(通常优于此答案),对于快速且肮脏的命名元组结构,我们可以做到:
import collections
x = collections.namedtuple( foobar , foo bar )(foo=1,bar=2)
y = collections.namedtuple( foobar , foo bar )(foo=3,bar=4)
print(x,y)
>foobar(foo=1, bar=2) foobar(foo=3, bar=4)